Merge pull request #139 from AnonymusRaccoon/native

This commit is contained in:
Zoe Roux 2023-01-07 23:47:28 +09:00 committed by GitHub
commit b0ea38b047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
193 changed files with 23739 additions and 5826 deletions

View File

@ -4,3 +4,4 @@ AUTHENTICATION_SECRET=
POSTGRES_USER=kyoousername
POSTGRES_PASSWORD=kyoopassword
POSTGRES_DB=kyooDB
PUBLIC_BACK_URL=http://localhost:5000

View File

@ -44,7 +44,7 @@ jobs:
if: github.event_name != 'pull_request'
with:
ref: ${{github.ref}}
check-name: tests
check-name: "Back tests"
repo-token: ${{secrets.GITHUB_TOKEN}}
running-workflow-name: analysis
allowed-conclusions: success,skipped,cancelled,neutral,failure
@ -53,7 +53,7 @@ jobs:
if: github.event_name == 'pull_request'
with:
ref: ${{github.event.pull_request.head.sha}}
check-name: tests
check-name: "Back tests"
repo-token: ${{secrets.GITHUB_TOKEN}}
running-workflow-name: analysis
allowed-conclusions: success,skipped,cancelled,neutral,failure

View File

@ -1,15 +1,43 @@
name: CodingStyle
name: Coding Style
on: [pull_request, workflow_dispatch]
jobs:
build:
name: "Coding style check"
back:
name: "Lint Back"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Build the app
run: cd back && dotnet build -p:CheckCodingStyle=true -p:TreatWarningsAsErrors=true '-p:SkipTranscoder=true'
front:
name: "Lint Front"
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./front
steps:
- uses: actions/checkout@v1
- name: Find yarn cache
id: yarn-cache-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Restore cache
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --immutable
- name: Lint
run: yarn lint

View File

@ -19,7 +19,7 @@ jobs:
- context: ./front
label: front
image: ghcr.io/${{github.repository_owner}}/kyoo_front
name: Docker build ${{matrix.label}}
name: Build ${{matrix.label}}
steps:
- uses: actions/checkout@v2
with:

View File

@ -1,4 +1,4 @@
name: Update the documentation
name: Documentation
on:
push:
@ -6,8 +6,9 @@ on:
- master
jobs:
Building:
runs-on: [ubuntu-latest]
doc:
name: Update the documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: nikeee/docfx-action@v1.0.0

60
.github/workflows/native-build.yml vendored Normal file
View File

@ -0,0 +1,60 @@
name: Native build
on:
push:
branches:
- master
- next
pull_request:
jobs:
update:
name: Expo Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./front
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Check for EXPO_TOKEN
run: |
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
cache: yarn
cache-dependency-path: front/yarn.lock
- name: Setup Expo
uses: expo/expo-github-action@v7
with:
expo-version: latest
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: Install dependencies
run: yarn install --immutable
- name: Build Mobile Release
run: yarn build:mobile | tee log.txt
- name: Parse Asset URL
id: url
run: |
ASSET_URL=$(grep -oe 'https://expo.dev/artifacts/eas/.*' log.txt)
echo The android url is $ASSET_URL
echo "assetUrl=$ASSET_URL" >> $GITHUB_OUTPUT
- name: Download APK Asset
run: wget -O kyoo.apk ${{ steps.url.outputs.assetUrl }}
- uses: actions/upload-artifact@v2
with:
name: kyoo.apk
path: ./front/kyoo.apk

44
.github/workflows/native-update.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Native update
on:
push:
branches:
- master
- next
jobs:
update:
name: Expo Update
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./front
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Check for EXPO_TOKEN
run: |
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
cache: yarn
cache-dependency-path: front/yarn.lock
- name: Setup Expo
uses: expo/expo-github-action@v7
with:
expo-version: latest
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: Install dependencies
run: yarn install --immutable
- name: Publish update
run: yarn update

View File

@ -8,6 +8,7 @@ on:
jobs:
release:
name: Release a new version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -20,17 +21,16 @@ jobs:
repo-token: ${{secrets.GITHUB_TOKEN}}
running-workflow-name: release
allowed-conclusions: success,skipped,cancelled,neutral
# - name: Public the abstractions to nuget
# id: publish_nuget
# uses: rohith/publish-nuget@v2
# with:
# PROJECT_FILE_PATH: Kyoo.Abstractions/Kyoo.Abstractions.csproj
# PACKAGE_NAME: Kyoo.Abstractions
# VERSION_REGEX: ^\s*<PackageVersion>(.*)<\/PackageVersion>\s*$
# NUGET_KEY: ${{secrets.NUGET_API_KEY}}
# INCLUDE_SYMBOLS: true
- name: Download artifacts
uses: dawidd6/action-download-artifact@v2
with:
commit: ${{env.COMMIT_SHA}}
workflow: native-build.yml
path: ./artifacts
github_token: ${{secrets.GITHUB_TOKEN}}
- name: Create Release
uses: ncipollo/release-action@v1
with:
generateReleaseNotes: true
artifacts: ./artifacts/**/*
token: ${{secrets.GITHUB_TOKEN}}

View File

@ -8,7 +8,8 @@ on:
jobs:
build:
test:
name: Run Robot Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

View File

@ -8,6 +8,7 @@ on:
jobs:
tests:
name: Back tests
runs-on: ubuntu-latest
container: mcr.microsoft.com/dotnet/sdk:6.0
services:

View File

