Skip to content

Commit df82f3f

Browse files
committed
Implement timeline front-end
1 parent ed62395 commit df82f3f

File tree

20 files changed

+1236
-32
lines changed

20 files changed

+1236
-32
lines changed

resources/js/components/gallery/albumModule/PhotoThumbPanel.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<template #content="slotProps">
2323
<div
2424
data-type="timelineBlock"
25-
:data-date="slotProps.item.data[0].timeline.timeDate"
25+
:data-date="(slotProps.item.data[0] as App.Http.Resources.Models.PhotoResource).timeline?.time_date"
2626
class="flex flex-wrap flex-row shrink w-full justify-start gap-1 sm:gap-2 md:gap-4 pb-8"
2727
>
2828
<div class="w-full text-left font-semibold text-muted-color-emphasis text-lg">{{ slotProps.item.header }}</div>

resources/js/components/gallery/photoModule/Dock.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
v-on:click="emits('toggleStar')"
1919
/>
2020
<DockButton
21+
v-if="!isTimeline"
2122
pi="image"
2223
class="lg:hover:text-primary-500 text-white"
2324
v-tooltip.bottom="$t('gallery.photo.actions.set_album_header')"
@@ -47,6 +48,8 @@ import DockButton from "./DockButton.vue";
4748
import { useLycheeStateStore } from "@/stores/LycheeState";
4849
import { useTogglablesStateStore } from "@/stores/ModalsState";
4950
import { storeToRefs } from "pinia";
51+
import { computed } from "vue";
52+
import { useRoute } from "vue-router";
5053
5154
const lycheeStore = useLycheeStateStore();
5255
const togglableStore = useTogglablesStateStore();
@@ -65,4 +68,7 @@ const emits = defineEmits<{
6568
toggleMove: [];
6669
toggleDelete: [];
6770
}>();
71+
72+
const route = useRoute();
73+
const isTimeline = computed(() => (route.name as string).includes("timeline"));
6874
</script>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<template>
2+
<div
3+
:class="{
4+
'absolute flex flex-col text-muted-color text-right top-0 h-full overflow-y-scroll no-scrollbar': true,
5+
'right-6': !isTouch,
6+
'right-2': isTouch,
7+
'bg-gradient-to-l from-(--p-surface-0) pt-14 dark:from-(--p-surface-900) text-shadow-sm group pb-24': true,
8+
}"
9+
@mouseleave="scrollToView"
10+
>
11+
<div v-for="yearChunk in dates" :key="yearChunk.header" class="">
12+
<span
13+
:class="{
14+
'sticky inline-block top-0 font-semibold z-10 shadow-surface-950 drop-shadow-md text-3xl scale-75 text-muted-color-emphasis': true,
15+
'group-hover:scale-100 transition-all duration-150 origin-right': true,
16+
'scale-100': currentYear === parseInt(yearChunk.header, 10),
17+
}"
18+
>
19+
{{ yearChunk.header }}
20+
</span>
21+
<!-- We only apply the hover property for items bellow the current scrolling year,
22+
this avoids the upper part of the element to scroll up and down and some jerky behaviours. -->
23+
<div :class="{ 'date-wrapper group-hover:grid-rows-[1]': currentYear > parseInt(yearChunk.header, 10) }">
24+
<div class="overflow-hidden flex flex-col">
25+
<span
26+
v-for="monthChunk in yearChunk.data"
27+
:key="monthChunk.time_date"
28+
:data-date-pointer="monthChunk.time_date"
29+
:class="{
30+
'cursor-pointer transition-all duration-150 scale-75 ease-in-out origin-right': true,
31+
'hover:text-primary-emphasis hover:font-bold hover:scale-100': !isTouch,
32+
'scale-110 text-primary-emphasis font-bold': currentDate === monthChunk.time_date,
33+
}"
34+
@click="emits('load', monthChunk.time_date)"
35+
>{{ monthChunk.format }}
36+
</span>
37+
</div>
38+
</div>
39+
</div>
40+
</div>
41+
</template>
42+
<script setup lang="ts">
43+
import { useSplitter } from "@/composables/album/splitter";
44+
import { isTouchDevice } from "@/utils/keybindings-utils";
45+
import { useDebounceFn } from "@vueuse/core";
46+
import { ref } from "vue";
47+
import { onMounted } from "vue";
48+
import { watch } from "vue";
49+
import { computed } from "vue";
50+
import { useRoute } from "vue-router";
51+
52+
const route = useRoute();
53+
const props = defineProps<{
54+
dates: App.Http.Resources.Models.Utils.TimelineData[];
55+
}>();
56+
57+
const { spliter } = useSplitter();
58+
59+
const dates = computed(() => {
60+
return spliter(
61+
props.dates,
62+
(d) => d.time_date.split("-")[0],
63+
(d) => d.time_date.split("-")[0],
64+
);
65+
});
66+
67+
const currentDate = computed(() => (route.params.date as string | undefined) ?? "");
68+
const currentYear = computed(() => parseInt(currentDate.value.split("-")[0], 10));
69+
70+
const isTouch = ref(isTouchDevice());
71+
72+
const emits = defineEmits<{
73+
load: [date: string];
74+
}>();
75+
76+
// Scroll magic side
77+
// Select the current date and center it in the view
78+
const scrollToView = useDebounceFn(() => {
79+
if (!currentDate.value) {
80+
return;
81+
}
82+
83+
const el = document.querySelector(`[data-date-pointer="${currentDate.value}"]`);
84+
if (el) {
85+
el.scrollIntoView({ behavior: "smooth", block: "center" });
86+
}
87+
}, 100);
88+
89+
// If the date change, we update!
90+
watch(() => route.params.date, scrollToView, { immediate: true });
91+
92+
// Also do that at when loading the component
93+
onMounted(() =>
94+
// But wait! if we do it too early the dom is not rendered and this does not work.
95+
// Let's wait 500ms for the rendering, then select the element.
96+
setTimeout(scrollToView, 500),
97+
);
98+
</script>
99+
<style>
100+
.date-wrapper {
101+
display: grid;
102+
grid-template-rows: 0fr;
103+
transition: grid-template-rows 0.25s ease-out;
104+
105+
&:is(:where(.group):hover *) {
106+
grid-template-rows: 1fr;
107+
}
108+
}
109+
</style>

