Skip to content

Commit a3b9b28

Browse files
committed
feat(list): adds quick action buttons - list
1 parent 516e0e2 commit a3b9b28

13 files changed

+160
-13
lines changed

src/components/common/list/ListButton.vue

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { buttonProps } = defineProps<{
55
buttonProps?: ButtonProps;
66
iconProps?: IconProps;
77
disabled?: boolean;
8+
colored?: boolean;
89
}>();
910
1011
const emit = defineEmits<{
@@ -21,6 +22,7 @@ const onClick = (e: MouseEvent) => {
2122
<template>
2223
<NButton
2324
class="list-button"
25+
:class="{ colored }"
2426
icon-placement="right"
2527
quaternary
2628
v-bind="buttonProps"
@@ -59,6 +61,11 @@ const onClick = (e: MouseEvent) => {
5961
margin-left: 0.25rem;
6062
}
6163
64+
&.colored {
65+
--color: var(--n-text-color);
66+
--progress: 1200%;
67+
}
68+
6269
// stylelint-disable-next-line selector-class-pattern -- framework override
6370
&:not(.n-button--disabled) {
6471
&:focus-visible,

src/components/common/list/ListButtonCheckin.vue

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup lang="ts">
22
import { computed } from 'vue';
33
4+
import type { ButtonProps, IconProps } from 'naive-ui';
5+
46
import ListButton from '~/components/common/list/ListButton.vue';
57
import IconCancel from '~/components/icons/IconCancel.vue';
68
import IconConfirmCircle from '~/components/icons/IconConfirmCircle.vue';
@@ -14,6 +16,8 @@ import { useI18n } from '~/utils/i18n.utils';
1416
const { disabled, item } = defineProps<{
1517
disabled?: boolean;
1618
item?: ListScrollItem;
19+
buttonProps?: ButtonProps;
20+
iconProps?: IconProps;
1721
}>();
1822
1923
const i18n = useI18n('list', 'checkin');
@@ -52,8 +56,9 @@ const { loading } = useWatchingStoreRefs();
5256
<ListButton
5357
:disabled="loading || disabled"
5458
:loading="loading"
55-
:button-props="{ type: watching ? 'warning' : 'error' }"
56-
:icon-props="{ component: watching ? IconCancel : IconConfirmCircle }"
59+
:colored="watching"
60+
:button-props="{ type: 'error', ...buttonProps }"
61+
:icon-props="{ component: watching ? IconCancel : IconConfirmCircle, ...iconProps }"
5762
@on-click="onClick"
5863
>
5964
<span>{{ i18n(watching ? 'cancel' : 'checkin', 'common', 'button') }}</span>

src/components/common/list/ListButtonCollected.vue

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<script setup lang="ts">
22
import { computed, type PropType, toRefs } from 'vue';
33
4+
import type { ButtonProps, IconProps } from 'naive-ui';
5+
46
import ListButton from '~/components/common/list/ListButton.vue';
5-
import IconGrid from '~/components/icons/IconGrid.vue';
7+
import IconGridEmpty from '~/components/icons/IconGridEmpty.vue';
68
import IconRestore from '~/components/icons/IconRestore.vue';
79
import { type ListScrollItem } from '~/models/list-scroll.model';
810
import { isListItemType, ListType } from '~/models/list.model';
@@ -30,6 +32,14 @@ const props = defineProps({
3032
type: Boolean,
3133
required: false,
3234
},
35+
buttonProps: {
36+
type: Object as PropType<ButtonProps>,
37+
required: false,
38+
},
39+
iconProps: {
40+
type: Object as PropType<IconProps>,
41+
required: false,
42+
},
3343
});
3444
3545
const { disabled, item, dateType } = toRefs(props);
@@ -74,8 +84,9 @@ const onClick = () => {
7484
<ListButton
7585
:disabled="isLoading || disabled"
7686
:loading="isLoading"
77-
:button-props="{ type: collected ? 'error' : 'info' }"
78-
:icon-props="{ component: collected ? IconRestore : IconGrid }"
87+
:colored="!!collected"
88+
:button-props="{ type: 'info', ...buttonProps }"
89+
:icon-props="{ component: collected ? IconRestore : IconGridEmpty, ...iconProps }"
7990
:title="
8091
i18n(collected ? 'collected' : 'mark_collected', 'common', 'tooltip') +
8192
(dateCollected ? `: ${ dateCollected }` : '')

src/components/common/list/ListButtonWatched.vue

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup lang="ts">
22
import { computed, type PropType, toRefs } from 'vue';
33
4+
import type { ButtonProps, IconProps } from 'naive-ui';
5+
46
import ListButton from '~/components/common/list/ListButton.vue';
57
import IconPlay from '~/components/icons/IconPlay.vue';
68
import IconRestore from '~/components/icons/IconRestore.vue';
@@ -30,6 +32,14 @@ const props = defineProps({
3032
type: Boolean,
3133
required: false,
3234
},
35+
buttonProps: {
36+
type: Object as PropType<ButtonProps>,
37+
required: false,
38+
},
39+
iconProps: {
40+
type: Object as PropType<IconProps>,
41+
required: false,
42+
},
3343
});
3444
3545
const { disabled, item, dateType } = toRefs(props);
@@ -74,14 +84,15 @@ const onClick = () => {
7484
<ListButton
7585
:disabled="isLoading || disabled"
7686
:loading="isLoading"
77-
:button-props="{ type: played ? 'error' : 'success' }"
78-
:icon-props="{ component: played ? IconRestore : IconPlay }"
87+
:colored="played"
88+
:button-props="{ type: 'success', ...buttonProps }"
89+
:icon-props="{ component: played ? IconRestore : IconPlay, ...iconProps }"
7990
:title="
80-
i18n(played ? 'watched' : 'mark_watched', 'common', 'tooltip') +
91+
i18n(played ? 'watch' : 'mark_watched', 'common', 'tooltip') +
8192
(datePlayed ? `: ${ datePlayed }` : '')
8293
"
8394
@on-click="onClick"
8495
>
85-
<span>{{ i18n(played ? 'remove' : 'watched', 'common', 'button') }}</span>
96+
<span>{{ i18n(played ? 'watched' : 'watch', 'common', 'button') }}</span>
8697
</ListButton>
8798
</template>

src/components/common/list/ListItem.vue

+28-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ const props = defineProps({
9797
required: false,
9898
default: false,
9999
},
100+
showWatching: {
101+
type: Boolean,
102+
required: false,
103+
default: false,
104+
},
100105
showTagLoader: {
101106
type: Boolean,
102107
required: false,
@@ -119,6 +124,19 @@ const itemRef = ref<
119124
Omit<InstanceType<typeof NTimelineItem>, '$el'> & { $el?: HTMLDivElement }
120125
>();
121126
127+
const focusin = ref(false);
128+
const focusTimeout = ref<ReturnType<typeof setTimeout>>();
129+
const toggleFocusin = (focus: boolean) => {
130+
clearTimeout(focusTimeout.value);
131+
if (focus) {
132+
focusin.value = true;
133+
return;
134+
}
135+
focusTimeout.value = setTimeout(() => {
136+
focusin.value = false;
137+
}, 100);
138+
};
139+
122140
const open = ref(false);
123141
const dragged = ref(0);
124142
const onHover = (_event: MouseEvent, _hover: boolean) => {
@@ -288,6 +306,7 @@ const onClick = (e: MouseEvent | KeyboardEvent) =>
288306
:show-progress="showProgress"
289307
:show-played="showPlayed"
290308
:show-collected="showCollected"
309+
:show-watching="showWatching"
291310
:show-tag-loader="showTagLoader"
292311
>
293312
<slot :item="item" :loading="loading" />
@@ -299,8 +318,16 @@ const onClick = (e: MouseEvent | KeyboardEvent) =>
299318
:class="{ open, dragged: dragged > 0 && dragged < 100 }"
300319
:style="{ '--dragged': `${dragged}%` }"
301320
vertical
321+
@focusin="toggleFocusin(true)"
322+
@focusout="toggleFocusin(false)"
302323
>
303-
<slot name="buttons" :open="open" :dragged="dragged" :item="item" />
324+
<slot
325+
name="buttons"
326+
:open="open"
327+
:dragged="dragged"
328+
:focusin="focusin"
329+
:item="item"
330+
/>
304331
</NButtonGroup>
305332
</NFlex>
306333
</NFlex>

src/components/common/list/ListItemPanel.vue

+36-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import AnchorLink from '~/components/common/buttons/AnchorLink.vue';
1818
import TagLink from '~/components/common/buttons/TagLink.vue';
1919
import ProgressTooltip from '~/components/common/tooltip/ProgressTooltip.vue';
2020
import IconGrid from '~/components/icons/IconGrid.vue';
21+
import IconLoading from '~/components/icons/IconLoading.vue';
2122
import IconPlayFilled from '~/components/icons/IconPlayFilled.vue';
2223
import { getCustomLinkIcon } from '~/models/link.model';
2324
import { type ListScrollItem } from '~/models/list-scroll.model';
2425
2526
import { ProgressType } from '~/models/progress-type.model';
2627
import { useItemCollected, useItemPlayed } from '~/stores/composable/use-item-played';
28+
import { useWatchingStore } from '~/stores/data/watching.store';
2729
import { useExtensionSettingsStoreRefs } from '~/stores/settings/extension.store';
2830
import { useLinksStore } from '~/stores/settings/links.store';
2931
import { useI18n } from '~/utils/i18n.utils';
@@ -72,13 +74,19 @@ const props = defineProps({
7274
required: false,
7375
default: false,
7476
},
77+
showWatching: {
78+
type: Boolean,
79+
required: false,
80+
default: false,
81+
},
7582
showTagLoader: {
7683
type: Boolean,
7784
required: false,
7885
},
7986
});
8087
81-
const { item, hideDate, showProgress, showPlayed, showCollected } = toRefs(props);
88+
const { item, hideDate, showProgress, showPlayed, showCollected, showWatching } =
89+
toRefs(props);
8290
8391
const type = computed(() =>
8492
item.value.type ? i18n(item.value.type, 'common', 'media', 'type') : item.value.type,
@@ -118,6 +126,13 @@ const {
118126
} = useItemPlayed(item, { showPlayed, showProgress });
119127
const { collected, date: collectedDate } = useItemCollected(item, showCollected);
120128
129+
const { isWatchingListItem } = useWatchingStore();
130+
const watching = computed(() => {
131+
if (!showWatching.value) return false;
132+
if (!item.value) return false;
133+
return isWatchingListItem(item.value);
134+
});
135+
121136
const { progressType } = useExtensionSettingsStoreRefs();
122137
123138
const percentage = computed(() => {
@@ -274,6 +289,26 @@ const onTagClick = (url?: string) => {
274289
</template>
275290
</NTag>
276291
</template>
292+
<template v-if="showWatching && watching">
293+
<NSkeleton
294+
v-if="loading"
295+
key="watching-loader"
296+
text
297+
style="width: 22px; height: 18px; border-radius: 2px"
298+
/>
299+
<NTag
300+
v-else
301+
key="watching"
302+
class="tag badge"
303+
size="small"
304+
type="error"
305+
:bordered="false"
306+
>
307+
<template #icon>
308+
<NIcon :component="IconLoading" />
309+
</template>
310+
</NTag>
311+
</template>
277312
</NFlex>
278313
<div v-if="showProgress" class="panel-progress">
279314
<NSkeleton

src/components/common/list/ListScroll.vue

+8-2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ const props = defineProps({
107107
required: false,
108108
default: false,
109109
},
110+
showWatching: {
111+
type: Boolean,
112+
required: false,
113+
default: false,
114+
},
110115
showTagLoader: {
111116
type: Boolean,
112117
required: false,
@@ -316,19 +321,20 @@ const listPaddingBottom = computed(() => {
316321
:show-progress="showProgress"
317322
:show-played="showPlayed"
318323
:show-collected="showCollected"
324+
:show-watching="showWatching"
319325
:show-tag-loader="showTagLoader"
320326
@on-hover="onHover"
321327
@on-item-click="(...args) => $emit('onItemClick', ...args)"
322328
@on-scroll-into-view="(...args) => $emit('onScrollIntoView', ...args)"
323329
@on-scroll-out-of-view="(...args) => $emit('onScrollOutOfView', ...args)"
324330
>
325331
<slot :item="item" :loading="item.loading" />
326-
<template v-if="$slots.buttons" #buttons="{ open, dragged }">
332+
<template v-if="$slots.buttons" #buttons="{ open, dragged, focusin }">
327333
<slot
328334
name="buttons"
329335
:item="item"
330336
:loading="item.loading"
331-
:open="open || dragged"
337+
:open="open || dragged || focusin"
332338
/>
333339
</template>
334340
</ListItem>

src/components/icons/IconLoading.vue

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<template>
2+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
3+
<g
4+
fill="none"
5+
stroke="currentColor"
6+
stroke-linecap="round"
7+
stroke-linejoin="round"
8+
stroke-width="1.5"
9+
>
10+
<path stroke-dasharray="16" stroke-dashoffset="16" d="M12 3c4.97 0 9 4.03 9 9">
11+
<animate
12+
fill="freeze"
13+
attributeName="stroke-dashoffset"
14+
dur="0.3s"
15+
values="16;0"
16+
/>
17+
<animateTransform
18+
attributeName="transform"
19+
dur="1.5s"
20+
repeatCount="indefinite"
21+
type="rotate"
22+
values="0 12 12;360 12 12"
23+
/>
24+
</path>
25+
<path
26+
stroke-dasharray="64"
27+
stroke-dashoffset="64"
28+
stroke-opacity="0.3"
29+
d="M12 3c4.97 0 9 4.03 9 9c0 4.97 -4.03 9 -9 9c-4.97 0 -9 -4.03 -9 -9c0 -4.97 4.03 -9 9 -9Z"
30+
>
31+
<animate
32+
fill="freeze"
33+
attributeName="stroke-dashoffset"
34+
dur="1.2s"
35+
values="64;0"
36+
/>
37+
</path>
38+
</g>
39+
</svg>
40+
</template>

src/components/views/calendar/CalendarComponent.vue

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const { onItemClick } = usePanelItem();
8080
:backdrop="imageSettings.backdrop"
8181
:poster-type="imageSettings.type"
8282
show-played
83+
show-watching
8384
show-collected
8485
show-tag-loader
8586
overscroll="none"

src/components/views/history/HistoryComponent.vue

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ useActiveAndDocumentVisible({
8888
:page-size="pageSize"
8989
:backdrop="imageSettings.backdrop"
9090
:poster-type="imageSettings.type"
91+
show-watching
9192
show-collected
9293
show-tag-loader
9394
@on-scroll="scrolled = true"

src/components/views/progress/ProgressComponent.vue

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ useActiveAndDocumentVisible({
112112
:poster-type="imageSettings.type"
113113
hide-date
114114
show-progress
115+
show-watching
115116
show-collected
116117
show-tag-loader
117118
@on-scroll="scrolled = true"

src/components/views/search/SearchComponent.vue

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const { onItemClick } = usePanelItem();
6363
:backdrop="imageSettings.backdrop"
6464
:poster-type="imageSettings.type"
6565
show-played
66+
show-watching
6667
show-collected
6768
@on-scroll="scrolled = true"
6869
@on-scroll-top="scrolled = false"

src/components/views/watchlist/WatchlistComponent.vue

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ useActiveAndDocumentVisible({
103103
:backdrop="imageSettings.backdrop"
104104
:poster-type="imageSettings.type"
105105
show-played
106+
show-watching
106107
show-collected
107108
@on-scroll="scrolled = true"
108109
@on-scroll-top="scrolled = false"

0 commit comments

Comments
 (0)