Skip to content

Commit

Permalink
timeline ready
Browse files Browse the repository at this point in the history
  • Loading branch information
ildyria committed Nov 10, 2024
1 parent 574533a commit bf8c541
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 110 deletions.
4 changes: 2 additions & 2 deletions database/migrations/2024_10_30_064336_timeline_options.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public function getConfigs(): array
],
[
'key' => 'timeline_photo_date_format_day',
'value' => 'Y-M-j',
'value' => 'j M Y',
'cat' => self::TIMELINE,
'type_range' => self::STRING_REQ,
'description' => 'Format the date at day granularity for photos',
Expand Down Expand Up @@ -141,7 +141,7 @@ public function getConfigs(): array
],
[
'key' => 'timeline_album_date_format_day',
'value' => 'M-j',
'value' => 'j M',
'cat' => self::TIMELINE,
'type_range' => self::STRING_REQ,
'description' => 'Format the date at day granularity for albums',
Expand Down
101 changes: 80 additions & 21 deletions resources/js/components/gallery/PhotoThumbPanel.vue
Original file line number Diff line number Diff line change
@@ -1,39 +1,78 @@
<template>
<Panel id="lychee_view_content" :header="$t(props.header)" class="w-full border-0">
<template #icons>
<a class="px-1 cursor-pointer group" @click="(layout = 'square') && activateLayout()" :title="$t('lychee.LAYOUT_SQUARES')">
<a
class="px-1 cursor-pointer group inline-block h-8"
@click="(layout = 'square') && activateLayout()"
:title="$t('lychee.LAYOUT_SQUARES')"
>
<MiniIcon icon="squares" fill="fill-transparent" :class="squareClass" />
</a>
<a class="px-1 cursor-pointer group" @click="(layout = 'justified') && activateLayout()" :title="$t('lychee.LAYOUT_JUSTIFIED')">
<a
class="px-1 cursor-pointer group inline-block h-8"
@click="(layout = 'justified') && activateLayout()"
:title="$t('lychee.LAYOUT_JUSTIFIED')"
>
<MiniIcon icon="justified" fill="" :class="justifiedClass" />
</a>
<a class="px-1 cursor-pointer group" @click="(layout = 'masonry') && activateLayout()" :title="$t('lychee.LAYOUT_MASONRY')">
<a
class="px-1 cursor-pointer group inline-block h-8"
@click="(layout = 'masonry') && activateLayout()"
:title="$t('lychee.LAYOUT_MASONRY')"
>
<MiniIcon icon="masonry" fill="fill-transparent" :class="masonryClass" />
</a>
<a class="px-1 cursor-pointer group" @click="(layout = 'grid') && activateLayout()" :title="$t('lychee.LAYOUT_GRID')">
<a class="px-1 cursor-pointer group inline-block h-8" @click="(layout = 'grid') && activateLayout()" :title="$t('lychee.LAYOUT_GRID')">
<MiniIcon icon="grid" fill="fill-transparent" :class="gridClass" />
</a>
</template>
<div class="relative flex flex-wrap flex-row flex-shrink w-full justify-start align-top" id="photoListing">
<template v-for="(photo, idx) in props.photos">
<PhotoThumb
@click="maySelect(idx, $event)"
@contextmenu.prevent="menuOpen(idx, $event)"
:is-selected="props.selectedPhotos.includes(photo.id)"
:photo="photo"
:album="props.album"
:is-lazy="idx > 10"
/>
</template>
</div>
<PhotoThumbPanelList
v-if="isTimeline === false"
:photos="props.photos"
:layout="layout"
:album="props.album"
:galleryConfig="props.galleryConfig"
:selectedPhotos="props.selectedPhotos"
:iter="0"
@clicked="propagateClicked"
@contexted="propagateMenuOpen"
:isTimeline="isTimeline"
/>
<template v-else>
<Timeline v-if="is_timeline_left_border_visible" :value="photosTimeLine" :pt:eventopposite:class="'hidden'" class="mt-4">
<template #content="slotProps">
<div class="flex flex-wrap flex-row flex-shrink w-full justify-start gap-1 sm:gap-2 md:gap-4 pb-8">
<div class="w-full text-left font-semibold text-muted-color-emphasis text-lg">{{ slotProps.item.header }}</div>
<PhotoThumbPanelList
:photos="slotProps.item.data"
:layout="layout"
:album="props.album"
:galleryConfig="props.galleryConfig"
:selectedPhotos="props.selectedPhotos"
:iter="slotProps.item.iter"
:isTimeline="isTimeline"
@clicked="propagateClicked"
@contexted="propagateMenuOpen"
/>
</div>
</template>
</Timeline>
</template>
</Panel>
</template>
<script setup lang="ts">
import { onMounted, onUpdated } from "vue";
import { computed, onMounted, onUpdated, ref } from "vue";
import Panel from "primevue/panel";
import PhotoThumb from "@/components/gallery/thumbs/PhotoThumb.vue";
import MiniIcon from "@/components/icons/MiniIcon.vue";
import { useLayouts } from "@/layouts/PhotoLayout";
import { useLycheeStateStore } from "@/stores/LycheeState";
import { storeToRefs } from "pinia";
import { SplitData, useSplitter } from "@/composables/album/splitter";
import Timeline from "primevue/timeline";
import PhotoThumbPanelList from "./PhotoThumbPanelList.vue";
const lycheeStore = useLycheeStateStore();
const { is_timeline_left_border_visible } = storeToRefs(lycheeStore);
const props = defineProps<{
header: string;
Expand All @@ -46,18 +85,38 @@ const props = defineProps<{
| undefined;
galleryConfig: App.Http.Resources.GalleryConfigs.PhotoLayoutConfig;
selectedPhotos: string[];
isTimeline: boolean;
}>();
const layout = ref(props.photoLayout);
const isTimeline = ref(props.isTimeline);
// bubble up.
const emits = defineEmits<{
clicked: [idx: number, event: MouseEvent];
contexted: [idx: number, event: MouseEvent];
}>();
const maySelect = (idx: number, e: MouseEvent) => emits("clicked", idx, e);
const menuOpen = (idx: number, e: MouseEvent) => emits("contexted", idx, e);
const propagateClicked = (idx: number, e: MouseEvent) => {
emits("clicked", idx, e);
};
const propagateMenuOpen = (idx: number, e: MouseEvent) => {
emits("contexted", idx, e);
};
const { spliter } = useSplitter();
const photosTimeLine = computed<SplitData<App.Http.Resources.Models.PhotoResource>[]>(() =>
spliter(
props.photos as App.Http.Resources.Models.PhotoResource[],
(p: App.Http.Resources.Models.PhotoResource) => p.timeline?.timeDate ?? "",
(p: App.Http.Resources.Models.PhotoResource) => p.timeline?.format ?? "Others",
),
);
// Layouts stuff
const { activateLayout, layout, squareClass, justifiedClass, masonryClass, gridClass } = useLayouts(props.galleryConfig, props.photoLayout);
const { activateLayout, squareClass, justifiedClass, masonryClass, gridClass } = useLayouts(props.galleryConfig, layout, isTimeline, "photoListing");
onMounted(() => activateLayout());
onUpdated(() => activateLayout());
</script>
47 changes: 47 additions & 0 deletions resources/js/components/gallery/PhotoThumbPanelList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<div class="relative flex flex-wrap flex-row flex-shrink w-full justify-start align-top" :id="'photoListing' + iter">
<template v-for="(photo, idx) in props.photos">
<PhotoThumb
@click="maySelect(idx + iter, $event)"
@contextmenu.prevent="menuOpen(idx + iter, $event)"
:is-selected="props.selectedPhotos.includes(photo.id)"
:photo="photo"
:album="props.album"
:is-lazy="idx + iter > 10"
/>
</template>
</div>
</template>
<script setup lang="ts">
import { useLayouts } from "@/layouts/PhotoLayout";
import { onMounted, onUpdated, Ref, ref } from "vue";
import PhotoThumb from "./thumbs/PhotoThumb.vue";
// import { watch } from 'vue';
const props = defineProps<{
photos: { [key: number]: App.Http.Resources.Models.PhotoResource };
album:
| App.Http.Resources.Models.AlbumResource
| App.Http.Resources.Models.TagAlbumResource
| App.Http.Resources.Models.SmartAlbumResource
| undefined;
galleryConfig: App.Http.Resources.GalleryConfigs.PhotoLayoutConfig;
selectedPhotos: string[];
iter: number;
}>();
const layout = defineModel("layout") as Ref<App.Enum.PhotoLayoutType>;
const isTimeline = defineModel("isTimeline") as Ref<boolean>;
const emits = defineEmits<{
clicked: [idx: number, event: MouseEvent];
contexted: [idx: number, event: MouseEvent];
}>();
const maySelect = (idx: number, e: MouseEvent) => emits("clicked", idx, e);
const menuOpen = (idx: number, e: MouseEvent) => emits("contexted", idx, e);
// Layouts stuff
const { activateLayout } = useLayouts(props.galleryConfig, layout, isTimeline, "photoListing" + props.iter);
onMounted(() => activateLayout());
onUpdated(() => activateLayout());
</script>
79 changes: 0 additions & 79 deletions resources/js/components/gallery/PhotoThumbTimeline.vue

This file was deleted.

1 change: 1 addition & 0 deletions resources/js/composables/album/splitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function useSplitter() {
sum += ret[idx].data.length;
}

console.log(ret);
return ret;
}

Expand Down
16 changes: 10 additions & 6 deletions resources/js/layouts/PhotoLayout.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { computed, ref } from "vue";
import { computed, Ref, ref } from "vue";
import { useSquare } from "./useSquare";
import { useJustify } from "./useJustify";
import { useMasonry } from "./useMasonry";
import { useGrid } from "./useGrid";

export function useLayouts(config: App.Http.Resources.GalleryConfigs.PhotoLayoutConfig, photo_layout: App.Enum.PhotoLayoutType) {
export function useLayouts(
config: App.Http.Resources.GalleryConfigs.PhotoLayoutConfig,
layout: Ref<App.Enum.PhotoLayoutType>,
isTimeline: Ref<boolean>,
elemId: string = "photoListing",
) {
const configRef = ref(config);
const layout = ref(photo_layout);
const elementId = elemId;
const BASE = "my-0 w-5 h-5 mr-0 ml-0 transition-all duration-300 group-hover:scale-150 group-hover:stroke-black dark:group-hover:stroke-white ";
const squareClass = computed(() => BASE + (layout.value === "square" ? "stroke-primary-400" : "stroke-neutral-400"));
const justifiedClass = computed(() => BASE + (layout.value === "justified" ? "fill-primary-400" : "fill-neutral-400"));
const masonryClass = computed(() => BASE + (layout.value === "masonry" ? "stroke-primary-400" : "stroke-neutral-400"));
const gridClass = computed(() => BASE + (layout.value === "grid" ? "stroke-primary-400" : "stroke-neutral-400"));

function activateLayout() {
const photoListing = document.getElementById("photoListing");
const photoListing = document.getElementById(elementId);
if (photoListing === null) {
return; // Nothing to do
}
Expand All @@ -24,7 +29,7 @@ export function useLayouts(config: App.Http.Resources.GalleryConfigs.PhotoLayout
return useSquare(photoListing, configRef.value.photo_layout_square_column_width, configRef.value.photo_layout_gap);
case "justified":
case "unjustified":
return useJustify(photoListing, configRef.value.photo_layout_justified_row_height);
return useJustify(photoListing, configRef.value.photo_layout_justified_row_height, isTimeline.value);
case "masonry":
return useMasonry(photoListing, configRef.value.photo_layout_masonry_column_width, configRef.value.photo_layout_gap);
case "grid":
Expand All @@ -33,7 +38,6 @@ export function useLayouts(config: App.Http.Resources.GalleryConfigs.PhotoLayout
}

return {
layout,
squareClass,
justifiedClass,
masonryClass,
Expand Down
12 changes: 10 additions & 2 deletions resources/js/layouts/useJustify.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { ChildNodeWithDataStyle } from "./types";
import createJustifiedLayout from "justified-layout";

export function useJustify(el: HTMLElement, photoDefaultHeight: number = 320) {
const containerWidth = parseInt(getComputedStyle(el).width);
export function useJustify(el: HTMLElement, photoDefaultHeight: number = 320, isTimeline: boolean) {
const baseElem = document.getElementById("lychee_view_content");
if (!baseElem) {
return;
}
const containerDefaultWidth = parseInt(getComputedStyle(baseElem).width) - 86;

// const width = el.clientWidth;
// const body_width = document.body.scrollWidth;
// console.log("containerDefaultWidth: " + containerDefaultWidth);
// console.log("containerWidthBefore: " + parseInt(getComputedStyle(el).width));

const containerWidth = isTimeline ? Math.min(parseInt(getComputedStyle(el).width), containerDefaultWidth) : parseInt(getComputedStyle(el).width);
// console.log("containerWidth: " + containerWidth);
// console.log("width: " + width)
// console.log("body_width: " + body_width);
Expand Down
1 change: 1 addition & 0 deletions resources/js/views/gallery-panels/Album.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
:selected-photos="selectedPhotosIds"
@clicked="photoClick"
@contexted="photoMenuOpen"
:is-timeline="config.is_photo_timeline_enabled"
/>
<GalleryFooter v-once />
</div>
Expand Down
1 change: 1 addition & 0 deletions resources/js/views/gallery-panels/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
:selected-photos="selectedPhotosIds"
@clicked="photoClick"
@contexted="photoMenuOpen"
:is-timeline="configForMenu.is_photo_timeline_enabled"
/>
</div>

Expand Down

0 comments on commit bf8c541

Please sign in to comment.