From 3bceb2a9051876eaeb1d691ea05a54ed306addd9 Mon Sep 17 00:00:00 2001 From: Kacper Kwapisz Date: Thu, 27 Jul 2023 13:39:09 +0200 Subject: [PATCH 01/22] Update flixhq.ts flixHqBase changed. Old: `https://consumet-api-clone.vercel.app` New: `https://consumet-api-clone-six.vercel.app` --- src/backend/providers/flixhq.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/providers/flixhq.ts b/src/backend/providers/flixhq.ts index fd905019..78da423b 100644 --- a/src/backend/providers/flixhq.ts +++ b/src/backend/providers/flixhq.ts @@ -9,7 +9,7 @@ import { registerProvider } from "../helpers/register"; import { MWCaption, MWStreamQuality, MWStreamType } from "../helpers/streams"; import { MWMediaType } from "../metadata/types/mw"; -const flixHqBase = "https://consumet-api-clone.vercel.app/meta/tmdb"; // instance stolen from streaminal :) +const flixHqBase = "https://consumet-api-clone-six.vercel.app/meta/tmdb"; // instance stolen from streaminal :) type FlixHQMediaType = "Movie" | "TV Series"; interface FLIXMediaBase { From 765cf2a17a9048aa653fbbbed49176740adb5a37 Mon Sep 17 00:00:00 2001 From: St Peter and St Pauls Catholic Voluntary Academy <80128659+ssppacademy@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:51:21 +0100 Subject: [PATCH 02/22] chore: bump version for FlixHQ patch --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 32d7f081..3b508b2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "movie-web", - "version": "3.1.3", + "version": "3.1.4", "private": true, "homepage": "https://movie-web.app", "dependencies": { From 6e8e3234177bc913bbf9559411ed3517df2a6b06 Mon Sep 17 00:00:00 2001 From: William Oldham Date: Thu, 27 Jul 2023 23:05:26 +0100 Subject: [PATCH 03/22] fix(flixhq): change consumet api to official URL --- src/backend/providers/flixhq.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/providers/flixhq.ts b/src/backend/providers/flixhq.ts index 78da423b..da8f2932 100644 --- a/src/backend/providers/flixhq.ts +++ b/src/backend/providers/flixhq.ts @@ -9,7 +9,7 @@ import { registerProvider } from "../helpers/register"; import { MWCaption, MWStreamQuality, MWStreamType } from "../helpers/streams"; import { MWMediaType } from "../metadata/types/mw"; -const flixHqBase = "https://consumet-api-clone-six.vercel.app/meta/tmdb"; // instance stolen from streaminal :) +const flixHqBase = "https://api.consumet.org/meta/tmdb"; // instance stolen from streaminal :) type FlixHQMediaType = "Movie" | "TV Series"; interface FLIXMediaBase { From 3bd2bb4b2cf0d57618790a60e2799d8db8c07046 Mon Sep 17 00:00:00 2001 From: William Oldham Date: Thu, 27 Jul 2023 23:09:03 +0100 Subject: [PATCH 04/22] chore(flixhq): remove wrong comment --- src/backend/providers/flixhq.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/providers/flixhq.ts b/src/backend/providers/flixhq.ts index da8f2932..24d90a87 100644 --- a/src/backend/providers/flixhq.ts +++ b/src/backend/providers/flixhq.ts @@ -9,7 +9,7 @@ import { registerProvider } from "../helpers/register"; import { MWCaption, MWStreamQuality, MWStreamType } from "../helpers/streams"; import { MWMediaType } from "../metadata/types/mw"; -const flixHqBase = "https://api.consumet.org/meta/tmdb"; // instance stolen from streaminal :) +const flixHqBase = "https://api.consumet.org/meta/tmdb"; type FlixHQMediaType = "Movie" | "TV Series"; interface FLIXMediaBase { From 606e55d5522850e310fce4f557770c9dcad5371b Mon Sep 17 00:00:00 2001 From: William Oldham Date: Mon, 14 Aug 2023 23:28:30 +0100 Subject: [PATCH 05/22] Create CODEOWNERS --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..d0f0ca6f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +* @movie-web/core + +.github @binaryoverload From 072b2d134b0cbb8f0e6e510c7f84cd68799c9a94 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 15 Aug 2023 20:10:51 +0200 Subject: [PATCH 06/22] Disabled broken providers --- src/backend/embeds/upcloud.ts | 1 + src/backend/providers/hdwatched.ts | 1 + src/backend/providers/sflix.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/backend/embeds/upcloud.ts b/src/backend/embeds/upcloud.ts index b2877bb3..bc435530 100644 --- a/src/backend/embeds/upcloud.ts +++ b/src/backend/embeds/upcloud.ts @@ -34,6 +34,7 @@ registerEmbedScraper({ displayName: "UpCloud", for: MWEmbedType.UPCLOUD, rank: 200, + disabled: true, // encryption broke async getStream({ url }) { // Example url: https://dokicloud.one/embed-4/{id}?z= const parsedUrl = new URL(url.replace("embed-5", "embed-4")); diff --git a/src/backend/providers/hdwatched.ts b/src/backend/providers/hdwatched.ts index 458c3424..533f711d 100644 --- a/src/backend/providers/hdwatched.ts +++ b/src/backend/providers/hdwatched.ts @@ -120,6 +120,7 @@ registerProvider({ id: "hdwatched", displayName: "HDwatched", rank: 150, + disabled: true, // very slow, haven't seen it work for a while type: [MWMediaType.MOVIE, MWMediaType.SERIES], async scrape(options) { const { media, progress } = options; diff --git a/src/backend/providers/sflix.ts b/src/backend/providers/sflix.ts index 2cb1c598..db331e3c 100644 --- a/src/backend/providers/sflix.ts +++ b/src/backend/providers/sflix.ts @@ -9,6 +9,7 @@ registerProvider({ id: "sflix", displayName: "Sflix", rank: 50, + disabled: true, // domain dead type: [MWMediaType.MOVIE, MWMediaType.SERIES], async scrape({ media, episode, progress }) { let searchQuery = `${media.meta.title} `; From 1524a3af39e741adaa417f1ddc07b2a3b479644e Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 15 Aug 2023 20:13:35 +0200 Subject: [PATCH 07/22] faster superstream url --- src/backend/providers/superstream/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/providers/superstream/index.ts b/src/backend/providers/superstream/index.ts index 5af85cb9..548c1ec1 100644 --- a/src/backend/providers/superstream/index.ts +++ b/src/backend/providers/superstream/index.ts @@ -248,13 +248,17 @@ registerProvider({ const mappedCaptions = subtitleRes.list .map(convertSubtitles) .filter(Boolean); + + const fasterUrl = new URL(hdQuality.path); + fasterUrl.host = "mp4.shegu.net"; // this domain is faster + return { embeds: [], stream: { quality: qualityMap[ hdQuality.quality as QualityInMap ] as MWStreamQuality, - streamUrl: hdQuality.path, + streamUrl: fasterUrl.toString(), type: MWStreamType.MP4, captions: mappedCaptions, }, From 31fcd22822339a9b851757ecf18decaa6d766e38 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 15 Aug 2023 20:19:25 +0200 Subject: [PATCH 08/22] Make superstream a fast boi --- src/backend/providers/superstream/index.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/backend/providers/superstream/index.ts b/src/backend/providers/superstream/index.ts index 548c1ec1..883d1ad5 100644 --- a/src/backend/providers/superstream/index.ts +++ b/src/backend/providers/superstream/index.ts @@ -18,6 +18,12 @@ import { compareTitle } from "@/utils/titleMatch"; const nanoid = customAlphabet("0123456789abcdef", 32); +function makeFasterUrl(url: string) { + const fasterUrl = new URL(url); + fasterUrl.host = "mp4.shegu.net"; // this domain is faster + return fasterUrl.toString(); +} + const qualityMap = { "360p": MWStreamQuality.Q360P, "480p": MWStreamQuality.Q480P, @@ -199,7 +205,7 @@ registerProvider({ return { embeds: [], stream: { - streamUrl: hdQuality.path, + streamUrl: makeFasterUrl(hdQuality.path), quality: qualityMap[hdQuality.quality as QualityInMap], type: MWStreamType.MP4, captions: mappedCaptions, @@ -249,16 +255,13 @@ registerProvider({ .map(convertSubtitles) .filter(Boolean); - const fasterUrl = new URL(hdQuality.path); - fasterUrl.host = "mp4.shegu.net"; // this domain is faster - return { embeds: [], stream: { quality: qualityMap[ hdQuality.quality as QualityInMap ] as MWStreamQuality, - streamUrl: fasterUrl.toString(), + streamUrl: makeFasterUrl(hdQuality.path), type: MWStreamType.MP4, captions: mappedCaptions, }, From 2e3684eaad886df6f4a8892f8a8a8dda80c7cf54 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 15 Aug 2023 22:46:48 +0200 Subject: [PATCH 09/22] rip out consumet and fix upcloud --- src/backend/embeds/upcloud.ts | 39 ++++---- src/backend/providers/flixhq.ts | 128 ------------------------- src/backend/providers/flixhq/common.ts | 1 + src/backend/providers/flixhq/index.ts | 34 +++++++ src/backend/providers/flixhq/scrape.ts | 41 ++++++++ src/backend/providers/flixhq/search.ts | 43 +++++++++ tailwind.config.js | 2 +- 7 files changed, 143 insertions(+), 145 deletions(-) delete mode 100644 src/backend/providers/flixhq.ts create mode 100644 src/backend/providers/flixhq/common.ts create mode 100644 src/backend/providers/flixhq/index.ts create mode 100644 src/backend/providers/flixhq/scrape.ts create mode 100644 src/backend/providers/flixhq/search.ts diff --git a/src/backend/embeds/upcloud.ts b/src/backend/embeds/upcloud.ts index bc435530..4bac2b94 100644 --- a/src/backend/embeds/upcloud.ts +++ b/src/backend/embeds/upcloud.ts @@ -34,7 +34,6 @@ registerEmbedScraper({ displayName: "UpCloud", for: MWEmbedType.UPCLOUD, rank: 200, - disabled: true, // encryption broke async getStream({ url }) { // Example url: https://dokicloud.one/embed-4/{id}?z= const parsedUrl = new URL(url.replace("embed-5", "embed-4")); @@ -52,27 +51,35 @@ registerEmbedScraper({ } ); - let sources: - | { - file: string; - type: string; + let sources: { file: string; type: string } | null = null; + + if (!isJSON(streamRes.sources)) { + const decryptionKey = JSON.parse( + await proxiedFetch( + `https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt` + ) + ) as [number, number][]; + + let extractedKey = ""; + const sourcesArray = streamRes.sources.split(""); + for (const index of decryptionKey) { + for (let i: number = index[0]; i < index[1]; i += 1) { + extractedKey += streamRes.sources[i]; + sourcesArray[i] = ""; } - | string = streamRes.sources; - - if (!isJSON(sources) || typeof sources === "string") { - const decryptionKey = await proxiedFetch( - `https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt` - ); - - const decryptedStream = AES.decrypt(sources, decryptionKey).toString( - enc.Utf8 - ); + } + const decryptedStream = AES.decrypt( + sourcesArray.join(""), + extractedKey + ).toString(enc.Utf8); const parsedStream = JSON.parse(decryptedStream)[0]; if (!parsedStream) throw new Error("No stream found"); - sources = parsedStream as { file: string; type: string }; + sources = parsedStream; } + if (!sources) throw new Error("upcloud source not found"); + return { embedId: MWEmbedType.UPCLOUD, streamUrl: sources.file, diff --git a/src/backend/providers/flixhq.ts b/src/backend/providers/flixhq.ts deleted file mode 100644 index 24d90a87..00000000 --- a/src/backend/providers/flixhq.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { compareTitle } from "@/utils/titleMatch"; - -import { - getMWCaptionTypeFromUrl, - isSupportedSubtitle, -} from "../helpers/captions"; -import { mwFetch } from "../helpers/fetch"; -import { registerProvider } from "../helpers/register"; -import { MWCaption, MWStreamQuality, MWStreamType } from "../helpers/streams"; -import { MWMediaType } from "../metadata/types/mw"; - -const flixHqBase = "https://api.consumet.org/meta/tmdb"; - -type FlixHQMediaType = "Movie" | "TV Series"; -interface FLIXMediaBase { - id: number; - title: string; - url: string; - image: string; - type: FlixHQMediaType; - releaseDate: string; -} -interface FLIXSubType { - url: string; - lang: string; -} -function convertSubtitles({ url, lang }: FLIXSubType): MWCaption | null { - if (lang.includes("(maybe)")) return null; - const supported = isSupportedSubtitle(url); - if (!supported) return null; - const type = getMWCaptionTypeFromUrl(url); - return { - url, - langIso: lang, - type, - }; -} - -const qualityMap: Record = { - "360": MWStreamQuality.Q360P, - "540": MWStreamQuality.Q540P, - "480": MWStreamQuality.Q480P, - "720": MWStreamQuality.Q720P, - "1080": MWStreamQuality.Q1080P, -}; - -function flixTypeToMWType(type: FlixHQMediaType) { - if (type === "Movie") return MWMediaType.MOVIE; - return MWMediaType.SERIES; -} - -registerProvider({ - id: "flixhq", - displayName: "FlixHQ", - rank: 100, - type: [MWMediaType.MOVIE, MWMediaType.SERIES], - async scrape({ media, episode, progress }) { - if (!this.type.includes(media.meta.type)) { - throw new Error("Unsupported type"); - } - // search for relevant item - const searchResults = await mwFetch( - `/${encodeURIComponent(media.meta.title)}`, - { - baseURL: flixHqBase, - } - ); - - const foundItem = searchResults.results.find((v: FLIXMediaBase) => { - if (v.type !== "Movie" && v.type !== "TV Series") return false; - return ( - compareTitle(v.title, media.meta.title) && - flixTypeToMWType(v.type) === media.meta.type && - v.releaseDate === media.meta.year - ); - }); - - if (!foundItem) throw new Error("No watchable item found"); - - // get media info - progress(25); - const mediaInfo = await mwFetch(`/info/${foundItem.id}`, { - baseURL: flixHqBase, - params: { - type: flixTypeToMWType(foundItem.type), - }, - }); - if (!mediaInfo.id) throw new Error("No watchable item found"); - // get stream info from media - progress(50); - - let episodeId: string | undefined; - if (media.meta.type === MWMediaType.MOVIE) { - episodeId = mediaInfo.episodeId; - } else if (media.meta.type === MWMediaType.SERIES) { - const seasonNo = media.meta.seasonData.number; - const episodeNo = media.meta.seasonData.episodes.find( - (e) => e.id === episode - )?.number; - - const season = mediaInfo.seasons.find((o: any) => o.season === seasonNo); - episodeId = season.episodes.find((o: any) => o.episode === episodeNo).id; - } - if (!episodeId) throw new Error("No watchable item found"); - progress(75); - const watchInfo = await mwFetch(`/watch/${episodeId}`, { - baseURL: flixHqBase, - params: { - id: mediaInfo.id, - }, - }); - - if (!watchInfo.sources) throw new Error("No watchable item found"); - - // get best quality source - // comes sorted by quality in descending order - const source = watchInfo.sources[0]; - return { - embeds: [], - stream: { - streamUrl: source.url, - quality: qualityMap[source.quality], - type: source.isM3U8 ? MWStreamType.HLS : MWStreamType.MP4, - captions: watchInfo.subtitles.map(convertSubtitles).filter(Boolean), - }, - }; - }, -}); diff --git a/src/backend/providers/flixhq/common.ts b/src/backend/providers/flixhq/common.ts new file mode 100644 index 00000000..a4e6b639 --- /dev/null +++ b/src/backend/providers/flixhq/common.ts @@ -0,0 +1 @@ +export const flixHqBase = "https://flixhq.to"; diff --git a/src/backend/providers/flixhq/index.ts b/src/backend/providers/flixhq/index.ts new file mode 100644 index 00000000..5f0e9003 --- /dev/null +++ b/src/backend/providers/flixhq/index.ts @@ -0,0 +1,34 @@ +import { MWEmbedType } from "@/backend/helpers/embed"; +import { registerProvider } from "@/backend/helpers/register"; +import { MWMediaType } from "@/backend/metadata/types/mw"; +import { + getFlixhqSourceDetails, + getFlixhqSources, +} from "@/backend/providers/flixhq/scrape"; +import { getFlixhqId } from "@/backend/providers/flixhq/search"; + +registerProvider({ + id: "flixhq", + displayName: "FlixHQ", + rank: 100, + type: [MWMediaType.MOVIE, MWMediaType.SERIES], + async scrape({ media }) { + const id = await getFlixhqId(media.meta); + if (!id) throw new Error("flixhq no matching item found"); + + const sources = await getFlixhqSources(id); + const upcloudStream = sources.find( + (v) => v.embed.toLowerCase() === "upcloud" + ); + if (!upcloudStream) throw new Error("upcloud stream not found for flixhq"); + + return { + embeds: [ + { + type: MWEmbedType.UPCLOUD, + url: await getFlixhqSourceDetails(upcloudStream.episodeId), + }, + ], + }; + }, +}); diff --git a/src/backend/providers/flixhq/scrape.ts b/src/backend/providers/flixhq/scrape.ts new file mode 100644 index 00000000..3ca32732 --- /dev/null +++ b/src/backend/providers/flixhq/scrape.ts @@ -0,0 +1,41 @@ +import { proxiedFetch } from "@/backend/helpers/fetch"; +import { flixHqBase } from "@/backend/providers/flixhq/common"; + +export async function getFlixhqSources(id: string) { + const type = id.split("/")[0]; + const episodeParts = id.split("-"); + const episodeId = episodeParts[episodeParts.length - 1]; + + const data = await proxiedFetch( + `/ajax/${type}/episodes/${episodeId}`, + { + baseURL: flixHqBase, + } + ); + const doc = new DOMParser().parseFromString(data, "text/html"); + + const sourceLinks = [...doc.querySelectorAll(".nav-item > a")].map((el) => { + const embedTitle = el.getAttribute("title"); + const linkId = el.getAttribute("data-linkid"); + if (!embedTitle || !linkId) throw new Error("invalid sources"); + return { + embed: embedTitle, + episodeId: linkId, + }; + }); + + return sourceLinks; +} + +export async function getFlixhqSourceDetails( + sourceId: string +): Promise { + const jsonData = await proxiedFetch>( + `/ajax/sources/${sourceId}`, + { + baseURL: flixHqBase, + } + ); + + return jsonData.link; +} diff --git a/src/backend/providers/flixhq/search.ts b/src/backend/providers/flixhq/search.ts new file mode 100644 index 00000000..64db2407 --- /dev/null +++ b/src/backend/providers/flixhq/search.ts @@ -0,0 +1,43 @@ +import { proxiedFetch } from "@/backend/helpers/fetch"; +import { MWMediaMeta } from "@/backend/metadata/types/mw"; +import { flixHqBase } from "@/backend/providers/flixhq/common"; +import { compareTitle } from "@/utils/titleMatch"; + +export async function getFlixhqId(meta: MWMediaMeta): Promise { + const searchResults = await proxiedFetch( + `/search/${meta.title.replaceAll(/[^a-z0-9A-Z]/g, "-")}`, + { + baseURL: flixHqBase, + } + ); + + const doc = new DOMParser().parseFromString(searchResults, "text/html"); + const items = [...doc.querySelectorAll(".film_list-wrap > div.flw-item")].map( + (el) => { + const id = el + .querySelector("div.film-poster > a") + ?.getAttribute("href") + ?.slice(1); + const title = el + .querySelector("div.film-detail > h2 > a") + ?.getAttribute("title"); + const year = el.querySelector( + "div.film-detail > div.fd-infor > span:nth-child(1)" + )?.textContent; + + if (!id || !title || !year) return null; + return { + id, + title, + year, + }; + } + ); + + const matchingItem = items.find( + (v) => v && compareTitle(meta.title, v.title) && meta.year === v.year + ); + + if (!matchingItem) return null; + return matchingItem.id; +} diff --git a/tailwind.config.js b/tailwind.config.js index e22b3b3a..3a6816a6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -42,5 +42,5 @@ module.exports = { animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" } } }, - plugins: [require("tailwind-scrollbar"), require("@tailwindcss/line-clamp")] + plugins: [require("tailwind-scrollbar")] }; From e2022297669675a1bb2dbafde39cfd73674da36a Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 15 Aug 2023 23:04:01 +0200 Subject: [PATCH 10/22] add todo --- src/backend/providers/flixhq/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/providers/flixhq/index.ts b/src/backend/providers/flixhq/index.ts index 5f0e9003..a30e6772 100644 --- a/src/backend/providers/flixhq/index.ts +++ b/src/backend/providers/flixhq/index.ts @@ -16,6 +16,8 @@ registerProvider({ const id = await getFlixhqId(media.meta); if (!id) throw new Error("flixhq no matching item found"); + // TODO tv shows not supported. just need to scrape the specific episode sources + const sources = await getFlixhqSources(id); const upcloudStream = sources.find( (v) => v.embed.toLowerCase() === "upcloud" From c1dceab8eba6dc2795ee9da71b9591cbe36a9877 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 15 Aug 2023 23:17:57 +0200 Subject: [PATCH 11/22] Fix class sorting --- src/components/media/MediaCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/media/MediaCard.tsx b/src/components/media/MediaCard.tsx index a153d8b4..707c4fe4 100644 --- a/src/components/media/MediaCard.tsx +++ b/src/components/media/MediaCard.tsx @@ -117,7 +117,7 @@ function MediaCardContent({ /> -

