Skip to content

Commit e8206ed

Browse files
committed
feat(releases): adds navbar support to releases
1 parent ddbe1fa commit e8206ed

20 files changed

+705
-132
lines changed

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@
5353
"dependencies": {
5454
"@dvcol/base-http-client": "^1.10.0",
5555
"@dvcol/common-utils": "^1.3.0",
56-
"@dvcol/tmdb-http-client": "^1.1.4",
56+
"@dvcol/tmdb-http-client": "^1.2.3",
5757
"@dvcol/trakt-http-client": "^1.3.6",
5858
"@dvcol/tvdb-http-client": "^1.1.3",
5959
"@dvcol/web-extension-utils": "^3.0.1",
6060
"@vue/devtools": "^7.0.15",
61+
"iso-3166-2": "^1.0.0",
6162
"naive-ui": "^2.38.1",
6263
"pinia": "^2.1.7",
6364
"vue": "^3.4.14",
@@ -66,7 +67,7 @@
6667
"devDependencies": {
6768
"@commitlint/cli": "^19.0.0",
6869
"@commitlint/config-conventional": "^19.0.0",
69-
"@dvcol/eslint-plugin-presets": "^1.3.10",
70+
"@dvcol/eslint-plugin-presets": "^1.3.11",
7071
"@dvcol/stylelint-plugin-presets": "^2.1.2",
7172
"@tsconfig/node20": "^20.1.2",
7273
"@types/chrome": "^0.0.266",

pnpm-lock.yaml

+20-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<script lang="ts" setup>
2+
import { computed, ref, watch } from 'vue';
3+
4+
import type {
5+
VirtualListRef,
6+
VirtualListScrollToOptions,
7+
} from '~/models/list-scroll.model';
8+
9+
import FloatingButton from '~/components/common/buttons/FloatingButton.vue';
10+
import ListScroll from '~/components/common/list/ListScroll.vue';
11+
import { useListScroll } from '~/components/common/list/use-list-scroll';
12+
import IconChevronDown from '~/components/icons/IconChevronDown.vue';
13+
import IconChevronUp from '~/components/icons/IconChevronUp.vue';
14+
15+
import { usePanelItem } from '~/components/views/panel/use-panel-item';
16+
import { useCalendarStore, useCalendarStoreRefs } from '~/stores/data/calendar.store';
17+
import { useI18n } from '~/utils/i18n.utils';
18+
import { watchUserChange } from '~/utils/store.utils';
19+
20+
const i18n = useI18n('calendar');
21+
22+
const { calendar, loading, center, filteredCalendar } = useCalendarStoreRefs();
23+
const { fetchCalendar, clearState } = useCalendarStore();
24+
25+
const list = useListScroll(filteredCalendar, 'date');
26+
27+
const centerItem = computed(() => {
28+
return list.value.find(
29+
item => item.date?.current.toLocaleDateString() === center.value.toLocaleDateString(),
30+
);
31+
});
32+
33+
const centerIsToday = computed(() => {
34+
return (
35+
centerItem.value?.date?.current.toLocaleDateString() ===
36+
new Date().toLocaleDateString()
37+
);
38+
});
39+
40+
const listRef = ref<{ list: VirtualListRef }>();
41+
42+
const scrollTo = (
43+
options?: VirtualListScrollToOptions,
44+
index = centerItem.value?.index,
45+
) => {
46+
if (index === undefined) return;
47+
if (!listRef.value?.list) return;
48+
49+
listRef.value?.list.scrollTo({
50+
top: index * 145,
51+
...options,
52+
});
53+
};
54+
55+
const reload = async () => {
56+
const promise = fetchCalendar();
57+
// watch for loading changes and recenter
58+
const unsub = watch(list, async () => scrollTo());
59+
await promise;
60+
scrollTo();
61+
unsub();
62+
};
63+
64+
watch(center, () => reload());
65+
66+
watchUserChange({
67+
mounted: reload,
68+
activated: async changed => {
69+
if (changed) await reload();
70+
},
71+
userChange: async active => {
72+
clearState();
73+
if (active) await reload();
74+
},
75+
});
76+
77+
const scrolledOut = ref(false);
78+
const scrolledDown = ref(true);
79+
const onClick = () => scrollTo({ behavior: 'smooth' });
80+
const onScrollIntoOutOfView = (_scrolled: boolean, _itemRef?: HTMLDivElement) => {
81+
scrolledOut.value = _scrolled;
82+
if (!_scrolled || !_itemRef) return;
83+
scrolledDown.value = _itemRef.getBoundingClientRect().top > 0;
84+
};
85+
const recenterIcon = computed(() =>
86+
scrolledDown.value ? IconChevronDown : IconChevronUp,
87+
);
88+
89+
const onScrollTop = async () => {
90+
const first = list.value[0];
91+
await fetchCalendar('start');
92+
93+
listRef.value?.list.scrollTo({
94+
top: (list.value.findIndex(item => item.id === first.id) - 1) * 145,
95+
});
96+
};
97+
98+
const onScrollBottom = async () => {
99+
await fetchCalendar('end');
100+
};
101+
102+
const { onItemClick } = usePanelItem();
103+
</script>
104+
105+
<template>
106+
<div class="container">
107+
<ListScroll
108+
ref="listRef"
109+
:items="list"
110+
:loading="loading"
111+
backdrop
112+
:scroll-into-view="centerItem?.id ? [centerItem?.id] : []"
113+
@on-item-click="onItemClick"
114+
@on-scroll-into-view="e => onScrollIntoOutOfView(false, e.ref)"
115+
@on-scroll-out-of-view="e => onScrollIntoOutOfView(true, e.ref)"
116+
@on-scroll-top="onScrollTop"
117+
@on-scroll-bottom="onScrollBottom"
118+
>
119+
<template #default>
120+
<!-- TODO buttons here-->
121+
</template>
122+
</ListScroll>
123+
<FloatingButton
124+
:show="scrolledOut"
125+
:width="centerIsToday ? '2.5rem' : '3.5rem'"
126+
:icon="recenterIcon"
127+
@on-click="onClick"
128+
>
129+
{{ i18n(centerIsToday ? 'today' : 'recenter', 'common', 'button') }}
130+
</FloatingButton>
131+
</div>
132+
</template>
133+
134+
<style lang="scss" scoped>
135+
.container {
136+
width: 100%;
137+
height: 100%;
138+
}
139+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<script setup lang="ts">
2+
import { TmdbMovieReleaseType } from '@dvcol/tmdb-http-client/models';
3+
import { NDatePicker, NFlex, NIcon, NSelect, type SelectOption } from 'naive-ui';
4+
5+
import { computed, defineProps, onBeforeMount, ref } from 'vue';
6+
7+
import IconCalendar from '~/components/icons/IconCalendar.vue';
8+
import IconChevron from '~/components/icons/IconChevronDownSmall.vue';
9+
import { useReleasesStore, useReleasesStoreRefs } from '~/stores/data/releases.store';
10+
import { useI18n } from '~/utils/i18n.utils';
11+
12+
defineProps({
13+
parentElement: {
14+
type: HTMLElement,
15+
required: false,
16+
},
17+
});
18+
19+
const i18n = useI18n('navbar', 'releases');
20+
21+
const { center, loading, region, regions, regionLoading, types } = useReleasesStoreRefs();
22+
const { clearState, fetchRegions } = useReleasesStore();
23+
24+
const pickerValue = computed({
25+
get: () => center.value.getTime(),
26+
set: (value: number) => {
27+
const newDate = value ? new Date(value) : new Date();
28+
if (newDate.toLocaleDateString() === center.value.toLocaleDateString()) return;
29+
clearState(value ? new Date(value) : undefined);
30+
},
31+
});
32+
33+
const typesOptions = computed<SelectOption[]>(() =>
34+
Object.entries(TmdbMovieReleaseType).map(([label, value], i) => ({
35+
label: i18n(label, 'release_type'),
36+
value,
37+
})),
38+
);
39+
40+
const regionOptions = computed<SelectOption[]>(() =>
41+
regions.value.map(_region => ({
42+
label: _region.native_name || _region.english_name,
43+
value: _region.iso_3166_1,
44+
})),
45+
);
46+
47+
const open = ref(false);
48+
49+
onBeforeMount(() => fetchRegions());
50+
</script>
51+
52+
<template>
53+
<NFlex class="row" align="center" justify="space-evenly" :vertical="false">
54+
<NSelect
55+
v-model:value="types"
56+
class="type-select"
57+
:options="typesOptions"
58+
:to="parentElement"
59+
:max-tag-count="1"
60+
:ellipsis-tag-popover-props="{ disabled: true }"
61+
multiple
62+
/>
63+
<NDatePicker
64+
v-model:show="open"
65+
v-model:value="pickerValue"
66+
class="date-picker"
67+
type="date"
68+
:to="parentElement"
69+
:disabled="loading"
70+
placement="bottom"
71+
update-value-on-close
72+
close-on-select
73+
clearable
74+
>
75+
<template #date-icon>
76+
<NIcon :component="open ? IconChevron : IconCalendar" />
77+
</template>
78+
</NDatePicker>
79+
<NSelect
80+
v-model:value="region"
81+
class="region-select"
82+
:options="regionOptions"
83+
:loading="regionLoading"
84+
:disabled="regionLoading || !regionOptions.length"
85+
:to="parentElement"
86+
filterable
87+
clearable
88+
/>
89+
</NFlex>
90+
</template>
91+
92+
<style lang="scss" scoped>
93+
.row {
94+
width: 100%;
95+
padding: 0 0.5rem;
96+
97+
.type-select {
98+
flex: 0 0 12rem;
99+
}
100+
101+
.region-select {
102+
flex: 0 0 12rem;
103+
}
104+
105+
.date-picker {
106+
flex: 1 1 33%;
107+
}
108+
109+
.search-input {
110+
flex: 1 1 33%;
111+
}
112+
}
113+
</style>
114+
115+
<style lang="scss">
116+
.n-date-panel {
117+
margin-top: 12px;
118+
margin-left: -16px;
119+
}
120+
</style>

src/components/views/settings/SettingsCache.vue

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const evictScopes = [
5757
]),
5858
},
5959
{ label: i18n('evict_calendar'), click: TraktService.evict.calendar },
60+
{ label: i18n('evict_releases'), click: TraktService.evict.releases },
6061
{ label: i18n('evict_history'), click: TraktService.evict.history },
6162
{
6263
label: i18n('evict_lists'),

0 commit comments

Comments
 (0)