From f194d70678af9f38e3b73b0158323aaa7a1f307d Mon Sep 17 00:00:00 2001 From: Masaya Kazama Date: Thu, 3 Oct 2024 07:47:05 +0000 Subject: [PATCH 1/6] =?UTF-8?q?liverEvent=E3=81=AEcomposition=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/streams/useLiverEvent.ts | 88 +++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/components/streams/useLiverEvent.ts diff --git a/src/components/streams/useLiverEvent.ts b/src/components/streams/useLiverEvent.ts new file mode 100644 index 0000000..ec2f9d6 --- /dev/null +++ b/src/components/streams/useLiverEvent.ts @@ -0,0 +1,88 @@ +import { computed, type Ref } from "vue"; +import type { LiverEvent } from "@/services/api"; +import { useBookmarkStore } from "@/store/bookmarkStore"; +import { useDateStore } from "@/store/dateStore"; +import { useEventListStore } from "@/store/eventListStore"; + +const oneHour = 60 * 60 * 1000; + +// ホロライブのライブ判定開始用 +const liveStartDuration = 20 * 60 * 1000; +const liveEndDuration = 60 * 60 * 1000; + +export const useLiverEvent = (liverEvent: Ref) => { + const dateStore = useDateStore(); + const eventListStore = useEventListStore(); + const bookmarkStore = useBookmarkStore(); + + // 配信終了判定 + const isFinished = computed(() => { + // 終了時刻が設定されているか(にじさんじのみ) + if (liverEvent.value.endAt) return true; + // 配信中か + if (liverEvent.value.isLive) return false; + + // 配信していない場合 + const now = dateStore.currentTime; + const startTime = liverEvent.value.startAt.getTime(); + const elapsed = now - startTime; + // 現在時刻を過ぎていなければ開始前 + if (elapsed < 0) return false; + // ホロライブの場合 + if (liverEvent.value.affilication === "hololive") { + // 配信開始直後は開始時間が更新されてもliveになっていない場合があるので一定時間判定しない + if (elapsed < liveStartDuration) return false; + + // startTimeの秒数が0以外あれば配信開始済み + if (liverEvent.value.startAt.getSeconds() !== 0) return true; + + // 秒数が0の場合、1時間経過していたら終了と見なす + if (elapsed > liveEndDuration) return true; + } + // それ以外の場合:未終了 + return false; + }); + + const elapsedTime = computed(() => { + const { isLive, endAt, startAt } = liverEvent.value; + + const time = (() => { + // 終了時間があれば終了時間から開始時間を引く + if (endAt) { + return endAt.getTime() - startAt.getTime(); + } + // ライブ中なら現在時刻から開始時間を引く + if (isLive) { + return dateStore.currentTime - startAt.getTime(); + } + return 0; + })(); + + if (time === 0) return null; + + const hour = time / oneHour; + return { + fixed: hour.toFixed(1), + count: Math.min(12, Math.max(1, Math.round(hour))), + }; + }); + + const isNew = computed(() => { + return eventListStore.addedEventIdSet.has(liverEvent.value.id); + }); + + const hasBookmark = computed(() => { + return bookmarkStore.hasBookmark(liverEvent.value.id); + }); + const hasNotify = computed(() => { + return bookmarkStore.hasNotify(liverEvent.value.id); + }); + + return { + isFinished, + elapsedTime, + isNew, + hasBookmark, + hasNotify, + }; +}; From 40954034862dbc713e59b3cce6f06c1b66b83551 Mon Sep 17 00:00:00 2001 From: Masaya Kazama Date: Thu, 3 Oct 2024 07:47:30 +0000 Subject: [PATCH 2/6] =?UTF-8?q?bookmark=E7=94=A8=E3=81=AEstore=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/bookmarkStore.ts | 40 ++++++++++++++++++++++++++++++++++++++ src/store/storageStore.ts | 15 -------------- 2 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 src/store/bookmarkStore.ts diff --git a/src/store/bookmarkStore.ts b/src/store/bookmarkStore.ts new file mode 100644 index 0000000..54b2844 --- /dev/null +++ b/src/store/bookmarkStore.ts @@ -0,0 +1,40 @@ +import { useLocalStorage } from "@vueuse/core"; +import { defineStore } from "pinia"; + +type BookmarkType = "bookmark" | "notify"; + +export const useBookmarkStore = defineStore("bookmarkStore", () => { + const bookmarkEventMap = useLocalStorage("bookmarkEventMap", new Map()); + + // ブックマーク登録済みの場合はmapから削除 + function toggleBookmarkEvent(id: string) { + if (bookmarkEventMap.value.has(id)) { + bookmarkEventMap.value.delete(id); + } else { + bookmarkEventMap.value.set(id, "bookmark"); + } + } + + // 既に通知登録済みの場合は通知登録を解除してブックマークに変更 + function toggleNotifyEvent(id: string) { + if (bookmarkEventMap.value.get(id) === "notify") { + bookmarkEventMap.value.set(id, "bookmark"); + } else { + bookmarkEventMap.value.set(id, "notify"); + } + } + + function hasBookmark(id: string) { + return bookmarkEventMap.value.has(id); + } + function hasNotify(id: string) { + return bookmarkEventMap.value.get(id) === "notify"; + } + return { + bookmarkEventMap, + toggleBookmarkEvent, + toggleNotifyEvent, + hasBookmark, + hasNotify, + }; +}); diff --git a/src/store/storageStore.ts b/src/store/storageStore.ts index 1928949..762a37e 100644 --- a/src/store/storageStore.ts +++ b/src/store/storageStore.ts @@ -15,7 +15,6 @@ export interface Node { export const useStorageStore = defineStore("storageStore", () => { const _talentFilterMap = useLocalStorage("talentFilter", new Map()); const talentFilterEnabled = useLocalStorage("talentFilterEnabled", true); - const bookmarkEventSet = useLocalStorage("bookmarkEventSet", new Set()); const talentFilterMap = computed(() => { if (!talentFilterEnabled.value) return new Map(); @@ -33,25 +32,11 @@ export const useStorageStore = defineStore("storageStore", () => { _talentFilterMap.value.clear(); } - function toggleBookmarkEvent(id: string) { - if (bookmarkEventSet.value.has(id)) { - bookmarkEventSet.value.delete(id); - } else { - bookmarkEventSet.value.add(id); - } - } - function resetBookmarkEventSet() { - bookmarkEventSet.value.clear(); - } - return { talentFilterMap, setTalentFilter, resetTalentFilter, talentFilterEnabled, - bookmarkEventSet, - toggleBookmarkEvent, - resetBookmarkEventSet, }; }); From 618c84c228523bc36ac8b59474fea617372a9fbf Mon Sep 17 00:00:00 2001 From: Masaya Kazama Date: Thu, 3 Oct 2024 07:47:43 +0000 Subject: [PATCH 3/6] =?UTF-8?q?bookmark=E3=81=ABnotify=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bookmarkedEvent/BookmarkedEventButton.vue | 7 +- .../BookmarkedEventPopover.vue | 6 +- src/components/streams/LiverEventCard.vue | 101 ++++-------------- .../detail/LiverEventDetailPopover.vue | 54 +++++++--- 4 files changed, 65 insertions(+), 103 deletions(-) diff --git a/src/components/menu/bookmarkedEvent/BookmarkedEventButton.vue b/src/components/menu/bookmarkedEvent/BookmarkedEventButton.vue index b0e48e5..8dbf8ff 100644 --- a/src/components/menu/bookmarkedEvent/BookmarkedEventButton.vue +++ b/src/components/menu/bookmarkedEvent/BookmarkedEventButton.vue @@ -4,17 +4,16 @@ import BookmarkedEventPopover from "./BookmarkedEventPopover.vue"; import type { LiverEvent } from "@/services/api"; import { usePopover } from "@/composable/usePopover"; +import { useBookmarkStore } from "@/store/bookmarkStore"; import { useEventListStore } from "@/store/eventListStore"; -import { useStorageStore } from "@/store/storageStore"; const eventListStore = useEventListStore(); -const storageStore = useStorageStore(); - +const bookmarkStore = useBookmarkStore(); const popover = usePopover(); const bookmarkEventList = computed(() => { const list: LiverEvent[] = []; - storageStore.bookmarkEventSet.forEach((id) => { + bookmarkStore.bookmarkEventMap.forEach((_value, id) => { const liverEvent = eventListStore.liverEventMap.get(id); if (liverEvent) { list.push(liverEvent); diff --git a/src/components/menu/bookmarkedEvent/BookmarkedEventPopover.vue b/src/components/menu/bookmarkedEvent/BookmarkedEventPopover.vue index bacaef2..a83ddc0 100644 --- a/src/components/menu/bookmarkedEvent/BookmarkedEventPopover.vue +++ b/src/components/menu/bookmarkedEvent/BookmarkedEventPopover.vue @@ -3,9 +3,9 @@ import { computed, onMounted } from "vue"; import type { LiverEvent } from "@/services/api"; import { scrollToLiverEventTop } from "@/lib/scroll"; import { getThumnail } from "@/lib/youtube"; +import { useBookmarkStore } from "@/store/bookmarkStore"; import { useDateStore } from "@/store/dateStore"; import { useFocusStore } from "@/store/focusStore"; -import { useStorageStore } from "@/store/storageStore"; import { compareDate, getDateTime } from "@/utils/date"; import { hhmmDateFormatter, toRelativeTime } from "@/utils/dateFormat"; import { closePopover } from "@/utils/popover"; @@ -16,7 +16,7 @@ const props = defineProps<{ const focusStore = useFocusStore(); const dateStore = useDateStore(); -const storageStore = useStorageStore(); +const bookmarkStore = useBookmarkStore(); const bookmarkCount = computed(() => props.bookmarkEventList.length); @@ -99,7 +99,7 @@ onMounted(() => {
diff --git a/src/components/streams/LiverEventCard.vue b/src/components/streams/LiverEventCard.vue index a9f088d..6642d68 100644 --- a/src/components/streams/LiverEventCard.vue +++ b/src/components/streams/LiverEventCard.vue @@ -1,12 +1,10 @@ @@ -165,4 +202,16 @@ function setSearchString(str: string) { opacity: 0; } } + +[popover] { + &:popover-open { + animation: fadeIn 0.2s ease-in-out forwards; + } +} + +@keyframes fadeIn { + from { + translate: 0 50%; + } +} diff --git a/src/store/notificationStore.ts b/src/store/notificationStore.ts new file mode 100644 index 0000000..679bb95 --- /dev/null +++ b/src/store/notificationStore.ts @@ -0,0 +1,29 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; + +const isSupported = window && "Notification" in window; + +export const useNotificationStore = defineStore("notificationStore", () => { + const permissionGranted = ref( + isSupported && "permission" in Notification && Notification.permission === "granted", + ); + + async function ensurePermissions() { + if (!isSupported) return false; + if (permissionGranted.value) return true; + + // deniedの場合はrequestできないのでfalseを返す + if (Notification.permission === "denied") return false; + + // request: ユーザーがアクションするかタイムアウトまで時間がかかる + const permission = await Notification.requestPermission(); + permissionGranted.value = permission === "granted"; + return permissionGranted.value; + } + + return { + isSupported, + permissionGranted, + ensurePermissions, + }; +});