+

{media.title}

From efb9a7a076af740ca2a3144dac0c481e8a985b86 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 15 Aug 2023 23:30:41 +0200 Subject: [PATCH 12/22] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b508b2c..05a36350 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "movie-web", - "version": "3.1.4", + "version": "3.2.0", "private": true, "homepage": "https://movie-web.app", "dependencies": { From eb57f1958fc2c4990ce73d59193dce466c53fea7 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 20 Aug 2023 16:19:38 +0200 Subject: [PATCH 13/22] added flares, themes and footer Co-authored-by: Jip Frijlink --- package.json | 2 + src/components/layout/Footer.tsx | 78 +++++++++++++++++++++++++ src/components/layout/WideContainer.tsx | 7 ++- src/components/media/MediaGrid.tsx | 5 +- src/components/utils/Flare.tsx | 75 ++++++++++++++++++++++++ src/setup/locales/en/translation.json | 11 ++++ src/views/search/HomeView.tsx | 2 +- src/views/search/SearchView.tsx | 5 +- tailwind.config.js | 29 ++++++++- yarn.lock | 52 ++++++++++++++++- 10 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 src/components/layout/Footer.tsx create mode 100644 src/components/utils/Flare.tsx diff --git a/package.json b/package.json index 71fef462..80d2483c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@sentry/integrations": "^7.49.0", "@sentry/react": "^7.49.0", "@use-gesture/react": "^10.2.24", + "classnames": "^2.3.2", "core-js": "^3.29.1", "crypto-js": "^4.1.1", "dompurify": "^3.0.1", @@ -95,6 +96,7 @@ "prettier-plugin-tailwindcss": "^0.1.7", "tailwind-scrollbar": "^2.0.1", "tailwindcss": "^3.2.4", + "tailwindcss-themer": "^3.1.0", "typescript": "^4.6.4", "vite": "^4.0.1", "vite-plugin-checker": "^0.5.6", diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx new file mode 100644 index 00000000..24f975fb --- /dev/null +++ b/src/components/layout/Footer.tsx @@ -0,0 +1,78 @@ +import { useTranslation } from "react-i18next"; + +import { Icon, Icons } from "@/components/Icon"; +import { BrandPill } from "@/components/layout/BrandPill"; +import { WideContainer } from "@/components/layout/WideContainer"; + +function FooterLink(props: { + href: string; + children: React.ReactNode; + icon: Icons; +}) { + return ( + + + {props.children} + + ); +} + +export function Footer() { + const { t } = useTranslation(); + + return ( +
+ +
+
+ +
+

{t("footer.tagline")}

+
+ + {t("footer.links.github")} + + + {t("footer.links.github")} + +
+
+
+

+ {t("footer.legal.disclaimer")} +

+

{t("footer.legal.disclaimerText")}

+
+ + {t("footer.links.dmca")} + +
+
+
+
+ ); +} + +export function FooterView(props: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+
{props.children}
+
+
+ ); +} diff --git a/src/components/layout/WideContainer.tsx b/src/components/layout/WideContainer.tsx index f7d745fe..bcccd5e5 100644 --- a/src/components/layout/WideContainer.tsx +++ b/src/components/layout/WideContainer.tsx @@ -3,14 +3,15 @@ import { ReactNode } from "react"; interface WideContainerProps { classNames?: string; children?: ReactNode; + ultraWide?: boolean; } export function WideContainer(props: WideContainerProps) { return (
{props.children}
diff --git a/src/components/media/MediaGrid.tsx b/src/components/media/MediaGrid.tsx index a9f75b22..17bd4c7a 100644 --- a/src/components/media/MediaGrid.tsx +++ b/src/components/media/MediaGrid.tsx @@ -7,7 +7,10 @@ interface MediaGridProps { export const MediaGrid = forwardRef( (props, ref) => { return ( -
+
{props.children}
); diff --git a/src/components/utils/Flare.tsx b/src/components/utils/Flare.tsx new file mode 100644 index 00000000..680c3812 --- /dev/null +++ b/src/components/utils/Flare.tsx @@ -0,0 +1,75 @@ +import c from "classnames"; +import { useEffect, useRef } from "react"; + +export interface FlareProps { + className?: string; + backgroundClass: string; + flareSize?: number; + cssColorVar?: string; + enabled?: boolean; +} + +const SIZE_DEFAULT = 200; +const CSS_VAR_DEFAULT = "--colors-global-accentA"; + +export function Flare(props: FlareProps) { + const outerRef = useRef(null); + const size = props.flareSize ?? SIZE_DEFAULT; + const cssVar = props.cssColorVar ?? CSS_VAR_DEFAULT; + + useEffect(() => { + function mouseMove(e: MouseEvent) { + if (!outerRef.current) return; + outerRef.current.style.setProperty( + "--bg-x", + `${(e.clientX - size / 2).toFixed(0)}px` + ); + outerRef.current.style.setProperty( + "--bg-y", + `${(e.clientY - size / 2).toFixed(0)}px` + ); + } + document.addEventListener("mousemove", mouseMove); + + return () => document.removeEventListener("mousemove", mouseMove); + }, [size]); + + return ( +
+
+
+
+
+ ); +} diff --git a/src/setup/locales/en/translation.json b/src/setup/locales/en/translation.json index 8f48a9d6..f78adc46 100644 --- a/src/setup/locales/en/translation.json +++ b/src/setup/locales/en/translation.json @@ -131,5 +131,16 @@ }, "errors": { "offline": "Check your internet connection" + }, + "footer": { + "tagline": "Watch your favorite shows and movies with this open source streaming app.", + "links": { + "github": "GitHub", + "dmca": "DMCA" + }, + "legal": { + "disclaimer": "Disclaimer", + "disclaimerText": "movie-web does not host any files, it merely links to 3rd party services. Legal issues should be taken up with the file hosts and providers. movie-web is not responsible for any media files shown by the video providers." + } } } diff --git a/src/views/search/HomeView.tsx b/src/views/search/HomeView.tsx index a7a9a396..42868cc6 100644 --- a/src/views/search/HomeView.tsx +++ b/src/views/search/HomeView.tsx @@ -198,7 +198,7 @@ function NewDomainModal() { export function HomeView() { return ( -
+
diff --git a/src/views/search/SearchView.tsx b/src/views/search/SearchView.tsx index 3fee80b8..01aec28a 100644 --- a/src/views/search/SearchView.tsx +++ b/src/views/search/SearchView.tsx @@ -3,6 +3,7 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; import Sticky from "react-stickynode"; +import { FooterView } from "@/components/layout/Footer"; import { Navigation } from "@/components/layout/Navigation"; import { ThinContainer } from "@/components/layout/ThinContainer"; import { WideContainer } from "@/components/layout/WideContainer"; @@ -25,7 +26,7 @@ export function SearchView() { ); return ( - <> +
{t("global.name")} @@ -61,6 +62,6 @@ export function SearchView() { - + ); } diff --git a/tailwind.config.js b/tailwind.config.js index 3a6816a6..0733f489 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,3 +1,5 @@ +const themer = require("tailwindcss-themer"); + /** @type {import('tailwindcss').Config} */ module.exports = { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], @@ -42,5 +44,30 @@ module.exports = { animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" } } }, - plugins: [require("tailwind-scrollbar")] + plugins: [ + require("tailwind-scrollbar"), + themer({ + defaultTheme: { + extend: { + colors: { + background: { + main: "#0A0A10", + accentA: "#6E3B80", + accentB: "#1F1F50" + }, + global: { + accentA: "#505DBD", + accentB: "#3440A1" + }, + type: { + emphasis: "#FFFFFF", + text: "#73739D", + dimmed: "#926CAD", + divider: "#353549" + } + } + } + } + }) + ] }; diff --git a/yarn.lock b/yarn.lock index 8b7759b5..592a48f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2147,7 +2147,7 @@ chokidar@^3.5.1, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -classnames@^2.0.0: +classnames@^2.0.0, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -2184,11 +2184,27 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.1.4, color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.1.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3410,6 +3426,11 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: get-intrinsic "^1.2.0" is-typed-array "^1.1.10" +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -3761,6 +3782,11 @@ jsonpointer@^5.0.0: array-includes "^3.1.5" object.assign "^4.1.3" +just-unique@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/just-unique/-/just-unique-4.2.0.tgz#80927360b92f5039fad7e7f5f8bc48904d9177b6" + integrity sha512-cxQGGUiit6CGUpuuiezY8N4m1wgF4o7127rXEXDFcxeDUFfdV7gSkwA26Fe2wWBiNQq2SZOgN4gSmMxB/StA8Q== + language-subtag-registry@~0.3.2: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -3840,6 +3866,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.mergewith@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" + integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== + lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" @@ -4859,6 +4890,13 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -5124,6 +5162,16 @@ tailwind-scrollbar@^2.0.1: resolved "https://registry.yarnpkg.com/tailwind-scrollbar/-/tailwind-scrollbar-2.1.0.tgz#46e0b8788cef75387f9d163a5ec82b8cacd66c44" integrity sha512-zpvY5mDs0130YzYjZKBiDaw32rygxk5RyJ4KmeHjGnwkvbjm/PszON1m4Bbt2DkMRIXlXsfNevykAESgURN4KA== +tailwindcss-themer@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/tailwindcss-themer/-/tailwindcss-themer-3.1.0.tgz#4440d62d7edc54f8f0aaef3404bb71844fa556d6" + integrity sha512-IfgxpCxWm5rRK3Q7aTvVyhQ/7tyyn8EJl5tFak5tS+/n8oXT7OGfv8praYepR7+IsM92waAuBDZng1HgnstrYA== + dependencies: + color "^4.1.0" + just-unique "^4.2.0" + lodash.merge "^4.6.2" + lodash.mergewith "^4.6.2" + tailwindcss@^3.2.4: version "3.3.1" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.1.tgz#b6662fab6a9b704779e48d083a9fef5a81d2b81e" From 1fde44076a3d4669eb661aa57874926dd97208aa Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 20 Aug 2023 17:59:46 +0200 Subject: [PATCH 14/22] Lightbar --- src/components/SearchBar.tsx | 54 +++++++-- src/components/layout/Footer.tsx | 37 +++--- src/components/layout/Navigation.tsx | 114 ++++++++++-------- .../text-inputs/TextInputControl.tsx | 3 + src/components/utils/Flare.css | 7 ++ src/components/utils/Flare.tsx | 34 ++++-- src/components/utils/Lightbar.css | 22 ++++ src/components/utils/Lightbar.tsx | 9 ++ src/setup/App.tsx | 2 - src/setup/index.css | 5 +- src/setup/locales/en/translation.json | 5 +- src/views/HomePage.tsx | 0 src/views/SearchPart.tsx | 0 src/views/other/v2Migration.tsx | 107 ---------------- src/views/parts/home/BookmarksPart.tsx | 58 +++++++++ src/views/parts/home/WatchingPart.tsx | 45 +++++++ src/views/search/HomeView.tsx | 106 +--------------- src/views/search/SearchView.tsx | 5 +- tailwind.config.js | 29 ++++- 19 files changed, 327 insertions(+), 315 deletions(-) create mode 100644 src/components/utils/Flare.css create mode 100644 src/components/utils/Lightbar.css create mode 100644 src/components/utils/Lightbar.tsx create mode 100644 src/views/HomePage.tsx create mode 100644 src/views/SearchPart.tsx delete mode 100644 src/views/other/v2Migration.tsx create mode 100644 src/views/parts/home/BookmarksPart.tsx create mode 100644 src/views/parts/home/WatchingPart.tsx diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 1d2ce354..9f2b6958 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,4 +1,8 @@ +import c from "classnames"; +import { useState } from "react"; + import { MWQuery } from "@/backend/metadata/types/mw"; +import { Flare } from "@/components/utils/Flare"; import { Icon, Icons } from "./Icon"; import { TextInputControl } from "./text-inputs/TextInputControl"; @@ -11,6 +15,8 @@ export interface SearchBarProps { } export function SearchBarInput(props: SearchBarProps) { + const [focused, setFocused] = useState(false); + function setSearch(value: string) { props.onChange( { @@ -22,18 +28,42 @@ export function SearchBarInput(props: SearchBarProps) { } return ( -
-
- -
- - setSearch(val)} - value={props.value.searchQuery} - className="w-full flex-1 bg-transparent px-4 py-4 pl-12 text-white placeholder-denim-700 focus:outline-none sm:py-4 sm:pr-2" - placeholder={props.placeholder} + + -
+ + +
+ +
+ + { + setFocused(false); + props.onUnFocus(); + }} + onFocus={() => setFocused(true)} + onChange={(val) => setSearch(val)} + value={props.value.searchQuery} + className="text-search-text w-full flex-1 bg-transparent px-4 py-4 pl-12 placeholder-search-placeholder focus:outline-none sm:py-4 sm:pr-2" + placeholder={props.placeholder} + /> +
+ ); } diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 24f975fb..05b17a01 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -22,6 +22,15 @@ function FooterLink(props: { ); } +function Dmca() { + const { t } = useTranslation(); + return ( + + {t("footer.links.dmca")} + + ); +} + export function Footer() { const { t } = useTranslation(); @@ -33,29 +42,27 @@ export function Footer() {

{t("footer.tagline")}

-
- - {t("footer.links.github")} - - - {t("footer.links.github")} - -

{t("footer.legal.disclaimer")}

{t("footer.legal.disclaimerText")}

-
- - {t("footer.links.dmca")} - +
+
+ + {t("footer.links.github")} + + + {t("footer.links.discord")} + +
+
+
+ +
); diff --git a/src/components/layout/Navigation.tsx b/src/components/layout/Navigation.tsx index a9a3e0c1..266735ce 100644 --- a/src/components/layout/Navigation.tsx +++ b/src/components/layout/Navigation.tsx @@ -3,6 +3,7 @@ import { Link } from "react-router-dom"; import { IconPatch } from "@/components/buttons/IconPatch"; import { Icons } from "@/components/Icon"; +import { Lightbar } from "@/components/utils/Lightbar"; import { useBannerSize } from "@/hooks/useBanner"; import { conf } from "@/setup/config"; import SettingsModal from "@/views/SettingsModal"; @@ -18,60 +19,67 @@ export function Navigation(props: NavigationProps) { const bannerHeight = useBannerSize(); const [showModal, setShowModal] = useState(false); return ( -
-
-
-
-
-
-
- - - -
- {props.children} -
-
- { - setShowModal(true); - }} - /> - - - - - - + <> +
+
+
- setShowModal(false)} /> -
+
+
+
+
+
+
+
+ + + +
+ {props.children} +
+
+ { + setShowModal(true); + }} + /> + + + + + + +
+
+ setShowModal(false)} /> +
+ ); } diff --git a/src/components/text-inputs/TextInputControl.tsx b/src/components/text-inputs/TextInputControl.tsx index a6d18994..c3b42616 100644 --- a/src/components/text-inputs/TextInputControl.tsx +++ b/src/components/text-inputs/TextInputControl.tsx @@ -1,6 +1,7 @@ export interface TextInputControlPropsNoLabel { onChange?: (data: string) => void; onUnFocus?: () => void; + onFocus?: () => void; value?: string; placeholder?: string; className?: string; @@ -17,6 +18,7 @@ export function TextInputControl({ label, className, placeholder, + onFocus, }: TextInputControlProps) { const input = ( onChange && onChange(e.target.value)} value={value} onBlur={() => onUnFocus && onUnFocus()} + onFocus={() => onFocus?.()} /> ); diff --git a/src/components/utils/Flare.css b/src/components/utils/Flare.css new file mode 100644 index 00000000..1c17cda8 --- /dev/null +++ b/src/components/utils/Flare.css @@ -0,0 +1,7 @@ +.flare-enabled .flare-light { + opacity: 1 !important; +} + +.hover\:flare-enabled:hover .flare-light { + opacity: 1 !important; +} diff --git a/src/components/utils/Flare.tsx b/src/components/utils/Flare.tsx index 680c3812..86ea0bfe 100644 --- a/src/components/utils/Flare.tsx +++ b/src/components/utils/Flare.tsx @@ -1,5 +1,6 @@ import c from "classnames"; -import { useEffect, useRef } from "react"; +import { ReactNode, useEffect, useRef } from "react"; +import "./Flare.css"; export interface FlareProps { className?: string; @@ -12,7 +13,15 @@ export interface FlareProps { const SIZE_DEFAULT = 200; const CSS_VAR_DEFAULT = "--colors-global-accentA"; -export function Flare(props: FlareProps) { +function Base(props: { className?: string; children?: ReactNode }) { + return
{props.children}
; +} + +function Child(props: { className?: string; children?: ReactNode }) { + return
{props.children}
; +} + +function Light(props: FlareProps) { const outerRef = useRef(null); const size = props.flareSize ?? SIZE_DEFAULT; const cssVar = props.cssColorVar ?? CSS_VAR_DEFAULT; @@ -20,13 +29,14 @@ export function Flare(props: FlareProps) { useEffect(() => { function mouseMove(e: MouseEvent) { if (!outerRef.current) return; + const rect = outerRef.current.getBoundingClientRect(); outerRef.current.style.setProperty( "--bg-x", - `${(e.clientX - size / 2).toFixed(0)}px` + `${(e.clientX - rect.left - size / 2).toFixed(0)}px` ); outerRef.current.style.setProperty( "--bg-y", - `${(e.clientY - size / 2).toFixed(0)}px` + `${(e.clientY - rect.top - size / 2).toFixed(0)}px` ); } document.addEventListener("mousemove", mouseMove); @@ -38,10 +48,10 @@ export function Flare(props: FlareProps) {
); } + +export const Flare = { + Base, + Light, + Child, +}; diff --git a/src/components/utils/Lightbar.css b/src/components/utils/Lightbar.css new file mode 100644 index 00000000..9cf845d4 --- /dev/null +++ b/src/components/utils/Lightbar.css @@ -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; +} diff --git a/src/components/utils/Lightbar.tsx b/src/components/utils/Lightbar.tsx new file mode 100644 index 00000000..604f7cba --- /dev/null +++ b/src/components/utils/Lightbar.tsx @@ -0,0 +1,9 @@ +import "./Lightbar.css"; + +export function Lightbar(props: { className?: string }) { + return ( +
+
+
+ ); +} diff --git a/src/setup/App.tsx b/src/setup/App.tsx index 6bb6b957..b3a49685 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -17,7 +17,6 @@ import { SettingsProvider } from "@/state/settings"; import { WatchedContextProvider } from "@/state/watched"; import { MediaView } from "@/views/media/MediaView"; import { NotFoundPage } from "@/views/notfound/NotFoundView"; -import { V2MigrationView } from "@/views/other/v2Migration"; import { SearchView } from "@/views/search/SearchView"; function LegacyUrlView({ children }: { children: ReactElement }) { @@ -62,7 +61,6 @@ function App() { {/* functional routes */} - diff --git a/src/setup/index.css b/src/setup/index.css index 259aaa61..168ce2ea 100644 --- a/src/setup/index.css +++ b/src/setup/index.css @@ -4,9 +4,10 @@ html, body { - @apply bg-denim-100 font-open-sans text-denim-700 overflow-x-hidden; + @apply bg-background-main font-open-sans text-denim-700 overflow-x-hidden; min-height: 100vh; min-height: 100dvh; + position: relative; } html[data-full], @@ -198,4 +199,4 @@ input[type=range].styled-slider.slider-progress::-ms-fill-lower { ::-webkit-scrollbar { /* For some reason the styles don't get applied without the width */ width: 13px; -} \ No newline at end of file +} diff --git a/src/setup/locales/en/translation.json b/src/setup/locales/en/translation.json index f78adc46..e11e67de 100644 --- a/src/setup/locales/en/translation.json +++ b/src/setup/locales/en/translation.json @@ -10,7 +10,7 @@ "headingTitle": "Search results", "bookmarks": "Bookmarks", "continueWatching": "Continue Watching", - "title": "What do you want to watch?", + "title": "What to watch tonight?", "placeholder": "What do you want to watch?" }, "media": { @@ -136,7 +136,8 @@ "tagline": "Watch your favorite shows and movies with this open source streaming app.", "links": { "github": "GitHub", - "dmca": "DMCA" + "dmca": "DMCA", + "discord": "Discord" }, "legal": { "disclaimer": "Disclaimer", diff --git a/src/views/HomePage.tsx b/src/views/HomePage.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/views/SearchPart.tsx b/src/views/SearchPart.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/views/other/v2Migration.tsx b/src/views/other/v2Migration.tsx deleted file mode 100644 index d0b05e42..00000000 --- a/src/views/other/v2Migration.tsx +++ /dev/null @@ -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