19 changed files with 324 additions and 312 deletions
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
.flare-enabled .flare-light { |
||||
opacity: 1 !important; |
||||
} |
||||
|
||||
.hover\:flare-enabled:hover .flare-light { |
||||
opacity: 1 !important; |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
.lightbar { |
||||
position: absolute; |
||||
left: -25vw; |
||||
top: 0; |
||||
width: 150vw; |
||||
height: 800px; |
||||
pointer-events: none; |
||||
user-select: none; |
||||
--top: theme('colors.background.main'); |
||||
--bottom: theme('colors.lightBar.light'); |
||||
--first: conic-gradient(from 90deg at 80% 50%,var(--top),var(--bottom)); |
||||
--second: conic-gradient(from 270deg at 20% 50%,var(--bottom),var(--top)); |
||||
mask-image: radial-gradient(100% 50% at center center, black, transparent); |
||||
background-image: var(--first), var(--second); |
||||
background-position-x: 1%, 99%; |
||||
background-position-y: 0%, 0%; |
||||
background-size: 50% 100%, 50% 100%; |
||||
opacity: 1; |
||||
transform: rotate(180deg) translateZ(0px) translateY(400px); |
||||
transform-origin: center center; |
||||
background-repeat: no-repeat; |
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
import "./Lightbar.css"; |
||||
|
||||
export function Lightbar(props: { className?: string }) { |
||||
return ( |
||||
<div className={props.className}> |
||||
<div className="lightbar" /> |
||||
</div> |
||||
); |
||||
} |
@ -1,107 +0,0 @@
@@ -1,107 +0,0 @@
|
||||
import pako from "pako"; |
||||
import { useEffect, useState } from "react"; |
||||
|
||||
import { MWMediaType } from "@/backend/metadata/types/mw"; |
||||
import { conf } from "@/setup/config"; |
||||
|
||||
function fromBinary(str: string): Uint8Array { |
||||
const result = new Uint8Array(str.length); |
||||
[...str].forEach((char, i) => { |
||||
result[i] = char.charCodeAt(0); |
||||
}); |
||||
return result; |
||||
} |
||||
|
||||
export function importV2Data({ data, time }: { data: any; time: Date }) { |
||||
const savedTime = localStorage.getItem("mw-migration-date"); |
||||
if (savedTime) { |
||||
if (new Date(savedTime) >= time) { |
||||
// has already migrated this or something newer, skip
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// restore migration data
|
||||
if (data.bookmarks) |
||||
localStorage.setItem("mw-bookmarks", JSON.stringify(data.bookmarks)); |
||||
if (data.videoProgress) |
||||
localStorage.setItem("video-progress", JSON.stringify(data.videoProgress)); |
||||
|
||||
localStorage.setItem("mw-migration-date", time.toISOString()); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
export function EmbedMigration() { |
||||
let hasReceivedMigrationData = false; |
||||
|
||||
const onMessage = (e: any) => { |
||||
const data = e.data; |
||||
if (data && data.isMigrationData && !hasReceivedMigrationData) { |
||||
hasReceivedMigrationData = true; |
||||
const didImport = importV2Data({ |
||||
data: data.data, |
||||
time: data.date, |
||||
}); |
||||
if (didImport) window.location.reload(); |
||||
} |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
window.addEventListener("message", onMessage); |
||||
|
||||
return () => { |
||||
window.removeEventListener("message", onMessage); |
||||
}; |
||||
}); |
||||
|
||||
return <iframe src="https://movie.squeezebox.dev" hidden />; |
||||
} |
||||
|
||||
export function V2MigrationView() { |
||||
const [done, setDone] = useState(false); |
||||
useEffect(() => { |
||||
const params = new URLSearchParams(window.location.search ?? ""); |
||||
if (!params.has("m-time") || !params.has("m-data")) { |
||||
// migration params missing, just redirect
|
||||
setDone(true); |
||||
return; |
||||
} |
||||
|
||||
const data = JSON.parse( |
||||
pako.inflate(fromBinary(atob(params.get("m-data") as string)), { |
||||
to: "string", |
||||
}) |
||||
); |
||||
const timeOfMigration = new Date(params.get("m-time") as string); |
||||
|
||||
importV2Data({ |
||||
data, |
||||
time: timeOfMigration, |
||||
}); |
||||
|
||||
// finished
|
||||
setDone(true); |
||||
}, []); |
||||
|
||||
// redirect when done
|
||||
useEffect(() => { |
||||
if (!done) return; |
||||
const newUrl = new URL(window.location.href); |
||||
|
||||
const newParams = [] as string[]; |
||||
newUrl.searchParams.forEach((_, key) => newParams.push(key)); |
||||
newParams.forEach((v) => newUrl.searchParams.delete(v)); |
||||
newUrl.searchParams.append("migrated", "1"); |
||||
|
||||
// hash router compatibility
|
||||
newUrl.hash = conf().NORMAL_ROUTER ? "" : `/search/${MWMediaType.MOVIE}`; |
||||
newUrl.pathname = conf().NORMAL_ROUTER |
||||
? `/search/${MWMediaType.MOVIE}` |
||||
: ""; |
||||
|
||||
window.location.href = newUrl.toString(); |
||||
}, [done]); |
||||
|
||||
return null; |
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react"; |
||||
import { useMemo, useState } from "react"; |
||||
import { useTranslation } from "react-i18next"; |
||||
|
||||
import { EditButton } from "@/components/buttons/EditButton"; |
||||
import { Icons } from "@/components/Icon"; |
||||
import { SectionHeading } from "@/components/layout/SectionHeading"; |
||||
import { MediaGrid } from "@/components/media/MediaGrid"; |
||||
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard"; |
||||
import { useBookmarkContext } from "@/state/bookmark"; |
||||
import { useWatchedContext } from "@/state/watched"; |
||||
|
||||
export function BookmarksPart() { |
||||
const { t } = useTranslation(); |
||||
const { getFilteredBookmarks, setItemBookmark } = useBookmarkContext(); |
||||
const bookmarks = getFilteredBookmarks(); |
||||
const [editing, setEditing] = useState(false); |
||||
const [gridRef] = useAutoAnimate<HTMLDivElement>(); |
||||
const { watched } = useWatchedContext(); |
||||
|
||||
const bookmarksSorted = useMemo(() => { |
||||
return bookmarks |
||||
.map((v) => { |
||||
return { |
||||
...v, |
||||
watched: watched.items |
||||
.sort((a, b) => b.watchedAt - a.watchedAt) |
||||
.find((watchedItem) => watchedItem.item.meta.id === v.id), |
||||
}; |
||||
}) |
||||
.sort( |
||||
(a, b) => (b.watched?.watchedAt || 0) - (a.watched?.watchedAt || 0) |
||||
); |
||||
}, [watched.items, bookmarks]); |
||||
|
||||
if (bookmarks.length === 0) return null; |
||||
|
||||
return ( |
||||
<div> |
||||
<SectionHeading |
||||
title={t("search.bookmarks") || "Bookmarks"} |
||||
icon={Icons.BOOKMARK} |
||||
> |
||||
<EditButton editing={editing} onEdit={setEditing} /> |
||||
</SectionHeading> |
||||
<MediaGrid ref={gridRef}> |
||||
{bookmarksSorted.map((v) => ( |
||||
<WatchedMediaCard |
||||
key={v.id} |
||||
media={v} |
||||
closable={editing} |
||||
onClose={() => setItemBookmark(v, false)} |
||||
/> |
||||
))} |
||||
</MediaGrid> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react"; |
||||
import { useState } from "react"; |
||||
import { useTranslation } from "react-i18next"; |
||||
|
||||
import { |
||||
getIfBookmarkedFromPortable, |
||||
useBookmarkContext, |
||||
} from "@/state/bookmark"; |
||||
import { useWatchedContext } from "@/state/watched"; |
||||
|
||||
function Watched() { |
||||
const { t } = useTranslation(); |
||||
const { getFilteredBookmarks } = useBookmarkContext(); |
||||
const { getFilteredWatched, removeProgress } = useWatchedContext(); |
||||
const [editing, setEditing] = useState(false); |
||||
const [gridRef] = useAutoAnimate<HTMLDivElement>(); |
||||
|
||||
const bookmarks = getFilteredBookmarks(); |
||||
const watchedItems = getFilteredWatched().filter( |
||||
(v) => !getIfBookmarkedFromPortable(bookmarks, v.item.meta) |
||||
); |
||||
|
||||
if (watchedItems.length === 0) return null; |
||||
|
||||
return ( |
||||
<div> |
||||
<SectionHeading |
||||
title={t("search.continueWatching") || "Continue Watching"} |
||||
icon={Icons.CLOCK} |
||||
> |
||||
<EditButton editing={editing} onEdit={setEditing} /> |
||||
</SectionHeading> |
||||
<MediaGrid ref={gridRef}> |
||||
{watchedItems.map((v) => ( |
||||
<WatchedMediaCard |
||||
key={v.item.meta.id} |
||||
media={v.item.meta} |
||||
closable={editing} |
||||
onClose={() => removeProgress(v.item.meta.id)} |
||||
/> |
||||
))} |
||||
</MediaGrid> |
||||
</div> |
||||
); |
||||
} |
Loading…
Reference in new issue