@ -120,6 +120,24 @@ namespace Kyoo.Core.Api
return _libraryManager.Search<Show>(query);
}
/// <summary>
/// Search items
/// </summary>
/// <remarks>
/// Search for items
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of items found for the specified query.</returns>
[HttpGet("items")]
[HttpGet("item", Order = AlternativeRoute)]
[Permission(nameof(Show), Kind.Read)]
[ApiDefinition("Items")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<LibraryItem>> SearchItems(string query)
{
return _libraryManager.Search<LibraryItem>(query);
}
/// <summary>
/// Search episodes
/// </summary>

View File

@ -29,20 +29,24 @@ services:
dockerfile: Dockerfile.dev
volumes:
- ./front:/app
- /app/node_modules/
- /app/.next/
- /app/.yarn
- /app/node_modules
- /app/apps/web/.next/
- /app/apps/mobile/.expo/
ports:
- "3000:3000"
- "19000:19000"
restart: on-failure
environment:
- KYOO_URL=http://back:5000
- KYOO_URL=${KYOO_URL:-http://back:5000}
- PUBLIC_BACK_URL=${PUBLIC_BACK_URL}
ingress:
image: nginx
restart: on-failure
environment:
- PORT=8901
- FRONT_URL=http://front:3000
- BACK_URL=http://back:5000
- BACK_URL=${KYOO_URL:-http://back:5000}
volumes:
- ./nginx.conf.template:/etc/nginx/templates/kyoo.conf.template:ro
depends_on:

View File

@ -22,14 +22,15 @@ services:
build: ./front
restart: on-failure
environment:
- KYOO_URL=http://back:5000
- KYOO_URL=${KYOO_URL:-http://back:5000}
- PUBLIC_BACK_URL=${PUBLIC_BACK_URL}
ingress:
image: nginx
restart: on-failure
environment:
- PORT=8901
- FRONT_URL=http://front:8901
- BACK_URL=http://back:5000
- BACK_URL=${KYOO_URL:-http://back:5000}
volumes:
- ./nginx.conf.template:/etc/nginx/templates/kyoo.conf.template:ro
depends_on:

View File

@ -7,4 +7,8 @@ node_modules
npm-debug.log
README.md
.next
.expo
.git
.yarn
!.yarn/releases
!.yarn/plugins

View File

@ -1,6 +1,11 @@
{
"extends": ["next/core-web-vitals", "prettier"],
"plugins": ["header"],
"settings": {
"next": {
"rootDir": "apps/web/"
}
},
"rules": {
"@next/next/no-img-element": "off",
"header/header": [

2
front/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/.yarn/releases/** binary
/.yarn/plugins/** binary

24
front/.gitignore vendored
View File

@ -1,19 +1,19 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
node_modules
.pnp
.pnp.js
# testing
/coverage
coverage
# next.js
/.next/
/out/
.next/
out/
# production
/build
build
# misc
.DS_Store
@ -33,3 +33,15 @@ yarn-error.log*
# typescript
*.tsbuildinfo
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
.expo
apps/web/next-env.d.ts

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

801
front/.yarn/releases/yarn-3.2.4.cjs vendored Executable file

File diff suppressed because one or more lines are too long

20
front/.yarn/sdks/eslint/bin/eslint.js vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/bin/eslint.js your application uses
module.exports = absRequire(`eslint/bin/eslint.js`);

20
front/.yarn/sdks/eslint/lib/api.js vendored Normal file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint your application uses
module.exports = absRequire(`eslint`);

6
front/.yarn/sdks/eslint/package.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"name": "eslint",
"version": "8.19.0-sdk",
"main": "./lib/api.js",
"type": "commonjs"
}

3
front/.yarn/sdks/integrations.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!

20
front/.yarn/sdks/prettier/index.js vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/index.js
require(absPnpApiPath).setup();
}
}
// Defer to the real prettier/index.js your application uses
module.exports = absRequire(`prettier/index.js`);

View File

@ -0,0 +1,6 @@
{
"name": "prettier",
"version": "2.7.1-sdk",
"main": "./index.js",
"type": "commonjs"
}

20
front/.yarn/sdks/typescript/bin/tsc vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsc
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsc your application uses
module.exports = absRequire(`typescript/bin/tsc`);

20
front/.yarn/sdks/typescript/bin/tsserver vendored Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsserver
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/bin/tsserver your application uses
module.exports = absRequire(`typescript/bin/tsserver`);

20
front/.yarn/sdks/typescript/lib/tsc.js vendored Normal file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsc.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/tsc.js your application uses
module.exports = absRequire(`typescript/lib/tsc.js`);

View File

@ -0,0 +1,223 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
const moduleWrapper = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = str => str.startsWith("portal:/");
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
// before forwarding it to TS, and to add it back on all returned paths.
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes it much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
// file instances instead of the real ones.
//
// We only do this to modules owned by the the dependency tree roots.
// This avoids breaking the resolution when jumping inside a vendor
// with peer dep (otherwise jumping into react-dom would show resolution
// errors on react).
//
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
str = resolved;
}
}
str = normalize(str);
if (str.match(/\.zip\//)) {
switch (hostInfo) {
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
// VSCode only adds it automatically for supported schemes,
// so we have to do it manually for the `zip` scheme.
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
//
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
//
// 2021-10-08: VSCode changed the format in 1.61.
// Before | ^zip:/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
// 2022-04-06: VSCode changed the format in 1.66.
// Before | ^/zip//c:/foo/bar.zip/package.json
// After | ^/zip/c:/foo/bar.zip/package.json
//
// 2022-05-06: VSCode changed the format in 1.68
// Before | ^/zip/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
case `vscode <1.61`: {
str = `^zip:${str}`;
} break;
case `vscode <1.66`: {
str = `^/zip/${str}`;
} break;
case `vscode <1.68`: {
str = `^/zip${str}`;
} break;
case `vscode`: {
str = `^/zip/${str}`;
} break;
// To make "go to definition" work,
// We have to resolve the actual file system path from virtual path
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
case `coc-nvim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
} break;
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
// We have to resolve the actual file system path from virtual path,
// everything else is up to neovim
case `neovim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
} break;
default: {
str = `zip:${str}`;
} break;
}
}
}
return str;
}
function fromEditorPath(str) {
switch (hostInfo) {
case `coc-nvim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
} break;
case `neovim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
} break;
case `vscode`:
default: {
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
} break;
}
}
// Force enable 'allowLocalPluginLoads'
// TypeScript tries to resolve plugins using a path relative to itself
// which doesn't work when using the global cache
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
// TypeScript already does local loads and if this code is running the user trusts the workspace
// https://github.com/microsoft/vscode/issues/45856
const ConfiguredProject = tsserver.server.ConfiguredProject;
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
this.projectService.allowLocalPluginLoads = true;
return originalEnablePluginsWithOptions.apply(this, arguments);
};
// And here is the point where we hijack the VSCode <-> TS communications
// by adding ourselves in the middle. We locate everything that looks
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let hostInfo = `unknown`;
Object.assign(Session.prototype, {
onMessage(/** @type {string | object} */ message) {
const isStringMessage = typeof message === 'string';
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
if (
parsedMessage != null &&
typeof parsedMessage === `object` &&
parsedMessage.arguments &&
typeof parsedMessage.arguments.hostInfo === `string`
) {
hostInfo = parsedMessage.arguments.hostInfo;
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []).map(Number)
if (major === 1) {
if (minor < 61) {
hostInfo += ` <1.61`;
} else if (minor < 66) {
hostInfo += ` <1.66`;
} else if (minor < 68) {
hostInfo += ` <1.68`;
}
}
}
}
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
return typeof value === 'string' ? fromEditorPath(value) : value;
});
return originalOnMessage.call(
this,
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
);
},
send(/** @type {any} */ msg) {
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
return tsserver;
};
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserver.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/tsserver.js your application uses
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`));

View File

@ -0,0 +1,223 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
const moduleWrapper = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
const {isAbsolute} = require(`path`);
const pnpApi = require(`pnpapi`);
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
const isPortal = str => str.startsWith("portal:/");
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
return `${locator.name}@${locator.reference}`;
}));
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
// doesn't understand. This layer makes sure to remove the protocol
// before forwarding it to TS, and to add it back on all returned paths.
function toEditorPath(str) {
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) {
// We also take the opportunity to turn virtual paths into physical ones;
// this makes it much easier to work with workspaces that list peer
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
// file instances instead of the real ones.
//
// We only do this to modules owned by the the dependency tree roots.
// This avoids breaking the resolution when jumping inside a vendor
// with peer dep (otherwise jumping into react-dom would show resolution
// errors on react).
//
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
if (resolved) {
const locator = pnpApi.findPackageLocator(resolved);
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
str = resolved;
}
}
str = normalize(str);
if (str.match(/\.zip\//)) {
switch (hostInfo) {
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
// VSCode only adds it automatically for supported schemes,
// so we have to do it manually for the `zip` scheme.
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
//
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
//
// 2021-10-08: VSCode changed the format in 1.61.
// Before | ^zip:/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
// 2022-04-06: VSCode changed the format in 1.66.
// Before | ^/zip//c:/foo/bar.zip/package.json
// After | ^/zip/c:/foo/bar.zip/package.json
//
// 2022-05-06: VSCode changed the format in 1.68
// Before | ^/zip/c:/foo/bar.zip/package.json
// After | ^/zip//c:/foo/bar.zip/package.json
//
case `vscode <1.61`: {
str = `^zip:${str}`;
} break;
case `vscode <1.66`: {
str = `^/zip/${str}`;
} break;
case `vscode <1.68`: {
str = `^/zip${str}`;
} break;
case `vscode`: {
str = `^/zip/${str}`;
} break;
// To make "go to definition" work,
// We have to resolve the actual file system path from virtual path
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
case `coc-nvim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = resolve(`zipfile:${str}`);
} break;
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
// We have to resolve the actual file system path from virtual path,
// everything else is up to neovim
case `neovim`: {
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
str = `zipfile://${str}`;
} break;
default: {
str = `zip:${str}`;
} break;
}
}
}
return str;
}
function fromEditorPath(str) {
switch (hostInfo) {
case `coc-nvim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
// So in order to convert it back, we use .* to match all the thing
// before `zipfile:`
return process.platform === `win32`
? str.replace(/^.*zipfile:\//, ``)
: str.replace(/^.*zipfile:/, ``);
} break;
case `neovim`: {
str = str.replace(/\.zip::/, `.zip/`);
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
return str.replace(/^zipfile:\/\//, ``);
} break;
case `vscode`:
default: {
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
} break;
}
}
// Force enable 'allowLocalPluginLoads'
// TypeScript tries to resolve plugins using a path relative to itself
// which doesn't work when using the global cache
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
// TypeScript already does local loads and if this code is running the user trusts the workspace
// https://github.com/microsoft/vscode/issues/45856
const ConfiguredProject = tsserver.server.ConfiguredProject;
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
this.projectService.allowLocalPluginLoads = true;
return originalEnablePluginsWithOptions.apply(this, arguments);
};
// And here is the point where we hijack the VSCode <-> TS communications
// by adding ourselves in the middle. We locate everything that looks
// like an absolute path of ours and normalize it.
const Session = tsserver.server.Session;
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
let hostInfo = `unknown`;
Object.assign(Session.prototype, {
onMessage(/** @type {string | object} */ message) {
const isStringMessage = typeof message === 'string';
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
if (
parsedMessage != null &&
typeof parsedMessage === `object` &&
parsedMessage.arguments &&
typeof parsedMessage.arguments.hostInfo === `string`
) {
hostInfo = parsedMessage.arguments.hostInfo;
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
// The RegExp from https://semver.org/ but without the caret at the start
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
) ?? []).map(Number)
if (major === 1) {
if (minor < 61) {
hostInfo += ` <1.61`;
} else if (minor < 66) {
hostInfo += ` <1.66`;
} else if (minor < 68) {
hostInfo += ` <1.68`;
}
}
}
}
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
return typeof value === 'string' ? fromEditorPath(value) : value;
});
return originalOnMessage.call(
this,
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
);
},
send(/** @type {any} */ msg) {
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
return typeof value === `string` ? toEditorPath(value) : value;
})));
}
});
return tsserver;
};
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));

View File

@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/typescript.js
require(absPnpApiPath).setup();
}
}
// Defer to the real typescript/lib/typescript.js your application uses
module.exports = absRequire(`typescript/lib/typescript.js`);

View File

@ -0,0 +1,6 @@
{
"name": "typescript",
"version": "4.7.4-sdk",
"main": "./lib/typescript.js",
"type": "commonjs"
}

24
front/.yarnrc.yml Normal file
View File

@ -0,0 +1,24 @@
nodeLinker: node-modules
packageExtensions:
"@expo/cli@*":
dependencies:
expo-modules-autolinking: "*"
babel-preset-expo@*:
dependencies:
"@babel/core": "*"
expo-asset@*:
dependencies:
expo: "*"
react-native-codegen@*:
peerDependenciesMeta:
"@babel/preset-env":
optional: true
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.2.4.cjs

View File

@ -1,20 +1,29 @@
FROM node:16-alpine AS builder
RUN apk add git bash
WORKDIR /app
COPY .yarn ./.yarn
COPY .yarnrc.yml ./
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
COPY apps/web/package.json apps/web/package.json
COPY apps/mobile/package.json apps/mobile/package.json
COPY packages/ui/package.json packages/ui/package.json
COPY packages/primitives/package.json packages/primitives/package.json
COPY packages/models/package.json packages/models/package.json
RUN yarn --immutable
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
ENV NODE_ENV production
RUN yarn build
RUN yarn build:web
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/.next/standalone .
COPY --from=builder /app/.next/static ./.next/static/
COPY --from=builder /app/public ./public
COPY --from=builder /app/apps/web/.next/standalone/apps/web .
COPY --from=builder /app/apps/web/.next/standalone/node_modules ./node_modules
COPY --from=builder /app/apps/web/.next/static ./.next/static/
COPY --from=builder /app/apps/web/public ./public
EXPOSE 8901
ENV PORT 8901

View File

@ -1,10 +1,17 @@
FROM node:16-alpine AS builder
RUN apk add git bash
WORKDIR /app
COPY .yarn ./.yarn
COPY .yarnrc.yml ./
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
COPY apps/web/package.json apps/web/package.json
COPY apps/mobile/package.json apps/mobile/package.json
COPY packages/ui/package.json packages/ui/package.json
COPY packages/primitives/package.json packages/primitives/package.json
COPY packages/models/package.json packages/models/package.json
RUN yarn --immutable
ENV NEXT_TELEMETRY_DISABLED 1
EXPOSE 3000
ENV PORT 3000
EXPOSE 19000
CMD ["yarn", "dev"]

View File

@ -0,0 +1,74 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
const IS_DEV = process.env.APP_VARIANT === "development";
// Defined outside the config because dark splashscreen needs to be platform specific.
const splash = {
image: "./assets/icon.png",
resizeMode: "contain",
backgroundColor: "#eff1f5",
dark: {
image: "./assets/icon.png",
resizeMode: "contain",
backgroundColor: "#1e1e2e",
},
};
const config = {
expo: {
name: IS_DEV ? "Kyoo Development" : "Kyoo",
slug: "kyoo",
scheme: "kyoo",
version: "1.0.0",
orientation: "default",
icon: "./assets/icon.png",
entryPoint: "./index.tsx",
userInterfaceStyle: "automatic",
splash,
updates: {
fallbackToCacheTimeout: 0,
},
assetBundlePatterns: ["**/*"],
ios: {
supportsTablet: true,
},
android: {
package: IS_DEV ? "moe.sdg.kyoo.dev" : "moe.sdg.kyoo",
adaptiveIcon: {
foregroundImage: "./assets/icon.png",
backgroundColor: "#eff1f5",
},
splash,
},
updates: {
url: "https://u.expo.dev/55de6b52-c649-4a15-9a45-569ff5ed036c",
},
runtimeVersion: {
policy: "sdkVersion",
},
extra: {
eas: {
projectId: "55de6b52-c649-4a15-9a45-569ff5ed036c",
},
},
},
};
export default config;

View File

@ -0,0 +1,114 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { PortalProvider } from "@gorhom/portal";
import { ThemeSelector } from "@kyoo/primitives";
import { NavbarRight, NavbarTitle } from "@kyoo/ui";
import { createQueryClient } from "@kyoo/models";
import { QueryClientProvider } from "@tanstack/react-query";
import i18next from "i18next";
import { Stack } from "expo-router";
import { getLocales } from "expo-localization";
import * as SplashScreen from "expo-splash-screen";
import {
useFonts,
Poppins_300Light,
Poppins_400Regular,
Poppins_900Black,
} from "@expo-google-fonts/poppins";
import { useCallback, useLayoutEffect, useState } from "react";
import { useColorScheme } from "react-native";
import { initReactI18next } from "react-i18next";
import { useTheme } from "yoshiki/native";
import "intl-pluralrules";
// TODO: use a backend to load jsons.
import en from "../../../translations/en.json";
import fr from "../../../translations/fr.json";
i18next.use(initReactI18next).init({
interpolation: {
escapeValue: false,
},
fallbackLng: "en",
lng: getLocales()[0].languageCode,
resources: {
en: { translation: en },
fr: { translation: fr },
},
});
const ThemedStack = ({ onLayout }: { onLayout?: () => void }) => {
const theme = useTheme();
return (
<Stack
screenOptions={{
headerTitle: () => <NavbarTitle onLayout={onLayout} />,
headerRight: () => <NavbarRight />,
contentStyle: {
backgroundColor: theme.background,
},
headerStyle: {
backgroundColor: theme.appbar,
},
headerTintColor: theme.colors.white,
}}
/>
);
};
SplashScreen.preventAutoHideAsync();
export default function Root() {
const [queryClient] = useState(() => createQueryClient());
const theme = useColorScheme();
const [fontsLoaded] = useFonts({ Poppins_300Light, Poppins_400Regular, Poppins_900Black });
useLayoutEffect(() => {
// This does not seems to work on the global scope so why not.
SplashScreen.preventAutoHideAsync();
})
const onLayout = useCallback(async () => {
if (fontsLoaded) {
await SplashScreen.hideAsync();
}
}, [fontsLoaded]);
if (!fontsLoaded) return null;
return (
<QueryClientProvider client={queryClient}>
<ThemeSelector
theme={theme ?? "light"}
font={{
normal: "Poppins_400Regular",
"300": "Poppins_300Light",
"400": "Poppins_400Regular",
"900": "Poppins_900Black",
}}
>
<PortalProvider>
<ThemedStack onLayout={onLayout} />
</PortalProvider>
</ThemeSelector>
</QueryClientProvider>
);
}

View File

@ -18,6 +18,6 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import BrowsePage from "./index";
import { BrowsePage } from "@kyoo/ui";
export default BrowsePage;

View File

@ -0,0 +1,24 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import BrowsePage from "./browse";
// While there is no home page, show the browse page.
export default BrowsePage;

View File

@ -0,0 +1,27 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { MovieDetails } from "@kyoo/ui";
import { withRoute } from "../../utils";
export default withRoute(MovieDetails, {
options: { headerTransparent: true, headerStyle: { backgroundColor: "transparent" } },
statusBar: { barStyle: "light-content" },
});

View File

@ -0,0 +1,59 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { SearchPage } from "@kyoo/ui";
import { Stack } from "expo-router";
import { useTranslation } from "react-i18next";
import { createParam } from "solito";
import { useRouter } from "solito/router";
import { useTheme } from "yoshiki/native";
const { useParam } = createParam<{ q?: string }>();
const Search = ({ route }: { route: any }) => {
const theme = useTheme();
const { back } = useRouter();
const { t } = useTranslation();
const [query, setQuery] = useParam("q");
return (
<>
<Stack.Screen
options={{
headerTitle: () => null,
// TODO: this shouuld not be null but since the header right is on the left of the search bar. shrug
headerRight: () => null,
headerSearchBarOptions: {
autoFocus: true,
headerIconColor: theme.colors.white,
hintTextColor: theme.light.overlay1,
textColor: theme.paragraph,
placeholder: t("navbar.search")!,
onClose: () => back(),
onChangeText: (e) => setQuery(e.nativeEvent.text),
},
}}
/>
<SearchPage {...route.params} />
</>
);
};
export default Search;

View File

@ -0,0 +1,27 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { ShowDetails } from "@kyoo/ui";
import { withRoute } from "../../utils";
export default withRoute(ShowDetails, {
options: { headerTransparent: true, headerStyle: { backgroundColor: "transparent" } },
statusBar: { barStyle: "light-content" },
});

View File

@ -18,9 +18,13 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { withThemeProps } from "./with-theme";
import _InfiniteScroll from "react-infinite-scroll-component";
import { Player } from "@kyoo/ui";
import { withRoute } from "../../utils";
export const InfiniteScroll = withThemeProps(_InfiniteScroll, {
name: "InfiniteScroll",
export default withRoute(Player, {
options: {
headerShown: false,
},
statusBar: { hidden: true },
fullscreen: true,
});

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,32 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
// NOTE: `expo-router/babel` is a temporary extension to `babel-preset-expo`.
require.resolve("expo-router/babel"),
"transform-inline-environment-variables",
"react-native-reanimated/plugin",
],
};
};

View File

@ -0,0 +1,26 @@
{
"cli": {
"version": ">= 3.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": {
"APP_VARIANT": "development"
}
},
"preview": {
"distribution": "internal"
},
"production": {
"channel": "prod",
"android": {
"buildType": "apk"
}
}
},
"submit": {
"production": {}
}
}

View File

@ -0,0 +1,30 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { registerRootComponent } from "expo";
import { ExpoRoot } from "expo-router";
export function App() {
// @ts-ignore
const ctx = require.context("./app");
return <ExpoRoot context={ctx} />;
}
registerRootComponent(App);

View File

@ -0,0 +1,60 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
const { getDefaultConfig } = require("expo/metro-config");
const path = require("path");
const projectRoot = __dirname;
const defaultConfig = getDefaultConfig(projectRoot);
function addMonorepoSupport(config) {
const workspaceRoot = path.resolve(projectRoot, "../..");
return {
...config,
watchFolders: [...config.watchFolders, workspaceRoot],
resolver: {
...config.resolver,
nodeModulesPaths: [
...config.resolver.nodeModulesPaths,
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
],
disableHierarchicalLookup: true,
},
};
}
function addSvgTransformer(config) {
return {
...config,
transformer: {
...config.transformer,
babelTransformerPath: require.resolve("react-native-svg-transformer"),
},
resolver: {
...config.resolver,
assetExts: config.resolver.assetExts.filter((ext) => ext !== "svg"),
sourceExts: [...config.resolver.sourceExts, "svg"],
},
};
}
module.exports = addMonorepoSupport(addSvgTransformer(defaultConfig));

View File

@ -0,0 +1,60 @@
{
"name": "mobile",
"version": "1.0.0",
"main": "index.tsx",
"scripts": {
"dev": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"build": "eas build --profile production --platform android --non-interactive",
"build:dev": "eas build --profile development --platform android --non-interactive",
"update": "eas update --auto --channel prod"
},
"dependencies": {
"@expo-google-fonts/poppins": "^0.2.2",
"@gorhom/portal": "^1.0.14",
"@kyoo/ui": "workspace:^",
"@material-symbols/svg-400": "^0.4.2",
"@shopify/flash-list": "1.3.1",
"@tanstack/react-query": "^4.19.1",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
"expo": "^47.0.0",
"expo-build-properties": "~0.4.1",
"expo-constants": "~14.0.2",
"expo-dev-client": "~2.0.1",
"expo-font": "~11.0.1",
"expo-linear-gradient": "~12.0.1",
"expo-linking": "~3.3.0",
"expo-localization": "~14.0.0",
"expo-navigation-bar": "~2.0.1",
"expo-router": "^0.0.36",
"expo-screen-orientation": "~5.0.1",
"expo-status-bar": "~1.4.2",
"expo-updates": "~0.15.6",
"i18next": "^22.0.6",
"intl-pluralrules": "^1.3.1",
"moti": "^0.21.0",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-i18next": "^12.0.0",
"react-native": "0.70.5",
"react-native-reanimated": "~2.12.0",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "~3.18.0",
"react-native-svg": "13.4.0",
"react-native-video": "alpha",
"yoshiki": "0.4.5"
},
"devDependencies": {
"@babel/core": "^7.19.3",
"@types/react": "~18.0.24",
"@types/react-native": "~0.70.6",
"react-native-svg-transformer": "^1.0.0",
"typescript": "^4.6.3"
},
"installConfig": {
"hoistingLimits": "workspaces"
},
"private": true
}

View File

@ -0,0 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}

View File

@ -0,0 +1,61 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { Stack } from "expo-router";
import { ComponentType, useEffect } from "react";
import { StatusBar, StatusBarProps } from "react-native";
import * as ScreenOrientation from "expo-screen-orientation";
import * as NavigationBar from "expo-navigation-bar";
const FullscreenProvider = () => {
useEffect(() => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
NavigationBar.setVisibilityAsync("hidden");
return () => {
ScreenOrientation.unlockAsync();
NavigationBar.setVisibilityAsync("visible");
};
}, []);
return null;
};
export const withRoute = <Props,>(
Component: ComponentType<Props>,
options?: Parameters<typeof Stack.Screen>[0] & {
statusBar?: StatusBarProps;
fullscreen?: boolean;
},
) => {
const { statusBar, fullscreen, ...routeOptions } = options ?? {};
const WithUseRoute = ({ route, ...props }: Props & { route: any }) => {
return (
<>
{routeOptions && <Stack.Screen {...routeOptions} />}
{statusBar && <StatusBar {...statusBar} />}
{fullscreen && <FullscreenProvider />}
<Component {...route.params} {...props} />
</>
);
};
const { ...all } = Component;
Object.assign(WithUseRoute, { ...all });
return WithUseRoute;
};

View File

@ -0,0 +1,27 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: ["react-native-reanimated/plugin"],
};
};

133
front/apps/web/next.config.js Executable file
View File

@ -0,0 +1,133 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const DefinePlugin = require("webpack").DefinePlugin;
const suboctopus = path.dirname(require.resolve("libass-wasm"));
/**
* @type {import("next").NextConfig}
*/
const nextConfig = {
// FIXME: https://github.com/nandorojo/moti/issues/224
reactStrictMode: false,
swcMinify: true,
output: "standalone",
webpack: (config) => {
config.plugins = [
...config.plugins,
new CopyPlugin({
patterns: [
{
context: suboctopus,
from: "*",
to: "static/chunks/",
},
],
}),
];
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
"react-native$": "react-native-web",
},
extensions: [".web.ts", ".web.tsx", ".web.js", ".web.jsx", ...config.resolve.extensions],
};
if (!config.plugins) config.plugins = [];
config.plugins.push(
new DefinePlugin({
__DEV__: JSON.stringify(process.env.NODE_ENV !== "production"),
}),
);
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: [
{
loader: "@svgr/webpack",
options: {
native: true,
svgoConfig: {
plugins: [
{
name: "removeViewBox",
active: false,
},
],
},
},
},
],
});
return config;
},
async redirects() {
return [
{
source: "/",
destination: "/browse",
permanent: true,
},
];
},
i18n: {
locales: ["en", "fr"],
defaultLocale: "en",
},
experimental: {
forceSwcTransforms: true,
outputFileTracingRoot: path.join(__dirname, "../../"),
transpilePackages: [
"@kyoo/ui",
"@kyoo/primitives",
"@kyoo/models",
"solito",
"react-native",
"react-native-web",
"react-native-svg",
"react-native-reanimated",
"moti",
"yoshiki",
"@expo/vector-icons",
"@expo/html-elements",
"expo-font",
"expo-asset",
"expo-av",
"expo-modules-core",
"expo-linear-gradient",
],
},
};
if (process.env.NODE_ENV !== "production") {
nextConfig.rewrites = async () => [
{
source: "/api/:path*",
destination: `${process.env.KYOO_URL}/:path*` ?? "http://localhost:5000/:path*",
},
];
}
module.exports = nextConfig;

View File

@ -0,0 +1,54 @@
{
"name": "web",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --check --ignore-path .gitignore '!src/utils/jotai-utils.tsx' .",
"format:fix": "prettier --write --ignore-path .gitignore '!src/utils/jotai-utils.tsx' ."
},
"dependencies": {
"@kyoo/models": "workspace:^",
"@kyoo/primitives": "workspace:^",
"@kyoo/ui": "workspace:^",
"@material-symbols/svg-400": "^0.4.2",
"@next/font": "13.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.1",
"@tanstack/react-query": "^4.19.1",
"expo-linear-gradient": "^12.0.1",
"hls.js": "^1.2.8",
"i18next": "^22.0.6",
"jotai": "^1.10.0",
"libass-wasm": "^4.1.0",
"moti": "^0.21.0",
"next": "13.0.5",
"next-translate": "^1.6.0",
"raf": "^3.4.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-i18next": "^12.0.0",
"react-native-reanimated": "^2.13.0",
"react-native-svg": "13.4.0",
"react-native-video": "alpha",
"react-native-web": "^0.18.10",
"solito": "^2.0.5",
"superjson": "^1.11.0",
"yoshiki": "0.4.5",
"zod": "^3.19.1"
},
"devDependencies": {
"@svgr/webpack": "^6.5.1",
"@types/node": "18.11.9",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.9",
"@types/react-native-video": "^5.0.14",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8.30.0",
"eslint-config-next": "13.0.5",
"typescript": "^4.9.3",
"webpack": "^5.75.0"
}
}

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,29 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import "i18next";
import en from "../../../translations/en.json";
declare module "i18next" {
interface CustomTypeOptions {
returnNull: false;
resources: { translations: typeof en };
}
}

View File

@ -0,0 +1,82 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { ComponentType, useMemo } from "react";
import i18next, { InitOptions } from "i18next";
import { I18nextProvider } from "react-i18next";
import { AppContext, AppInitialProps, type AppProps } from "next/app";
import en from "../../../translations/en.json";
import fr from "../../../translations/fr.json";
export const withTranslations = (
AppToTranslate: ComponentType<AppProps> & {
getInitialProps: (ctx: AppContext) => Promise<AppInitialProps>;
},
) => {
const i18n = i18next.createInstance();
const commonOptions: InitOptions = {
interpolation: {
escapeValue: false,
},
};
const AppWithTranslations = (props: AppProps) => {
const li18n = useMemo(
() =>
typeof window === "undefined"
? i18n
: (i18next.init({
...commonOptions,
lng: props.pageProps.__lang,
resources: props.pageProps.__resources,
}),
i18next),
[props.pageProps.__lang, props.pageProps.__resources],
);
return (
<I18nextProvider i18n={li18n}>
<AppToTranslate {...props} />
</I18nextProvider>
);
};
AppWithTranslations.getInitialProps = async (ctx: AppContext) => {
const props: AppInitialProps = await AppToTranslate.getInitialProps(ctx);
const lng = ctx.router.locale || ctx.router.defaultLocale || "en";
// TODO: use a backend to fetch only the needed translations.
// TODO: use a different backend on the client and fetch needed translations.
const resources = {
en: { translation: en },
fr: { translation: fr },
};
await i18n.init({
...commonOptions,
lng,
fallbackLng: ctx.router.defaultLocale || "en",
resources,
});
props.pageProps.__lang = lng;
props.pageProps.__resources = resources;
return props;
};
return AppWithTranslations;
};

View File

@ -18,37 +18,31 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import React, { useState } from "react";
import appWithI18n from "next-translate/appWithI18n";
import { ThemeProvider } from "@mui/material";
import NextApp, { AppContext } from "next/app";
import type { AppProps } from "next/app";
import { Hydrate, QueryClientProvider } from "react-query";
import { createQueryClient, fetchQuery, QueryIdentifier, QueryPage } from "~/utils/query";
import { defaultTheme } from "~/utils/themes/default-theme";
import "../polyfill";
import { Hydrate, QueryClientProvider } from "@tanstack/react-query";
import { HiddenIfNoJs, SkeletonCss, ThemeSelector, WebTooltip } from "@kyoo/primitives";
import { createQueryClient, fetchQuery, QueryIdentifier, QueryPage } from "@kyoo/models";
import { useState } from "react";
import NextApp, { AppContext, type AppProps } from "next/app";
import { Poppins } from "@next/font/google";
import { useTheme, useMobileHover } from "yoshiki/web";
import superjson from "superjson";
import Head from "next/head";
import { useMobileHover } from "~/utils/utils";
import { withTranslations } from "../i18n";
// Simply silence a SSR warning (see https://github.com/facebook/react/issues/14927 for more details)
if (typeof window === "undefined") {
React.useLayoutEffect = React.useEffect;
}
const App = ({ Component, pageProps }: AppProps) => {
const [queryClient] = useState(() => createQueryClient());
const { queryState, ...props } = superjson.deserialize<any>(pageProps ?? {});
const getLayout = (Component as QueryPage).getLayout ?? ((page) => page);
useMobileHover();
const font = Poppins({ weight: ["300", "400", "900"], subsets: ["latin"], display: "swap" });
const GlobalCssTheme = () => {
const theme = useTheme();
return (
<>
<style jsx global>{`
body {
margin: 0px;
padding: 0px;
background-color: ${defaultTheme.palette.background.default};
background-color: ${theme.background};
font-family: ${font.style.fontFamily};
}
*::-webkit-scrollbar {
@ -64,13 +58,48 @@ const App = ({ Component, pageProps }: AppProps) => {
*:hover::-webkit-scrollbar-thumb {
background-color: rgb(134, 127, 127);
}
#__next {
height: 100vh;
}
.infinite-scroll-component__outerdiv {
width: 100%;
height: 100%;
}
::cue {
background-color: transparent;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
`}</style>
<WebTooltip theme={theme} />
<SkeletonCss />
<HiddenIfNoJs />
</>
);
};
const App = ({ Component, pageProps }: AppProps) => {
const [queryClient] = useState(() => createQueryClient());
const { queryState, ...props } = superjson.deserialize<any>(pageProps ?? { json: {} });
const layoutInfo = (Component as QueryPage).getLayout ?? (({ page }) => page);
const { Layout, props: layoutProps } =
typeof layoutInfo === "function" ? { Layout: layoutInfo, props: {} } : layoutInfo;
useMobileHover();
return (
<>
<Head>
<title>Kyoo</title>
</Head>
<QueryClientProvider client={queryClient}>
<Hydrate state={queryState}>
<ThemeProvider theme={defaultTheme}>{getLayout(<Component {...props} />)}</ThemeProvider>
<ThemeSelector theme="auto" font={{ normal: "inherit" }}>
<GlobalCssTheme />
<Layout page={<Component {...props} />} {...layoutProps} />
</ThemeSelector>
</Hydrate>
</QueryClientProvider>
</>
@ -81,21 +110,15 @@ App.getInitialProps = async (ctx: AppContext) => {
const appProps = await NextApp.getInitialProps(ctx);
const getUrl = (ctx.Component as QueryPage).getFetchUrls;
const urls: QueryIdentifier[] = getUrl ? getUrl(ctx.router.query as any) : [];
const getLayoutUrl = ((ctx.Component as QueryPage).getLayout as QueryPage)?.getFetchUrls;
const urls: QueryIdentifier[] = [
...(getUrl ? getUrl(ctx.router.query as any) : []),
...(getLayoutUrl ? getLayoutUrl(ctx.router.query as any) : []),
];
appProps.pageProps.queryState = await fetchQuery(urls);
return { pageProps: superjson.serialize(appProps.pageProps) };
};
// The as any is needed since appWithI18n as wrong type hints
export default appWithI18n(App as any, {
skipInitialProps: false,
locales: ["en", "fr"],
defaultLocale: "en",
loader: false,
pages: {
"*": ["common", "browse", "player"],
},
loadLocaleFrom: (locale, namespace) =>
import(`../../locales/${locale}/${namespace}`).then((m) => m.default),
});
export default withTranslations(App);

View File

@ -0,0 +1,120 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { AppRegistry } from "react-native";
import { Html, Main, Head, NextScript, DocumentContext } from "next/document";
import { createStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
export const style = `
/**
* Building on the RNWeb reset:
* https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js
*/
html, body, #__next {
width: 100%;
/* To smooth any scrolling behavior */
-webkit-overflow-scrolling: touch;
margin: 0px;
padding: 0px;
/* Allows content to fill the viewport and go beyond the bottom */
min-height: 100%;
}
#__next {
flex-shrink: 0;
flex-basis: auto;
flex-direction: column;
flex-grow: 1;
display: flex;
flex: 1;
overflow: hidden;
}
html {
scroll-behavior: smooth;
/* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
-webkit-text-size-adjust: 100%;
height: 100%;
}
body {
display: flex;
/* Allows you to scroll below the viewport; default value is visible */
overflow-y: auto;
overscroll-behavior-y: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-ms-overflow-style: scrollbar;
}
`;
const Document = () => {
return (
<Html>
<Head>
<link rel="icon" type="image/png" sizes="16x16" href="/icon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/icon-32x32.png" />
<link rel="icon" type="image/png" sizes="64x64" href="/icon-64x64.png" />
<link rel="icon" type="image/png" sizes="128x128" href="/icon-128x128.png" />
<link rel="icon" type="image/png" sizes="256x256" href="/icon-256x256.png" />
</Head>
<body className="hoverEnabled">
<Main />
<NextScript />
</body>
</Html>
);
};
Document.getInitialProps = async (ctx: DocumentContext) => {
const renderPage = ctx.renderPage;
const registry = createStyleRegistry();
ctx.renderPage = () =>
renderPage({
enhanceApp: (App) => (props) => {
return (
<StyleRegistryProvider registry={registry}>
<App {...props} />
</StyleRegistryProvider>
);
},
});
const props = await ctx.defaultGetInitialProps(ctx);
AppRegistry.registerComponent("Main", () => Main);
// @ts-ignore React native web missing type.
const { getStyleElement } = AppRegistry.getApplication("Main");
const page = await ctx.renderPage();
return {
...props,
...page,
styles: (
<>
{props.styles}
{page.styles}
<style>{style}</style>
{getStyleElement()}
{registry.flushToComponent()}
</>
),
};
};
export default Document;

View File

@ -0,0 +1,24 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { BrowsePage } from "@kyoo/ui";
import { withRoute } from "~/router";
export default withRoute(BrowsePage);

View File

@ -0,0 +1,24 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { BrowsePage } from "@kyoo/ui";
import { withRoute } from "~/router";
export default withRoute(BrowsePage);

View File

@ -0,0 +1,24 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { MovieDetails } from "@kyoo/ui";
import { withRoute } from "~/router";
export default withRoute(MovieDetails);

View File

@ -0,0 +1,24 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { SearchPage } from "@kyoo/ui";
import { withRoute } from "~/router";
export default withRoute(SearchPage);

View File

@ -0,0 +1,24 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { ShowDetails } from "@kyoo/ui";
import { withRoute } from "~/router";
export default withRoute(ShowDetails);

View File

@ -0,0 +1,24 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { Player } from "@kyoo/ui";
import { withRoute } from "~/router";
export default withRoute(Player);

View File

@ -18,24 +18,15 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { styled, experimental_sx as sx } from "@mui/system";
import "raf/polyfill";
import React from "react";
export const Container = styled("div")(
sx({
display: "flex",
px: "15px",
mx: "auto",
width: {
sm: "540px",
md: "880px",
lg: "1170px",
},
}),
);
export const containerPadding = {
xs: "15px",
sm: "calc((100vw - 540px) / 2)",
md: "calc((100vw - 880px) / 2)",
lg: "calc((100vw - 1170px) / 2)",
};
// FIXME need reanimated update, see https://github.com/software-mansion/react-native-reanimated/issues/3355
if (typeof window !== "undefined") {
// @ts-ignore
window._frameTimestamp = null;
}
// Simply silence a SSR warning (see https://github.com/facebook/react/issues/14927 for more details)
if (typeof window === "undefined") {
React.useLayoutEffect = React.useEffect;
}

View File

@ -25,6 +25,7 @@ export const withRoute = <Props,>(Component: ComponentType<Props>) => {
const WithUseRoute = (props: Props) => {
const router = useRouter();
// @ts-ignore
return <Component {...router.query} {...props} />;
};

View File

@ -13,7 +13,6 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsxImportSource": "@emotion/react",
"incremental": true,
"baseUrl": ".",
"paths": {

View File

@ -1,6 +0,0 @@
{
"navbar": {
"home": "Home",
"login": "Login"
}
}

View File

@ -1,12 +0,0 @@
{
"back": "Back",
"previous": "Previous episode",
"next": "Next episode",
"play": "Play",
"pause": "Pause",
"mute": "Toggle mute",
"volume": "Volume",
"subtitles": "Subtitles",
"subtitle-none": "None",
"fullscreen": "Fullscreen"
}

View File

@ -1,6 +0,0 @@
{
"navbar": {
"home": "Accueil",
"login": "Connexion"
}
}

View File

@ -1,12 +0,0 @@
{
"back": "Retour",
"previous": "Episode précédent",
"next": "Episode suivant",
"play": "Jouer",
"pause": "Pause",
"mute": "Muet",
"volume": "Volume",
"subtitles": "Sous titres",
"subtitle-none": "Aucun",
"fullscreen": "Plein-écran"
}

5
front/next-env.d.ts vendored
View File

@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -1,66 +0,0 @@
/*
* Kyoo - A portable and vast media library solution.
* Copyright (c) Kyoo.
*
* See AUTHORS.md and LICENSE file in the project root for full license information.
*
* Kyoo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Kyoo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
const CopyPlugin = require("copy-webpack-plugin");
/**
* @type {import("next").NextConfig}
*/
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
output: "standalone",
webpack: (config) => {
config.plugins = [
...config.plugins,
new CopyPlugin({
patterns: [
{
context: "node_modules/@jellyfin/libass-wasm/dist/js/",
from: "*",
to: "static/chunks/",
},
],
}),
];
return config;
},
async redirects() {
return [
{
source: "/",
destination: "/browse",
permanent: true,
},
];
},
i18n: {
locales: ["en", "fr"],
defaultLocale: "en",
},
};
if (process.env.NODE_ENV !== "production") {
nextConfig.rewrites = async () => [
{ source: "/api/:path*", destination: process.env.KYOO_URL ?? "http://localhost:5000/:path*" },
];
}
module.exports = nextConfig;

View File

@ -1,15 +1,23 @@
{
"name": "kyoo",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --check --ignore-path .gitignore '!src/utils/jotai-utils.tsx' .",
"format:fix": "prettier --write --ignore-path .gitignore '!src/utils/jotai-utils.tsx' ."
"dev": "yarn workspaces foreach -pi run dev",
"web": "yarn workspace web dev",
"mobile": "yarn workspace mobile dev",
"build:web": "yarn workspace web build",
"build:mobile": "yarn workspace mobile build",
"build:mobile:dev": "yarn workspace mobile build:dev",
"update": "yarn workspace mobile update",
"lint": "eslint ."
},
"eslintIgnore": [
"next-env.d.ts"
],
"workspaces": [
"apps/*",
"packages/*"
],
"prettier": {
"useTabs": true,
"printWidth": 100,
@ -20,34 +28,14 @@
"jsdocSingleLineComment": false,
"tsdoc": true
},
"dependencies": {
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11.9.3",
"@jellyfin/libass-wasm": "^4.1.1",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.8.7",
"hls.js": "^1.2.4",
"jotai": "^1.8.4",
"next": "12.2.2",
"next-translate": "^1.5.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-infinite-scroll-component": "^6.1.0",
"react-query": "^4.0.0-beta.23",
"superjson": "^1.9.1",
"zod": "^3.18.0"
},
"devDependencies": {
"@types/node": "18.0.3",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
"copy-webpack-plugin": "^11.0.0",
"eslint": "8.19.0",
"eslint-config-next": "12.2.2",
"eslint": "8.30.0",
"eslint-config-next": "13.0.5",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-header": "^3.1.1",
"prettier": "^2.7.1",
"prettier-plugin-jsdoc": "^0.3.38",
"typescript": "4.7.4"
}
"prettier": "^2.8.0",
"prettier-plugin-jsdoc": "^0.4.2",
"typescript": "4.9.3"
},
"packageManager": "yarn@3.2.4"
}

View File

@ -0,0 +1,23 @@
{
"name": "@kyoo/models",
"main": "src/index.ts",
"types": "src/index.ts",
"packageManager": "yarn@3.2.4",
"devDependencies": {
"@types/react": "^18.0.25",
"typescript": "^4.9.3"
},
"peerDependencies": {
"@tanstack/react-query": "*",
"react": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
},
"dependencies": {
"zod": "^3.19.1"
}
}

View File

@ -18,7 +18,10 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./resources";
export * from "./traits";
export * from "./page";
export * from "./kyoo-errors";
export * from "./traits";
export * from "./resources";
export * from "./utils"
export * from "./query";

View File

@ -18,28 +18,36 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/
import { ComponentType, ReactElement, ReactNode } from "react";
import { ComponentProps, ComponentType, ReactElement } from "react";
import {
dehydrate,
QueryClient,
QueryFunctionContext,
useInfiniteQuery,
UseInfiniteQueryOptions,
useQuery,
} from "react-query";
} from "@tanstack/react-query";
import { z } from "zod";
import { KyooErrors, Page } from "~/models";
import { Paged } from "~/models/page";
import { KyooErrors } from "./kyoo-errors";
import { Page, Paged } from "./page";
import { Platform } from "react-native";
const queryFn = async <Data>(
const queryFn = async <Data,>(
type: z.ZodType<Data>,
context: QueryFunctionContext,
): Promise<Data> => {
const kyoo_url = process.env.KYOO_URL ?? "http://localhost:5000";
const kyooUrl =
Platform.OS !== "web"
? process.env.PUBLIC_BACK_URL
: typeof window === "undefined"
? process.env.KYOO_URL ?? "http://localhost:5000"
: "/api";
if (!kyooUrl) console.error("Kyoo's url is not defined.");
let resp;
try {
resp = await fetch(
[typeof window === "undefined" ? kyoo_url : "/api"]
[kyooUrl]
.concat(
context.pageParam ? [context.pageParam] : (context.queryKey.filter((x) => x) as string[]),
)
@ -93,24 +101,30 @@ export const createQueryClient = () =>
});
export type QueryIdentifier<T = unknown> = {
parser: z.ZodType<T>;
parser: z.ZodType<T, z.ZodTypeDef, any>;
path: (string | undefined)[];
params?: { [query: string]: boolean | number | string | string[] | undefined };
infinite?: boolean;
/**
* A custom get next function if the infinite query is not a page.
*/
getNext?: (item: unknown) => string | undefined;
};
export type QueryPage<Props = {}> = ComponentType<Props> & {
getFetchUrls?: (route: { [key: string]: string }) => QueryIdentifier[];
getLayout?: (page: ReactElement) => ReactNode;
getLayout?:
| ComponentType<{ page: ReactElement }>
| { Layout: ComponentType<{ page: ReactElement }>; props: object };
};
const toQueryKey = <Data>(query: QueryIdentifier<Data>) => {
const toQueryKey = <Data,>(query: QueryIdentifier<Data>) => {
if (query.params) {
return [
...query.path,
"?" +
Object.entries(query.params)
.filter(([k, v]) => v !== undefined)
.filter(([_, v]) => v !== undefined)
.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`)
.join("&"),
];
@ -119,14 +133,28 @@ const toQueryKey = <Data>(query: QueryIdentifier<Data>) => {
}
};
export const useFetch = <Data>(query: QueryIdentifier<Data>) => {
export const useFetch = <Data,>(query: QueryIdentifier<Data>) => {
return useQuery<Data, KyooErrors>({
queryKey: toQueryKey(query),
queryFn: (ctx) => queryFn(query.parser, ctx),
});
};
export const useInfiniteFetch = <Data>(query: QueryIdentifier<Data>) => {
export const useInfiniteFetch = <Data,>(
query: QueryIdentifier<Data>,
options?: Partial<UseInfiniteQueryOptions<Data[], KyooErrors>>,
) => {
if (query.getNext) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const ret = useInfiniteQuery<Data[], KyooErrors>({
queryKey: toQueryKey(query),
queryFn: (ctx) => queryFn(z.array(query.parser), ctx),
getNextPageParam: query.getNext,
...options,
});
return { ...ret, items: ret.data?.pages.flatMap((x) => x) };
}
// eslint-disable-next-line react-hooks/rules-of-hooks
const ret = useInfiniteQuery<Page<Data>, KyooErrors>({
queryKey: toQueryKey(query),
queryFn: (ctx) => queryFn(Paged(query.parser), ctx),

View File

@ -19,7 +19,7 @@
*/
import { z } from "zod";
import { zdate } from "~/utils/zod";
import { zdate } from "../utils";
import { ImagesP } from "../traits";
import { ResourceP } from "../traits/resource";

View File

@ -28,3 +28,4 @@ export * from "./person";
export * from "./studio";
export * from "./episode";
export * from "./season";
export * from "./watch-item";

View File

@ -34,7 +34,7 @@ export enum ItemType {
export const LibraryItemP = z.preprocess(
(x: any) => {
x.aliases ??= [];
if (!x.aliases) x.aliases = [];
return x;
},
z.union([

View File

@ -19,7 +19,7 @@
*/
import { z } from "zod";
import { zdate } from "~/utils/zod";
import { zdate } from "../utils";
import { ImagesP, ResourceP } from "../traits";
import { GenreP } from "./genre";
import { StudioP } from "./studio";

View File

@ -19,7 +19,7 @@
*/
import { z } from "zod";
import { zdate } from "~/utils/zod";
import { zdate } from "../utils";
import { ImagesP } from "../traits";
import { ResourceP } from "../traits/resource";

View File

@ -19,7 +19,7 @@
*/
import { z } from "zod";
import { zdate } from "~/utils/zod";
import { zdate } from "../utils";
import { ImagesP, ResourceP } from "../traits";
import { GenreP } from "./genre";
import { SeasonP } from "./season";

Some files were not shown because too many files have changed in this diff Show More