resources/js/components/headers/AlbumsHeader.vue

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,6 @@
77
:pt:center:class="'absolute top-0 py-3 left-1/2 -translate-x-1/2 h-14'"
88
>
99
<template #start>
10-
<!-- Not logged in. -->
11-
<!-- <BackLinkButton v-if="props.user.id === null && !isLoginLeft" :config="props.config" />
12-
<Button
13-
v-if="props.user.id === null && isLoginLeft"
14-
icon="pi pi-sign-in"
15-
class="border-none"
16-
severity="secondary"
17-
text
18-
@click="togglableStore.toggleLogin()"
19-
/> -->
20-
<!-- Logged in. -->
2110
<OpenLeftMenu />
2211
</template>
2312

@@ -155,7 +144,7 @@ const lycheeStore = useLycheeStateStore();
155144
const togglableStore = useTogglablesStateStore();
156145
const favourites = useFavouriteStore();
157146
158-
const { dropbox_api_key, is_favourite_enabled, is_se_preview_enabled, is_live_metrics_enabled, is_registration_enabled } = storeToRefs(lycheeStore);
147+
const { dropbox_api_key, is_favourite_enabled, is_timeline_page_enabled, is_se_preview_enabled, is_live_metrics_enabled, is_registration_enabled } = storeToRefs(lycheeStore);
159148
const { is_login_open, is_upload_visible, is_create_album_visible, is_create_tag_album_visible, is_metrics_open } = storeToRefs(togglableStore);
160149
161150
const router = useRouter();

0 commit comments

Comments
 (0)