mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
commit
c7f3f774bb
17 changed files with 1508 additions and 1358 deletions
48
.github/workflows/linting_annotate.yml
vendored
48
.github/workflows/linting_annotate.yml
vendored
|
@ -1,48 +0,0 @@
|
|||
name: Annotate linting
|
||||
|
||||
permissions:
|
||||
actions: read # download artifact
|
||||
checks: write # annotate
|
||||
|
||||
# this is done as a seperate workflow so
|
||||
# the annotater has access to write to checks (to annotate)
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Linting and Testing"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
annotate:
|
||||
name: Annotate linting
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download linting report
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "eslint_report.json"
|
||||
})[0];
|
||||
const download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
const fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/eslint_report.zip', Buffer.from(download.data));
|
||||
|
||||
- run: unzip eslint_report.zip
|
||||
|
||||
- name: Annotate linting
|
||||
uses: ataylorme/eslint-annotate-action@v2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
report-json: "eslint_report.json"
|
11
.github/workflows/linting_testing.yml
vendored
11
.github/workflows/linting_testing.yml
vendored
|
@ -25,15 +25,8 @@ jobs:
|
|||
- name: Install Yarn packages
|
||||
run: yarn install
|
||||
|
||||
- name: Run ESLint Report
|
||||
run: yarn lint:report
|
||||
# continue on error, so it still reports it in the next step
|
||||
continue-on-error: true
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: eslint_report.json
|
||||
path: eslint_report.json
|
||||
- name: Run ESLint
|
||||
run: yarn lint
|
||||
|
||||
building:
|
||||
name: Build project
|
||||
|
|
18
package.json
18
package.json
|
@ -8,6 +8,7 @@
|
|||
"@headlessui/react": "^1.5.0",
|
||||
"@react-spring/web": "^9.7.1",
|
||||
"@use-gesture/react": "^10.2.24",
|
||||
"core-js": "^3.29.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dompurify": "^3.0.1",
|
||||
"fscreen": "^1.2.0",
|
||||
|
@ -44,9 +45,8 @@
|
|||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
"defaults",
|
||||
"chrome > 90"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
|
@ -55,6 +55,9 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.21.0",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@types/chromecast-caf-sender": "^1.0.5",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
|
@ -65,13 +68,14 @@
|
|||
"@types/pako": "^2.0.0",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-router": "^5.1.18",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-stickynode": "^4.0.0",
|
||||
"@types/react-transition-group": "^4.4.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
|
@ -94,7 +98,7 @@
|
|||
"vite-plugin-package-version": "^1.0.2",
|
||||
"vite-plugin-pwa": "^0.14.4",
|
||||
"vitest": "^0.28.5",
|
||||
"workbox-window": "^6.5.4",
|
||||
"@types/react-helmet": "^6.1.6"
|
||||
"workbox-build": "^6.5.4",
|
||||
"workbox-window": "^6.5.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export type JWMediaResult = {
|
|||
title: string;
|
||||
poster?: string;
|
||||
id: number;
|
||||
original_release_year: number;
|
||||
original_release_year?: number;
|
||||
jw_entity_id: string;
|
||||
object_type: JWContentTypes;
|
||||
seasons?: JWSeasonShort[];
|
||||
|
@ -67,7 +67,7 @@ export function formatJWMeta(
|
|||
return {
|
||||
title: media.title,
|
||||
id: media.id.toString(),
|
||||
year: media.original_release_year.toString(),
|
||||
year: media.original_release_year?.toString(),
|
||||
poster: media.poster
|
||||
? `${JW_IMAGE_BASE}${media.poster.replace("{profile}", "s166")}`
|
||||
: undefined,
|
||||
|
|
|
@ -24,7 +24,7 @@ export type MWSeasonWithEpisodeMeta = {
|
|||
type MWMediaMetaBase = {
|
||||
title: string;
|
||||
id: string;
|
||||
year: string;
|
||||
year?: string;
|
||||
poster?: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ function MediaCardContent({
|
|||
|
||||
const canLink = linkable && !closable;
|
||||
|
||||
const dotListContent = [t(`media.${media.type}`)];
|
||||
if (media.year) dotListContent.push(media.year);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group -m-3 mb-2 rounded-xl bg-denim-300 bg-opacity-0 transition-colors duration-100 ${
|
||||
|
@ -115,10 +118,7 @@ function MediaCardContent({
|
|||
<h1 className="mb-1 max-h-[4.5rem] text-ellipsis break-words font-bold text-white line-clamp-3">
|
||||
<span>{media.title}</span>
|
||||
</h1>
|
||||
<DotList
|
||||
className="text-xs"
|
||||
content={[t(`media.${media.type}`), media.year]}
|
||||
/>
|
||||
<DotList className="text-xs" content={dotListContent} />
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import React, { ReactNode, Suspense } from "react";
|
||||
import "core-js/stable";
|
||||
import React, { Suspense } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { BrowserRouter, HashRouter } from "react-router-dom";
|
||||
import type { ReactNode } from "react-router-dom/node_modules/@types/react/index";
|
||||
import { ErrorBoundary } from "@/components/layout/ErrorBoundary";
|
||||
import { conf } from "@/setup/config";
|
||||
import { registerSW } from "virtual:pwa-register";
|
||||
|
@ -21,9 +23,7 @@ if (key) {
|
|||
}
|
||||
initializeChromecast();
|
||||
registerSW({
|
||||
onNeedRefresh() {
|
||||
window.location.reload();
|
||||
},
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
const LazyLoadedApp = React.lazy(async () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const APP_VERSION = import.meta.env.PACKAGE_VERSION;
|
||||
export const DISCORD_LINK = "https://discord.gg/Jhqt4Xzpfb";
|
||||
export const GITHUB_LINK = "https://github.com/movie-web/movie-web";
|
||||
export const APP_VERSION = "3.0.6";
|
||||
export const GA_ID = "G-44YVXRL61C";
|
||||
|
|
|
@ -94,7 +94,6 @@ input[type=range].styled-slider::-webkit-slider-runnable-track {
|
|||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: var(--slider-border-radius);
|
||||
margin-bottom: 1.1em;
|
||||
}
|
||||
|
||||
input[type=range].styled-slider::-webkit-slider-thumb:hover {
|
||||
|
|
|
@ -99,7 +99,7 @@ function buildStorageObject<T>(store: InternalStoreData): StoreRet<T> {
|
|||
localStorage.setItem(key, JSON.stringify(withVersion));
|
||||
|
||||
if (!storeCallbacks[key]) storeCallbacks[key] = [];
|
||||
storeCallbacks[key].forEach((v) => v(structuredClone(data)));
|
||||
storeCallbacks[key].forEach((v) => v(window.structuredClone(data)));
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PopoutListAction } from "../../popouts/PopoutUtils";
|
||||
import { QualityDisplayAction } from "./QualityDisplayAction";
|
||||
|
|
|
@ -41,7 +41,7 @@ function VideoElement(props: Props) {
|
|||
autoPlay={props.autoPlay}
|
||||
muted={mediaPlaying.volume === 0}
|
||||
playsInline
|
||||
className="h-full w-full"
|
||||
className="z-0 h-full w-full"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ import { MWMediaMeta } from "@/backend/metadata/types";
|
|||
import { ErrorMessage } from "@/components/layout/ErrorBoundary";
|
||||
import { Link } from "@/components/text/Link";
|
||||
import { conf } from "@/setup/config";
|
||||
import { Component, ReactNode } from "react";
|
||||
import { Component } from "react";
|
||||
import type { ReactNode } from "react-router-dom/node_modules/@types/react/index";
|
||||
import { Trans } from "react-i18next";
|
||||
import { VideoPlayerHeader } from "./VideoPlayerHeader";
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { useLoading } from "@/hooks/useLoading";
|
||||
|
|
|
@ -43,6 +43,8 @@ export function ScrollToActive(props: ScrollToActiveProps) {
|
|||
const ref = createRef<HTMLDivElement>();
|
||||
const inited = useRef<boolean>(false);
|
||||
|
||||
const SAFE_OFFSET = 30;
|
||||
|
||||
// Scroll to "active" child on first load (AKA mount except React dumb)
|
||||
useEffect(() => {
|
||||
if (inited.current) return;
|
||||
|
@ -61,27 +63,31 @@ export function ScrollToActive(props: ScrollToActiveProps) {
|
|||
wrapper?.querySelector(".active");
|
||||
|
||||
if (wrapper && active) {
|
||||
let activeYPositionCentered = 0;
|
||||
const setActiveYPositionCentered = () => {
|
||||
activeYPositionCentered =
|
||||
active.getBoundingClientRect().top -
|
||||
wrapper.getBoundingClientRect().top +
|
||||
active.offsetHeight / 2;
|
||||
let wrapperHeight = 0;
|
||||
let activePos = 0;
|
||||
let activeHeight = 0;
|
||||
let wrapperScroll = 0;
|
||||
|
||||
const getCoords = () => {
|
||||
const activeRect = active.getBoundingClientRect();
|
||||
const wrapperRect = wrapper.getBoundingClientRect();
|
||||
wrapperHeight = wrapperRect.height;
|
||||
activeHeight = activeRect.height;
|
||||
activePos = activeRect.top - wrapperRect.top + wrapper.scrollTop;
|
||||
wrapperScroll = wrapper.scrollTop;
|
||||
};
|
||||
setActiveYPositionCentered();
|
||||
getCoords();
|
||||
|
||||
if (activeYPositionCentered >= wrapper.offsetHeight / 2) {
|
||||
// Check if the active element is below the vertical center line, then scroll it into center
|
||||
const isVisible =
|
||||
activePos + activeHeight <
|
||||
wrapperScroll + wrapperHeight - SAFE_OFFSET ||
|
||||
activePos > wrapperScroll + SAFE_OFFSET;
|
||||
if (isVisible) {
|
||||
const activeMiddlePos = activePos + activeHeight / 2; // pos of middle of active element
|
||||
const viewMiddle = wrapperHeight / 2; // half of the available height
|
||||
const pos = activeMiddlePos - viewMiddle;
|
||||
wrapper.scrollTo({
|
||||
top: activeYPositionCentered - wrapper.offsetHeight / 2,
|
||||
});
|
||||
}
|
||||
|
||||
setActiveYPositionCentered();
|
||||
if (activeYPositionCentered > wrapper.offsetHeight / 2) {
|
||||
// If the element is over the vertical center line, scroll to the end
|
||||
wrapper.scrollTo({
|
||||
top: wrapper.scrollHeight,
|
||||
top: pos,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import loadVersion from "vite-plugin-package-version";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import checker from "vite-plugin-checker";
|
||||
|
@ -7,10 +7,25 @@ import path from "path";
|
|||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
react({
|
||||
babel: {
|
||||
presets: [
|
||||
"@babel/preset-typescript",
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
modules: false,
|
||||
useBuiltIns: "entry",
|
||||
corejs: {
|
||||
version: "3.29",
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
injectRegister: "inline",
|
||||
workbox: {
|
||||
globIgnores: ["**ping.txt**"],
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue