mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-13 16:20:59 +03:00
Compare commits
3 Commits
ui-updates
...
docker-hot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4ca1abb7c | ||
|
|
d90434c240 | ||
|
|
7a8e94ee64 |
2
.github/workflows/docker-pr.yml
vendored
2
.github/workflows/docker-pr.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
|
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
||||||
#platforms: linux/amd64
|
#platforms: linux/amd64
|
||||||
push: false
|
push: false
|
||||||
tags: tzahi12345/youtubedl-material:nightly-pr
|
tags: tzahi12345/youtubedl-material:nightly-pr
|
||||||
4
.github/workflows/docker-release.yml
vendored
4
.github/workflows/docker-release.yml
vendored
@@ -80,7 +80,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
tags: ${{ steps.docker-meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
5
.github/workflows/docker.yml
vendored
5
.github/workflows/docker.yml
vendored
@@ -80,7 +80,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
tags: ${{ steps.docker-meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
66
Dockerfile
66
Dockerfile
@@ -1,17 +1,15 @@
|
|||||||
# Fetching our utils
|
# Fetching our ffmpeg
|
||||||
FROM ubuntu:22.04 AS utils
|
FROM ubuntu:22.04 AS ffmpeg
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
# Use script due local build compability
|
# Use script due local build compability
|
||||||
COPY docker-utils/*.sh .
|
COPY docker-utils/ffmpeg-fetch.sh .
|
||||||
RUN chmod +x *.sh
|
RUN chmod +x ffmpeg-fetch.sh
|
||||||
RUN sh ./ffmpeg-fetch.sh
|
RUN sh ./ffmpeg-fetch.sh
|
||||||
RUN sh ./fetch-twitchdownloader.sh
|
|
||||||
|
|
||||||
|
|
||||||
# Create our Ubuntu 22.04 with node 16.14.2 (that specific version is required as per: https://stackoverflow.com/a/72855258/8088021)
|
# Create our Ubuntu 22.04 with node 16.14.2 (that specific version is required as per: https://stackoverflow.com/a/72855258/8088021)
|
||||||
# Go to 20.04
|
# Go to 20.04
|
||||||
FROM ubuntu:22.04 AS base
|
FROM ubuntu:20.04 AS base
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ENV UID=1000
|
ENV UID=1000
|
||||||
ENV GID=1000
|
ENV GID=1000
|
||||||
@@ -19,30 +17,19 @@ ENV USER=youtube
|
|||||||
ENV NO_UPDATE_NOTIFIER=true
|
ENV NO_UPDATE_NOTIFIER=true
|
||||||
ENV PM2_HOME=/app/pm2
|
ENV PM2_HOME=/app/pm2
|
||||||
ENV ALLOW_CONFIG_MUTATIONS=true
|
ENV ALLOW_CONFIG_MUTATIONS=true
|
||||||
# Directy fetch specific version
|
|
||||||
## https://deb.nodesource.com/node_16.x/pool/main/n/nodejs/nodejs_16.14.2-deb-1nodesource1_amd64.deb
|
|
||||||
RUN groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER && \
|
RUN groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER && \
|
||||||
apt update && \
|
apt update && \
|
||||||
apt install -y --no-install-recommends curl ca-certificates tzdata libicu70 && \
|
apt install -y --no-install-recommends curl ca-certificates tzdata && \
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
|
||||||
|
apt install -y --no-install-recommends nodejs && \
|
||||||
|
npm -g install npm n && \
|
||||||
|
n 16.14.2 && \
|
||||||
apt clean && \
|
apt clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
RUN case ${TARGETPLATFORM} in \
|
|
||||||
"linux/amd64") NODE_ARCH=amd64 ;; \
|
|
||||||
"linux/arm") NODE_ARCH=armhf ;; \
|
|
||||||
"linux/arm/v7") NODE_ARCH=armhf ;; \
|
|
||||||
"linux/arm64") NODE_ARCH=arm64 ;; \
|
|
||||||
esac \
|
|
||||||
&& curl -L https://deb.nodesource.com/node_16.x/pool/main/n/nodejs/nodejs_16.14.2-deb-1nodesource1_$NODE_ARCH.deb -o ./nodejs.deb && \
|
|
||||||
apt update && \
|
|
||||||
apt install -y ./nodejs.deb && \
|
|
||||||
apt clean && \
|
|
||||||
rm -rf /var/lib/apt/lists/* &&\
|
|
||||||
rm nodejs.deb;
|
|
||||||
|
|
||||||
|
|
||||||
# Build frontend
|
# Build frontend
|
||||||
ARG BUILDPLATFORM
|
FROM base as frontend
|
||||||
FROM --platform=${BUILDPLATFORM} node:16 as frontend
|
|
||||||
RUN npm install -g @angular/cli
|
RUN npm install -g @angular/cli
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY [ "package.json", "package-lock.json", "angular.json", "tsconfig.json", "/build/" ]
|
COPY [ "package.json", "package-lock.json", "angular.json", "tsconfig.json", "/build/" ]
|
||||||
@@ -62,35 +49,32 @@ RUN npm config set strict-ssl false && \
|
|||||||
npm install --prod && \
|
npm install --prod && \
|
||||||
ls -al
|
ls -al
|
||||||
|
|
||||||
#FROM base as python
|
FROM base as python
|
||||||
# armv7 need build from source
|
WORKDIR /app
|
||||||
#WORKDIR /app
|
COPY docker-utils/GetTwitchDownloader.py .
|
||||||
#COPY docker-utils/GetTwitchDownloader.py .
|
RUN apt update && \
|
||||||
#RUN apt update && \
|
apt install -y --no-install-recommends python3-minimal python-is-python3 python3-pip python3.8-dev build-essential && \
|
||||||
# apt install -y --no-install-recommends python3-minimal python-is-python3 python3-pip python3-dev build-essential libffi-dev && \
|
apt clean && \
|
||||||
# apt clean && \
|
rm -rf /var/lib/apt/lists/*
|
||||||
# rm -rf /var/lib/apt/lists/*
|
RUN pip install PyGithub requests
|
||||||
#RUN pip install PyGithub requests
|
RUN python GetTwitchDownloader.py
|
||||||
#RUN python GetTwitchDownloader.py
|
|
||||||
|
|
||||||
# Final image
|
# Final image
|
||||||
FROM base
|
FROM base
|
||||||
RUN npm install -g pm2 && \
|
RUN npm install -g pm2 && \
|
||||||
apt update && \
|
apt update && \
|
||||||
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley build-essential && \
|
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley build-essential && \
|
||||||
pip install pycryptodomex && \
|
|
||||||
apt remove -y --purge build-essential && \
|
|
||||||
apt autoremove -y --purge && \
|
|
||||||
apt clean && \
|
apt clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN pip install pycryptodomex
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# User 1000 already exist from base image
|
# User 1000 already exist from base image
|
||||||
COPY --chown=$UID:$GID --from=utils [ "/usr/local/bin/ffmpeg", "/usr/local/bin/ffmpeg" ]
|
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffmpeg", "/usr/local/bin/ffmpeg" ]
|
||||||
COPY --chown=$UID:$GID --from=utils [ "/usr/local/bin/ffprobe", "/usr/local/bin/ffprobe" ]
|
COPY --chown=$UID:$GID --from=ffmpeg [ "/usr/local/bin/ffprobe", "/usr/local/bin/ffprobe" ]
|
||||||
COPY --chown=$UID:$GID --from=utils [ "/usr/local/bin/TwitchDownloaderCLI", "/usr/local/bin/TwitchDownloaderCLI"]
|
|
||||||
COPY --chown=$UID:$GID --from=backend ["/app/","/app/"]
|
COPY --chown=$UID:$GID --from=backend ["/app/","/app/"]
|
||||||
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
||||||
#COPY --chown=$UID:$GID --from=python ["/app/TwitchDownloaderCLI","/usr/local/bin/TwitchDownloaderCLI"]
|
COPY --chown=$UID:$GID --from=python ["/app/TwitchDownloaderCLI","/usr/local/bin/TwitchDownloaderCLI"]
|
||||||
|
RUN chown $UID:$GID .
|
||||||
RUN chmod +x /app/fix-scripts/*.sh
|
RUN chmod +x /app/fix-scripts/*.sh
|
||||||
# Add some persistence data
|
# Add some persistence data
|
||||||
#VOLUME ["/app/appdata"]
|
#VOLUME ["/app/appdata"]
|
||||||
|
|||||||
@@ -2742,7 +2742,7 @@ components:
|
|||||||
error:
|
error:
|
||||||
type: string
|
type: string
|
||||||
schedule:
|
schedule:
|
||||||
$ref: '#/components/schemas/Schedule'
|
type: object
|
||||||
options:
|
options:
|
||||||
type: object
|
type: object
|
||||||
Schedule:
|
Schedule:
|
||||||
@@ -2877,7 +2877,6 @@ components:
|
|||||||
- sharing
|
- sharing
|
||||||
- advanced_download
|
- advanced_download
|
||||||
- downloads_manager
|
- downloads_manager
|
||||||
- tasks_manager
|
|
||||||
YesNo:
|
YesNo:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
|
|||||||
@@ -68,7 +68,14 @@ exports.initialize = function () {
|
|||||||
const setupRoles = async () => {
|
const setupRoles = async () => {
|
||||||
const required_roles = {
|
const required_roles = {
|
||||||
admin: {
|
admin: {
|
||||||
permissions: consts.AVAILABLE_PERMISSIONS
|
permissions: [
|
||||||
|
'filemanager',
|
||||||
|
'settings',
|
||||||
|
'subscriptions',
|
||||||
|
'sharing',
|
||||||
|
'advanced_download',
|
||||||
|
'downloads_manager'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
permissions: [
|
permissions: [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
CMD="npm start && pm2 start"
|
CMD="npm start"
|
||||||
|
|
||||||
# if the first arg starts with "-" pass it to program
|
# if the first arg starts with "-" pass it to program
|
||||||
if [ "${1#-}" != "$1" ]; then
|
if [ "${1#-}" != "$1" ]; then
|
||||||
@@ -10,7 +10,7 @@ fi
|
|||||||
|
|
||||||
# chown current working directory to current user
|
# chown current working directory to current user
|
||||||
if [ "$*" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
|
if [ "$*" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
|
||||||
find . \! -user "$UID" -exec chown "$UID:$GID" -R '{}' + || echo "WARNING! Could not change directory ownership. If you manage permissions externally this is fine, otherwise you may experience issues when downloading or deleting videos."
|
find . \! -user "$UID" -exec chown "$UID:$GID" '{}' + || echo "WARNING! Could not change directory ownership. If you manage permissions externally this is fine, otherwise you may experience issues when downloading or deleting videos."
|
||||||
exec gosu "$UID:$GID" "$0" "$@"
|
exec gosu "$UID:$GID" "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
5
backend/package-lock.json
generated
5
backend/package-lock.json
generated
@@ -769,11 +769,6 @@
|
|||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"command-exists": {
|
|
||||||
"version": "1.2.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
|
|
||||||
"integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="
|
|
||||||
},
|
|
||||||
"compress-commons": {
|
"compress-commons": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz",
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
"async-mutex": "^0.4.0",
|
"async-mutex": "^0.4.0",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
"bcryptjs": "^2.4.0",
|
"bcryptjs": "^2.4.0",
|
||||||
"command-exists": "^1.2.9",
|
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"config": "^3.2.3",
|
"config": "^3.2.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ const fs = require('fs-extra')
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { promisify } = require('util');
|
const { promisify } = require('util');
|
||||||
const child_process = require('child_process');
|
const child_process = require('child_process');
|
||||||
const commandExistsSync = require('command-exists').sync;
|
|
||||||
|
|
||||||
async function getCommentsForVOD(vodId) {
|
async function getCommentsForVOD(vodId) {
|
||||||
const exec = promisify(child_process.exec);
|
const exec = promisify(child_process.exec);
|
||||||
@@ -21,7 +20,7 @@ async function getCommentsForVOD(vodId) {
|
|||||||
const cliExt = is_windows ? '.exe' : ''
|
const cliExt = is_windows ? '.exe' : ''
|
||||||
const cliPath = `TwitchDownloaderCLI${cliExt}`
|
const cliPath = `TwitchDownloaderCLI${cliExt}`
|
||||||
|
|
||||||
if (!commandExistsSync(cliPath)) {
|
if (!fs.existsSync(cliPath)) {
|
||||||
logger.error(`${cliPath} does not exist. Twitch chat download failed! Get it here: https://github.com/lay295/TwitchDownloader`);
|
logger.error(`${cliPath} does not exist. Twitch chat download failed! Get it here: https://github.com/lay295/TwitchDownloader`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ def getZipName():
|
|||||||
|
|
||||||
def getLatestFileInRepo(repo, search_string):
|
def getLatestFileInRepo(repo, search_string):
|
||||||
# Create an unauthenticated instance of the Github object
|
# Create an unauthenticated instance of the Github object
|
||||||
g = Github(os.environ.get('GH_TOKEN'))
|
gh_token = os.environ.get('GH_TOKEN')
|
||||||
|
g = Github(gh_token if gh_token else None) # ensure it's none if it's falsy
|
||||||
|
|
||||||
# Replace with the repository owner and name
|
# Replace with the repository owner and name
|
||||||
repo = g.get_repo(repo)
|
repo = g.get_repo(repo)
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# THANK YOU TALULAH (https://github.com/nottalulah) for your help in figuring this out
|
|
||||||
# and also optimizing some code with this commit.
|
|
||||||
# xoxo :D
|
|
||||||
|
|
||||||
case $(uname -m) in
|
|
||||||
x86_64)
|
|
||||||
ARCH=Linux-x64;;
|
|
||||||
aarch64)
|
|
||||||
ARCH=LinuxArm64;;
|
|
||||||
armhf)
|
|
||||||
ARCH=LinuxArm;;
|
|
||||||
armv7)
|
|
||||||
ARCH=LinuxArm;;
|
|
||||||
armv7l)
|
|
||||||
ARCH=LinuxArm;;
|
|
||||||
*)
|
|
||||||
echo "Unsupported architecture: $(uname -m)"
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "(INFO) Architecture detected: $ARCH"
|
|
||||||
echo "(1/5) READY - Install unzip"
|
|
||||||
apt-get update && apt-get -y install unzip curl jq libicu70
|
|
||||||
VERSION=$(curl --silent "https://api.github.com/repos/lay295/TwitchDownloader/releases" | jq -r --arg arch "$ARCH" '[.[] | select(.assets | length > 0) | select(.assets[].name | contains("CLI") and contains($arch))] | max_by(.published_at) | .tag_name')
|
|
||||||
echo "(2/5) DOWNLOAD - Acquire twitchdownloader"
|
|
||||||
curl -o twitchdownloader.zip \
|
|
||||||
--connect-timeout 5 \
|
|
||||||
--max-time 120 \
|
|
||||||
--retry 5 \
|
|
||||||
--retry-delay 0 \
|
|
||||||
--retry-max-time 40 \
|
|
||||||
-L "https://github.com/lay295/TwitchDownloader/releases/download/$VERSION/TwitchDownloaderCLI-$VERSION-$ARCH.zip"
|
|
||||||
unzip twitchdownloader.zip
|
|
||||||
chmod +x TwitchDownloaderCLI
|
|
||||||
echo "(3/5) Smoke test"
|
|
||||||
./TwitchDownloaderCLI --help
|
|
||||||
cp ./TwitchDownloaderCLI /usr/local/bin/TwitchDownloaderCLI
|
|
||||||
@@ -30,7 +30,7 @@ curl -o ffmpeg.txz \
|
|||||||
--retry 5 \
|
--retry 5 \
|
||||||
--retry-delay 0 \
|
--retry-delay 0 \
|
||||||
--retry-max-time 40 \
|
--retry-max-time 40 \
|
||||||
"https://johnvansickle.com/ffmpeg/old-releases/ffmpeg-5.1.1-${ARCH}-static.tar.xz"
|
"https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-${ARCH}-static.tar.xz"
|
||||||
mkdir /tmp/ffmpeg
|
mkdir /tmp/ffmpeg
|
||||||
tar xf ffmpeg.txz -C /tmp/ffmpeg
|
tar xf ffmpeg.txz -C /tmp/ffmpeg
|
||||||
echo "(3/5) CLEANUP - Remove temp dependencies from ffmpeg obtain layer"
|
echo "(3/5) CLEANUP - Remove temp dependencies from ffmpeg obtain layer"
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export type Config = {
|
export type Config = {
|
||||||
YoutubeDLMaterial: Record<string, any>;
|
YoutubeDLMaterial: any;
|
||||||
};
|
};
|
||||||
@@ -26,5 +26,5 @@ export type Download = {
|
|||||||
user_uid?: string;
|
user_uid?: string;
|
||||||
sub_id?: string;
|
sub_id?: string;
|
||||||
sub_name?: string;
|
sub_name?: string;
|
||||||
prefetched_info?: Record<string, any>;
|
prefetched_info?: any;
|
||||||
};
|
};
|
||||||
@@ -5,6 +5,6 @@
|
|||||||
export type GetFileFormatsResponse = {
|
export type GetFileFormatsResponse = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
result: {
|
result: {
|
||||||
formats?: Array<Record<string, any>>;
|
formats?: Array<any>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -6,5 +6,5 @@ import type { Subscription } from './Subscription';
|
|||||||
|
|
||||||
export type GetSubscriptionResponse = {
|
export type GetSubscriptionResponse = {
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
files: Array<Record<string, any>>;
|
files: Array<any>;
|
||||||
};
|
};
|
||||||
@@ -11,6 +11,6 @@ export type Notification = {
|
|||||||
user_uid?: string;
|
user_uid?: string;
|
||||||
action?: Array<NotificationAction>;
|
action?: Array<NotificationAction>;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
data?: Record<string, any>;
|
data?: any;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
};
|
};
|
||||||
@@ -15,5 +15,5 @@ export type Subscription = {
|
|||||||
timerange?: string;
|
timerange?: string;
|
||||||
custom_args?: string;
|
custom_args?: string;
|
||||||
custom_output?: string;
|
custom_output?: string;
|
||||||
videos: Array<Record<string, any>>;
|
videos: Array<any>;
|
||||||
};
|
};
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
import type { Schedule } from './Schedule';
|
|
||||||
|
|
||||||
export type Task = {
|
export type Task = {
|
||||||
key: string;
|
key: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -11,8 +9,8 @@ export type Task = {
|
|||||||
last_confirmed: number;
|
last_confirmed: number;
|
||||||
running: boolean;
|
running: boolean;
|
||||||
confirming: boolean;
|
confirming: boolean;
|
||||||
data: Record<string, any>;
|
data: any;
|
||||||
error: string;
|
error: string;
|
||||||
schedule: Schedule;
|
schedule: any;
|
||||||
options?: Record<string, any>;
|
options?: any;
|
||||||
};
|
};
|
||||||
@@ -10,5 +10,5 @@ export type UpdateFileRequest = {
|
|||||||
/**
|
/**
|
||||||
* Object with fields to update as keys and their new values
|
* Object with fields to update as keys and their new values
|
||||||
*/
|
*/
|
||||||
change_obj: Record<string, any>;
|
change_obj: any;
|
||||||
};
|
};
|
||||||
@@ -4,5 +4,5 @@
|
|||||||
|
|
||||||
export type UpdateTaskDataRequest = {
|
export type UpdateTaskDataRequest = {
|
||||||
task_key: string;
|
task_key: string;
|
||||||
new_data: Record<string, any>;
|
new_data: any;
|
||||||
};
|
};
|
||||||
@@ -4,5 +4,5 @@
|
|||||||
|
|
||||||
export type UpdateTaskOptionsRequest = {
|
export type UpdateTaskOptionsRequest = {
|
||||||
task_key: string;
|
task_key: string;
|
||||||
new_options: Record<string, any>;
|
new_options: any;
|
||||||
};
|
};
|
||||||
@@ -9,5 +9,4 @@ export enum UserPermission {
|
|||||||
SHARING = 'sharing',
|
SHARING = 'sharing',
|
||||||
ADVANCED_DOWNLOAD = 'advanced_download',
|
ADVANCED_DOWNLOAD = 'advanced_download',
|
||||||
DOWNLOADS_MANAGER = 'downloads_manager',
|
DOWNLOADS_MANAGER = 'downloads_manager',
|
||||||
TASKS_MANAGER = 'tasks_manager',
|
|
||||||
}
|
}
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
</mat-menu>
|
</mat-menu>
|
||||||
<button [matMenuTriggerFor]="menuSettings" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
<button [matMenuTriggerFor]="menuSettings" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
||||||
<mat-menu #menuSettings="matMenu">
|
<mat-menu #menuSettings="matMenu">
|
||||||
<button class="top-menu-button" (click)="openProfileDialog()" mat-menu-item>
|
<button class="top-menu-button" (click)="openProfileDialog()" *ngIf="postsService.isLoggedIn" mat-menu-item>
|
||||||
<mat-icon>person</mat-icon>
|
<mat-icon>person</mat-icon>
|
||||||
<span i18n="Profile menu label">Profile</span>
|
<span i18n="Profile menu label">Profile</span>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="!postsService.config?.Advanced.multi_user_mode || postsService.isLoggedIn" class="top-menu-button" (click)="openArchivesDialog()" mat-menu-item>
|
<button class="top-menu-button" (click)="openArchivesDialog()" mat-menu-item>
|
||||||
<mat-icon>topic</mat-icon>
|
<mat-icon>topic</mat-icon>
|
||||||
<span i18n="Archives menu label">Archives</span>
|
<span i18n="Archives menu label">Archives</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
this.postsService.open_create_default_admin_dialog.subscribe(open => {
|
this.postsService.open_create_default_admin_dialog.subscribe(open => {
|
||||||
if (open) {
|
if (open) {
|
||||||
const dialogRef = this.dialog.open(SetDefaultAdminDialogComponent);
|
const dialogRef = this.dialog.open(SetDefaultAdminDialogComponent);
|
||||||
dialogRef.afterClosed().subscribe(res => {
|
dialogRef.afterClosed().subscribe(success => {
|
||||||
if (!res || !res['user']) {
|
if (success) {
|
||||||
if (this.router.url !== '/login') { this.router.navigate(['/login']); }
|
if (this.router.url !== '/login') { this.router.navigate(['/login']); }
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to create default admin account. See logs for details.');
|
console.error('Failed to create default admin account. See logs for details.');
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import { MatBadgeModule } from '@angular/material/badge';
|
|||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
import { ClipboardModule } from '@angular/cdk/clipboard';
|
import { ClipboardModule } from '@angular/cdk/clipboard';
|
||||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
@@ -190,7 +189,6 @@ registerLocaleData(es, 'es');
|
|||||||
DragDropModule,
|
DragDropModule,
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
TextFieldModule,
|
TextFieldModule,
|
||||||
ScrollingModule,
|
|
||||||
NgxFileDropModule,
|
NgxFileDropModule,
|
||||||
AvatarModule,
|
AvatarModule,
|
||||||
ContentLoaderModule,
|
ContentLoaderModule,
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
<!-- Title Column -->
|
<!-- Title Column -->
|
||||||
<ng-container matColumnDef="title">
|
<ng-container matColumnDef="title">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header style="flex: 2"> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let element" style="flex: 2">
|
<mat-cell *matCellDef="let element">
|
||||||
<span class="one-line" [matTooltip]="element.title ? element.title : null">
|
<span class="one-line" [matTooltip]="element.title ? element.title : null">
|
||||||
{{element.title}}
|
{{element.title}}
|
||||||
</span>
|
</span>
|
||||||
@@ -31,47 +31,41 @@
|
|||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Stage Column -->
|
||||||
|
<ng-container matColumnDef="step_index">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Stage">Stage</ng-container> </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let element"> {{STEP_INDEX_TO_LABEL[element.step_index]}} </mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Progress Column -->
|
<!-- Progress Column -->
|
||||||
<ng-container matColumnDef="percent_complete">
|
<ng-container matColumnDef="percent_complete">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Progress">Progress</ng-container> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Progress">Progress</ng-container> </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let element">
|
<mat-cell *matCellDef="let element">
|
||||||
<ng-container *ngIf="!element.error && element.step_index !== 2">
|
|
||||||
{{STEP_INDEX_TO_LABEL[element.step_index]}}
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!element.error && element.step_index === 2">
|
|
||||||
<ng-container *ngIf="element.percent_complete">
|
<ng-container *ngIf="element.percent_complete">
|
||||||
{{+(element.percent_complete) > 100 ? '100' : element.percent_complete}}%
|
{{+(element.percent_complete) > 100 ? '100' : element.percent_complete}}%
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!element.percent_complete">
|
<ng-container *ngIf="!element.percent_complete">
|
||||||
N/A
|
N/A
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="element.error" i18n="Error">Error</ng-container>
|
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Actions Column -->
|
<!-- Actions Column -->
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<mat-header-cell *matHeaderCellDef [ngStyle]="{flex: actionsFlex}"> <ng-container i18n="Actions">Actions</ng-container> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Actions">Actions</ng-container> </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let element" [ngStyle]="{flex: actionsFlex}">
|
<mat-cell *matCellDef="let element">
|
||||||
<div *ngIf="!minimizeButtons">
|
<div>
|
||||||
<ng-container *ngFor="let downloadAction of downloadActions">
|
<ng-container *ngIf="!element.finished">
|
||||||
<span class="button-span">
|
<button (click)="pauseDownload(element.uid)" *ngIf="!element.paused || !element.finished_step" [disabled]="element.paused && !element.finished_step" mat-icon-button matTooltip="Pause" i18n-matTooltip="Pause"><mat-spinner [diameter]="28" *ngIf="element.paused && !element.finished_step" class="icon-button-spinner"></mat-spinner><mat-icon>pause</mat-icon></button>
|
||||||
<mat-spinner [diameter]="28" *ngIf="downloadAction.loading && downloadAction.loading(element)" class="icon-button-spinner"></mat-spinner>
|
<button (click)="resumeDownload(element.uid)" *ngIf="element.paused && element.finished_step" mat-icon-button matTooltip="Resume" i18n-matTooltip="Resume"><mat-icon>play_arrow</mat-icon></button>
|
||||||
<button *ngIf="downloadAction.show(element)" (click)="downloadAction.action(element)" [disabled]="downloadAction.loading && downloadAction.loading(element)" [matTooltip]="downloadAction.tooltip" mat-icon-button><mat-icon>{{downloadAction.icon}}</mat-icon></button>
|
<button *ngIf="false && !element.paused" (click)="cancelDownload(element.uid)" mat-icon-button matTooltip="Cancel" i18n-matTooltip="Cancel"><mat-icon>cancel</mat-icon></button>
|
||||||
</span>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
<ng-container *ngIf="element.finished">
|
||||||
<div *ngIf="minimizeButtons">
|
<button *ngIf="!element.error" (click)="watchContent(element)" mat-icon-button matTooltip="Watch content" i18n-matTooltip="Watch content"><mat-icon>smart_display</mat-icon></button>
|
||||||
<button [matMenuTriggerFor]="download_actions" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
<button *ngIf="element.error" (click)="showError(element)" mat-icon-button matTooltip="Show error" i18n-matTooltip="Show error"><mat-icon>warning</mat-icon></button>
|
||||||
<mat-menu #download_actions="matMenu">
|
<button (click)="restartDownload(element.uid)" mat-icon-button matTooltip="Restart" i18n-matTooltip="Restart"><mat-icon>restart_alt</mat-icon></button>
|
||||||
<ng-container *ngFor="let downloadAction of downloadActions">
|
</ng-container>
|
||||||
<button *ngIf="downloadAction.show(element)" (click)="downloadAction.action(element)" [disabled]="downloadAction.loading && downloadAction.loading(element)" mat-menu-item>
|
<button *ngIf="element.finished || element.paused" (click)="clearDownload(element.uid)" mat-icon-button matTooltip="Clear" i18n-matTooltip="Clear"><mat-icon>delete</mat-icon></button>
|
||||||
<mat-icon>{{downloadAction.icon}}</mat-icon>
|
|
||||||
<span>{{downloadAction.tooltip}}</span>
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
</div>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -86,9 +80,9 @@
|
|||||||
</mat-paginator>
|
</mat-paginator>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!uids" class="downloads-action-button-div">
|
<div *ngIf="!uids" class="downloads-action-button-div">
|
||||||
<button class="downloads-action-button" [disabled]="!running_download_exists" mat-stroked-button (click)="pauseAllDownloads()"><ng-container i18n="Pause all downloads">Pause all downloads</ng-container></button>
|
<button [disabled]="!running_download_exists" mat-stroked-button (click)="pauseAllDownloads()"><ng-container i18n="Pause all downloads">Pause all downloads</ng-container></button>
|
||||||
<button class="downloads-action-button" [disabled]="!paused_download_exists" mat-stroked-button (click)="resumeAllDownloads()"><ng-container i18n="Resume all downloads">Resume all downloads</ng-container></button>
|
<button style="margin-left: 10px;" [disabled]="!paused_download_exists" mat-stroked-button (click)="resumeAllDownloads()"><ng-container i18n="Resume all downloads">Resume all downloads</ng-container></button>
|
||||||
<button class="downloads-action-button" color="warn" mat-stroked-button (click)="clearDownloadsByType()"><ng-container i18n="Clear downloads">Clear downloads</ng-container></button>
|
<button color="warn" style="margin-left: 10px;" mat-stroked-button (click)="clearDownloadsByType()"><ng-container i18n="Clear downloads">Clear downloads</ng-container></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,21 +10,13 @@ mat-header-cell, mat-cell {
|
|||||||
|
|
||||||
.icon-button-spinner {
|
.icon-button-spinner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -13px;
|
top: 7px;
|
||||||
left: 10px;
|
left: 6px;
|
||||||
}
|
|
||||||
|
|
||||||
.button-span {
|
|
||||||
position: relative;;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloads-action-button-div {
|
.downloads-action-button-div {
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.downloads-action-button {
|
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-right: 10px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded-top {
|
.rounded-top {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewChild, Input, EventEmitter, HostListener } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild, Input, EventEmitter } from '@angular/core';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
import { trigger, transition, animateChild, stagger, query, style, animate } from '@angular/animations';
|
import { trigger, transition, animateChild, stagger, query, style, animate } from '@angular/animations';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@@ -13,7 +13,31 @@ import { Download } from 'api-types';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-downloads',
|
selector: 'app-downloads',
|
||||||
templateUrl: './downloads.component.html',
|
templateUrl: './downloads.component.html',
|
||||||
styleUrls: ['./downloads.component.scss']
|
styleUrls: ['./downloads.component.scss'],
|
||||||
|
animations: [
|
||||||
|
// nice stagger effect when showing existing elements
|
||||||
|
trigger('list', [
|
||||||
|
transition(':enter', [
|
||||||
|
// child animation selector + stagger
|
||||||
|
query('@items',
|
||||||
|
stagger(100, animateChild()), { optional: true }
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
trigger('items', [
|
||||||
|
// cubic-bezier for a tiny bouncing feel
|
||||||
|
transition(':enter', [
|
||||||
|
style({ transform: 'scale(0.5)', opacity: 0 }),
|
||||||
|
animate('500ms cubic-bezier(.8,-0.6,0.2,1.5)',
|
||||||
|
style({ transform: 'scale(1)', opacity: 1 }))
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
style({ transform: 'scale(1)', opacity: 1, height: '*' }),
|
||||||
|
animate('1s cubic-bezier(.8,-0.6,0.2,1.5)',
|
||||||
|
style({ transform: 'scale(0.5)', opacity: 0, height: '0px', margin: '0px' }))
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class DownloadsComponent implements OnInit, OnDestroy {
|
export class DownloadsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@@ -38,79 +62,13 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
3: $localize`Complete`
|
3: $localize`Complete`
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsFlex = 2;
|
displayedColumns: string[] = ['timestamp_start', 'title', 'step_index', 'sub_name', 'percent_complete', 'actions'];
|
||||||
minimizeButtons = false;
|
|
||||||
displayedColumnsBig: string[] = ['timestamp_start', 'title', 'sub_name', 'percent_complete', 'actions'];
|
|
||||||
displayedColumnsSmall: string[] = ['title', 'percent_complete', 'actions'];
|
|
||||||
displayedColumns: string[] = this.displayedColumnsBig;
|
|
||||||
dataSource = null; // new MatTableDataSource<Download>();
|
dataSource = null; // new MatTableDataSource<Download>();
|
||||||
|
|
||||||
// The purpose of this is to reduce code reuse for displaying these actions as icons or in a menu
|
|
||||||
downloadActions: DownloadAction[] = [
|
|
||||||
{
|
|
||||||
tooltip: $localize`Watch content`,
|
|
||||||
action: (download: Download) => this.watchContent(download),
|
|
||||||
show: (download: Download) => download.finished && !download.error,
|
|
||||||
icon: 'smart_display'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: $localize`Show error`,
|
|
||||||
action: (download: Download) => this.showError(download),
|
|
||||||
show: (download: Download) => download.finished && !!download.error,
|
|
||||||
icon: 'warning'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: $localize`Restart`,
|
|
||||||
action: (download: Download) => this.restartDownload(download),
|
|
||||||
show: (download: Download) => download.finished,
|
|
||||||
icon: 'restart_alt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: $localize`Pause`,
|
|
||||||
action: (download: Download) => this.pauseDownload(download),
|
|
||||||
show: (download: Download) => !download.finished && (!download.paused || !download.finished_step),
|
|
||||||
icon: 'pause',
|
|
||||||
loading: (download: Download) => download.paused && !download.finished_step
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: $localize`Resume`,
|
|
||||||
action: (download: Download) => this.resumeDownload(download),
|
|
||||||
show: (download: Download) => !download.finished && download.paused && download.finished_step,
|
|
||||||
icon: 'play_arrow'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: $localize`Resume`,
|
|
||||||
action: (download: Download) => this.resumeDownload(download),
|
|
||||||
show: (download: Download) => !download.finished && download.paused && download.finished_step,
|
|
||||||
icon: 'play_arrow'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: $localize`Cancel`,
|
|
||||||
action: (download: Download) => this.cancelDownload(download),
|
|
||||||
show: (download: Download) => false && !download.finished && !download.paused, // TODO: add possibility to cancel download
|
|
||||||
icon: 'cancel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: $localize`Clear`,
|
|
||||||
action: (download: Download) => this.clearDownload(download),
|
|
||||||
show: (download: Download) => download.finished || download.paused,
|
|
||||||
icon: 'delete'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
downloads_retrieved = false;
|
downloads_retrieved = false;
|
||||||
|
|
||||||
innerWidth: number;
|
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
|
||||||
onResize(): void {
|
|
||||||
this.innerWidth = window.innerWidth;
|
|
||||||
this.recalculateColumns();
|
|
||||||
}
|
|
||||||
|
|
||||||
sort_downloads = (a: Download, b: Download): number => {
|
sort_downloads = (a: Download, b: Download): number => {
|
||||||
const result = b.timestamp_start - a.timestamp_start;
|
const result = b.timestamp_start - a.timestamp_start;
|
||||||
return result;
|
return result;
|
||||||
@@ -119,10 +77,6 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
constructor(public postsService: PostsService, private router: Router, private dialog: MatDialog, private clipboard: Clipboard) { }
|
constructor(public postsService: PostsService, private router: Router, private dialog: MatDialog, private clipboard: Clipboard) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Remove sub name as it's not necessary for one-off downloads
|
|
||||||
if (this.uids) this.displayedColumnsBig = this.displayedColumnsBig.filter(col => col !== 'sub_name');
|
|
||||||
this.innerWidth = window.innerWidth;
|
|
||||||
this.recalculateColumns();
|
|
||||||
if (this.postsService.initialized) {
|
if (this.postsService.initialized) {
|
||||||
this.getCurrentDownloadsRecurring();
|
this.getCurrentDownloadsRecurring();
|
||||||
} else {
|
} else {
|
||||||
@@ -210,8 +164,8 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseDownload(download: Download): void {
|
pauseDownload(download_uid: string): void {
|
||||||
this.postsService.pauseDownload(download['uid']).subscribe(res => {
|
this.postsService.pauseDownload(download_uid).subscribe(res => {
|
||||||
if (!res['success']) {
|
if (!res['success']) {
|
||||||
this.postsService.openSnackBar($localize`Failed to pause download! See server logs for more info.`);
|
this.postsService.openSnackBar($localize`Failed to pause download! See server logs for more info.`);
|
||||||
}
|
}
|
||||||
@@ -226,8 +180,8 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
resumeDownload(download: Download): void {
|
resumeDownload(download_uid: string): void {
|
||||||
this.postsService.resumeDownload(download['uid']).subscribe(res => {
|
this.postsService.resumeDownload(download_uid).subscribe(res => {
|
||||||
if (!res['success']) {
|
if (!res['success']) {
|
||||||
this.postsService.openSnackBar($localize`Failed to resume download! See server logs for more info.`);
|
this.postsService.openSnackBar($localize`Failed to resume download! See server logs for more info.`);
|
||||||
}
|
}
|
||||||
@@ -242,8 +196,8 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
restartDownload(download: Download): void {
|
restartDownload(download_uid: string): void {
|
||||||
this.postsService.restartDownload(download['uid']).subscribe(res => {
|
this.postsService.restartDownload(download_uid).subscribe(res => {
|
||||||
if (!res['success']) {
|
if (!res['success']) {
|
||||||
this.postsService.openSnackBar($localize`Failed to restart download! See server logs for more info.`);
|
this.postsService.openSnackBar($localize`Failed to restart download! See server logs for more info.`);
|
||||||
} else {
|
} else {
|
||||||
@@ -254,16 +208,16 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelDownload(download: Download): void {
|
cancelDownload(download_uid: string): void {
|
||||||
this.postsService.cancelDownload(download['uid']).subscribe(res => {
|
this.postsService.cancelDownload(download_uid).subscribe(res => {
|
||||||
if (!res['success']) {
|
if (!res['success']) {
|
||||||
this.postsService.openSnackBar($localize`Failed to cancel download! See server logs for more info.`);
|
this.postsService.openSnackBar($localize`Failed to cancel download! See server logs for more info.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
clearDownload(download: Download): void {
|
clearDownload(download_uid: string): void {
|
||||||
this.postsService.clearDownload(download['uid']).subscribe(res => {
|
this.postsService.clearDownload(download_uid).subscribe(res => {
|
||||||
if (!res['success']) {
|
if (!res['success']) {
|
||||||
this.postsService.openSnackBar($localize`Failed to pause download! See server logs for more info.`);
|
this.postsService.openSnackBar($localize`Failed to pause download! See server logs for more info.`);
|
||||||
}
|
}
|
||||||
@@ -303,7 +257,6 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showError(download: Download): void {
|
showError(download: Download): void {
|
||||||
console.log(download)
|
|
||||||
const copyToClipboardEmitter = new EventEmitter<boolean>();
|
const copyToClipboardEmitter = new EventEmitter<boolean>();
|
||||||
this.dialog.open(ConfirmDialogComponent, {
|
this.dialog.open(ConfirmDialogComponent, {
|
||||||
data: {
|
data: {
|
||||||
@@ -323,22 +276,4 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
recalculateColumns() {
|
|
||||||
if (this.innerWidth < 650) this.displayedColumns = this.displayedColumnsSmall;
|
|
||||||
else this.displayedColumns = this.displayedColumnsBig;
|
|
||||||
|
|
||||||
this.actionsFlex = this.uids || this.innerWidth < 800 ? 1 : 2;
|
|
||||||
|
|
||||||
if (this.innerWidth < 800 && !this.uids || this.innerWidth < 1100 && this.uids) this.minimizeButtons = true;
|
|
||||||
else this.minimizeButtons = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DownloadAction {
|
|
||||||
tooltip: string,
|
|
||||||
action: (download: Download) => void,
|
|
||||||
show: (download: Download) => boolean,
|
|
||||||
icon: string,
|
|
||||||
loading?: (download: Download) => boolean
|
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,30 @@
|
|||||||
<cdk-virtual-scroll-viewport itemSize="50" class="viewport" minBufferPx="1200" maxBufferPx="1200">
|
<div class="card-radius mat-elevation-z2" *ngFor="let notification of notifications; let i = index;">
|
||||||
<div #notification_parent class="notification-card-parent card-radius mat-elevation-z2" *cdkVirtualFor="let notification of notifications; let i = index;">
|
<mat-card class="notification-card card-radius">
|
||||||
<mat-card class="notification-card card-radius">
|
<mat-card-header>
|
||||||
<mat-card-header>
|
<mat-card-subtitle>
|
||||||
<mat-card-subtitle>
|
<div>
|
||||||
<div>
|
<span class="notification-timestamp">{{notification.timestamp * 1000 | date:'short'}}</span>
|
||||||
<span class="notification-timestamp">{{notification.timestamp * 1000 | date:'short'}}</span>
|
</div>
|
||||||
</div>
|
</mat-card-subtitle>
|
||||||
</mat-card-subtitle>
|
<mat-card-title>
|
||||||
<mat-card-title>
|
<ng-container *ngIf="NOTIFICATION_PREFIX[notification.type]">
|
||||||
<ng-container *ngIf="NOTIFICATION_PREFIX[notification.type]">
|
{{NOTIFICATION_PREFIX[notification.type]}}
|
||||||
{{NOTIFICATION_PREFIX[notification.type]}}
|
|
||||||
</ng-container>
|
|
||||||
</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<ng-container *ngIf="NOTIFICATION_SUFFIX_KEY[notification.type]">
|
|
||||||
<div style="word-break: break-word">
|
|
||||||
{{notification['data'][NOTIFICATION_SUFFIX_KEY[notification.type]]}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-card-content>
|
</mat-card-title>
|
||||||
<mat-card-actions class="notification-actions" *ngIf="notification.actions?.length > 0">
|
</mat-card-header>
|
||||||
<button matTooltip="Remove" i18n-matTooltip="Remove" (click)="emitDeleteNotification(notification.uid)" mat-icon-button><mat-icon>close</mat-icon></button>
|
<mat-card-content>
|
||||||
<span *ngFor="let action of notification.actions">
|
<ng-container *ngIf="NOTIFICATION_SUFFIX_KEY[notification.type]">
|
||||||
<button [matTooltip]="NOTIFICATION_ACTION_TO_STRING[action]" (click)="emitNotificationAction(notification, action)" mat-icon-button><mat-icon>{{NOTIFICATION_ICON[action]}}</mat-icon></button>
|
<div style="word-break: break-word">
|
||||||
</span>
|
{{notification['data'][NOTIFICATION_SUFFIX_KEY[notification.type]]}}
|
||||||
</mat-card-actions>
|
</div>
|
||||||
<span *ngIf="!notification.read" class="dot"></span>
|
</ng-container>
|
||||||
</mat-card>
|
</mat-card-content>
|
||||||
</div>
|
<mat-card-actions *ngIf="notification.actions?.length > 0">
|
||||||
</cdk-virtual-scroll-viewport>
|
<button matTooltip="Remove" i18n-matTooltip="Remove" (click)="emitDeleteNotification(notification.uid)" mat-icon-button><mat-icon>close</mat-icon></button>
|
||||||
|
<span *ngFor="let action of notification.actions">
|
||||||
|
<button [matTooltip]="NOTIFICATION_ACTION_TO_STRING[action]" (click)="emitNotificationAction(notification, action)" mat-icon-button><mat-icon>{{NOTIFICATION_ICON[action]}}</mat-icon></button>
|
||||||
|
</span>
|
||||||
|
</mat-card-actions>
|
||||||
|
<span *ngIf="!notification.read" class="dot"></span>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
@@ -13,21 +13,12 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-card-parent {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-card {
|
.notification-card {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-actions {
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-radius {
|
.card-radius {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
height: 166px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot {
|
.dot {
|
||||||
@@ -39,8 +30,4 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewport {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
@@ -4,10 +4,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notifications-list-parent {
|
.notifications-list-parent {
|
||||||
|
max-height: 70vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0px 10px 10px 10px;
|
padding: 0px 10px 10px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications-list {
|
|
||||||
display: block
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<mat-chip-listbox [value]="selectedFilters" [multiple]="true" (change)="selectedFiltersChanged($event)">
|
<mat-chip-listbox [value]="selectedFilters" [multiple]="true" (change)="selectedFiltersChanged($event)">
|
||||||
<mat-chip-option *ngFor="let filter of notificationFilters | keyvalue: originalOrder" [value]="filter.key" [selected]="selectedFilters.includes(filter.key)" color="accent">{{filter.value.label}}</mat-chip-option>
|
<mat-chip-option *ngFor="let filter of notificationFilters | keyvalue: originalOrder" [value]="filter.key" [selected]="selectedFilters.includes(filter.key)" color="accent">{{filter.value.label}}</mat-chip-option>
|
||||||
</mat-chip-listbox>
|
</mat-chip-listbox>
|
||||||
<app-notifications-list class="notifications-list" [style.height]="list_height" (notificationAction)="notificationAction($event)" (deleteNotification)="deleteNotification($event)" [notifications]="filtered_notifications"></app-notifications-list>
|
<app-notifications-list (notificationAction)="notificationAction($event)" (deleteNotification)="deleteNotification($event)" [notifications]="filtered_notifications"></app-notifications-list>
|
||||||
</div>
|
</div>
|
||||||
<button style="margin: 10px 0px 2px 10px;" *ngIf="notifications?.length > 0" color="warn" (click)="deleteAllNotifications()" mat-stroked-button>Remove all</button>
|
<button style="margin: 10px 0px 2px 10px;" *ngIf="notifications?.length > 0" color="warn" (click)="deleteAllNotifications()" mat-stroked-button>Remove all</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export class NotificationsComponent implements OnInit {
|
|||||||
|
|
||||||
notifications: Notification[] = null;
|
notifications: Notification[] = null;
|
||||||
filtered_notifications: Notification[] = null;
|
filtered_notifications: Notification[] = null;
|
||||||
list_height = '65vh';
|
|
||||||
|
|
||||||
@Output() notificationCount = new EventEmitter<number>();
|
@Output() notificationCount = new EventEmitter<number>();
|
||||||
|
|
||||||
@@ -111,8 +110,6 @@ export class NotificationsComponent implements OnInit {
|
|||||||
|
|
||||||
filterNotifications(): void {
|
filterNotifications(): void {
|
||||||
this.filtered_notifications = this.notifications.filter(notification => this.selectedFilters.length === 0 || this.selectedFilters.includes(notification.type));
|
this.filtered_notifications = this.notifications.filter(notification => this.selectedFilters.length === 0 || this.selectedFilters.includes(notification.type));
|
||||||
// We need to do this to get the virtual scroll component to have an appropriate height
|
|
||||||
this.calculateListHeight();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedFiltersChanged(event: MatChipListboxChange): void {
|
selectedFiltersChanged(event: MatChipListboxChange): void {
|
||||||
@@ -120,12 +117,6 @@ export class NotificationsComponent implements OnInit {
|
|||||||
this.filterNotifications();
|
this.filterNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateListHeight() {
|
|
||||||
const avgHeight = 166;
|
|
||||||
const calcHeight = this.filtered_notifications.length * avgHeight;
|
|
||||||
this.list_height = calcHeight > window.innerHeight*0.65 ? '65vh' : `${calcHeight}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
originalOrder = (): number => {
|
originalOrder = (): number => {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,36 @@
|
|||||||
<p>
|
<p>
|
||||||
<ng-container i18n="About bug prefix">Found a bug or have a suggestion?</ng-container> <a [href]="issuesLink" target="_blank"><ng-container i18n="About bug click here">Click here</ng-container></a> <ng-container i18n="About bug suffix">to create an issue!</ng-container>
|
<ng-container i18n="About bug prefix">Found a bug or have a suggestion?</ng-container> <a [href]="issuesLink" target="_blank"><ng-container i18n="About bug click here">Click here</ng-container></a> <ng-container i18n="About bug suffix">to create an issue!</ng-container>
|
||||||
</p>
|
</p>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<h5>Personal settings:</h5>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label i18n="Sidepanel mode">Sidepanel mode</mat-label>
|
||||||
|
<mat-select [(ngModel)]="sidepanel_mode" (selectionChange)="sidePanelModeChanged($event.value)">
|
||||||
|
<mat-option value="over">
|
||||||
|
Over
|
||||||
|
</mat-option>
|
||||||
|
<mat-option value="side">
|
||||||
|
Side
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<br/>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label i18n="File card size">File card size</mat-label>
|
||||||
|
<mat-select [(ngModel)]="card_size" (selectionChange)="cardSizeOptionChanged($event.value)">
|
||||||
|
<mat-option value="large">
|
||||||
|
Large
|
||||||
|
</mat-option>
|
||||||
|
<mat-option value="medium">
|
||||||
|
Medium
|
||||||
|
</mat-option>
|
||||||
|
<mat-option value="small">
|
||||||
|
Small
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export class AboutDialogComponent implements OnInit {
|
|||||||
checking_for_updates = true;
|
checking_for_updates = true;
|
||||||
|
|
||||||
current_version_tag = CURRENT_VERSION;
|
current_version_tag = CURRENT_VERSION;
|
||||||
|
sidepanel_mode = this.postsService.sidepanel_mode;
|
||||||
|
card_size = this.postsService.card_size;
|
||||||
|
|
||||||
constructor(public postsService: PostsService) { }
|
constructor(public postsService: PostsService) { }
|
||||||
|
|
||||||
@@ -29,4 +31,15 @@ export class AboutDialogComponent implements OnInit {
|
|||||||
this.latestGithubRelease = res;
|
this.latestGithubRelease = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidePanelModeChanged(new_mode) {
|
||||||
|
localStorage.setItem('sidepanel_mode', new_mode);
|
||||||
|
this.postsService.sidepanel_mode = new_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
cardSizeOptionChanged(new_size) {
|
||||||
|
localStorage.setItem('card_size', new_size);
|
||||||
|
this.postsService.card_size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,52 +13,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 20px;">
|
<div style="margin-top: 20px;">
|
||||||
</div>
|
</div>
|
||||||
<mat-divider style="margin-bottom: 20px"></mat-divider>
|
|
||||||
</div>
|
</div>
|
||||||
<mat-form-field color="accent">
|
|
||||||
<mat-label><ng-container i18n="Language select label">Language</ng-container></mat-label>
|
<div *ngIf="!postsService.isLoggedIn || !postsService.user">
|
||||||
<mat-select (selectionChange)="localeSelectChanged($event.value)" [(value)]="initialLocale">
|
<h5><mat-icon>warn</mat-icon><ng-container i18n="Not logged in notification">You are not logged in.</ng-container></h5>
|
||||||
<mat-option *ngFor="let locale of supported_locales" [value]="locale">
|
<button (click)="loginClicked()" mat-raised-button color="primary"><ng-container i18n="Login">Login</ng-container></button>
|
||||||
<ng-container *ngIf="all_locales[locale]">
|
</div>
|
||||||
{{all_locales[locale]['nativeName']}}
|
|
||||||
</ng-container>
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<br/>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label i18n="Sidepanel mode">Sidepanel mode</mat-label>
|
|
||||||
<mat-select [(ngModel)]="sidepanel_mode" (selectionChange)="sidePanelModeChanged($event.value)">
|
|
||||||
<mat-option i18n="Over" value="over">
|
|
||||||
Over
|
|
||||||
</mat-option>
|
|
||||||
<mat-option i18n="Side" value="side">
|
|
||||||
Side
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
<br/>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label i18n="File card size">File card size</mat-label>
|
|
||||||
<mat-select [(ngModel)]="card_size" (selectionChange)="cardSizeOptionChanged($event.value)">
|
|
||||||
<mat-option i18n="Large" value="large">
|
|
||||||
Large
|
|
||||||
</mat-option>
|
|
||||||
<mat-option i18n="Medium" value="medium">
|
|
||||||
Medium
|
|
||||||
</mat-option>
|
|
||||||
<mat-option i18n="Small" value="small">
|
|
||||||
Small
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|
||||||
<mat-dialog-actions>
|
<mat-dialog-actions>
|
||||||
<div style="width: 100%">
|
<div style="width: 100%">
|
||||||
<div style="position: relative">
|
<div style="position: relative">
|
||||||
<button mat-stroked-button mat-dialog-close color="primary"><ng-container i18n="Close">Close</ng-container></button>
|
<button mat-stroked-button mat-dialog-close color="primary"><ng-container i18n="Close">Close</ng-container></button>
|
||||||
<button *ngIf="postsService.isLoggedIn" style="position: absolute; right: 0px;" (click)="logoutClicked()" mat-stroked-button color="warn"><ng-container i18n="Logout">Logout</ng-container></button>
|
<button style="position: absolute; right: 0px;" (click)="logoutClicked()" mat-stroked-button color="warn"><ng-container i18n="Logout">Logout</ng-container></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { isoLangs } from './locales_list';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-profile-dialog',
|
selector: 'app-user-profile-dialog',
|
||||||
@@ -11,24 +10,9 @@ import { isoLangs } from './locales_list';
|
|||||||
})
|
})
|
||||||
export class UserProfileDialogComponent implements OnInit {
|
export class UserProfileDialogComponent implements OnInit {
|
||||||
|
|
||||||
all_locales = isoLangs;
|
|
||||||
supported_locales = ['en', 'es', 'de', 'fr', 'nl', 'pt', 'it', 'ca', 'cs', 'nb', 'ru', 'zh', 'ko', 'id', 'en-GB'];
|
|
||||||
initialLocale = localStorage.getItem('locale');
|
|
||||||
sidepanel_mode = this.postsService.sidepanel_mode;
|
|
||||||
card_size = this.postsService.card_size;
|
|
||||||
|
|
||||||
constructor(public postsService: PostsService, private router: Router, public dialogRef: MatDialogRef<UserProfileDialogComponent>) { }
|
constructor(public postsService: PostsService, private router: Router, public dialogRef: MatDialogRef<UserProfileDialogComponent>) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.postsService.getSupportedLocales().subscribe(res => {
|
|
||||||
if (res && res['supported_locales']) {
|
|
||||||
this.supported_locales = ['en', 'en-GB']; // required
|
|
||||||
this.supported_locales = this.supported_locales.concat(res['supported_locales']);
|
|
||||||
}
|
|
||||||
}, err => {
|
|
||||||
console.error(`Failed to retrieve list of supported languages! You may need to run: 'node src/postbuild.mjs'. Error below:`);
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loginClicked() {
|
loginClicked() {
|
||||||
@@ -41,19 +25,4 @@ export class UserProfileDialogComponent implements OnInit {
|
|||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
localeSelectChanged(new_val: string): void {
|
|
||||||
localStorage.setItem('locale', new_val);
|
|
||||||
this.postsService.openSnackBar($localize`Language successfully changed! Reload to update the page.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
sidePanelModeChanged(new_mode) {
|
|
||||||
localStorage.setItem('sidepanel_mode', new_mode);
|
|
||||||
this.postsService.sidepanel_mode = new_mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
cardSizeOptionChanged(new_size) {
|
|
||||||
localStorage.setItem('card_size', new_size);
|
|
||||||
this.postsService.card_size = new_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: center;" *ngIf="downloads && downloads.length > 0 && !autoplay">
|
<div style="display: flex; justify-content: center;" *ngIf="downloads && downloads.length > 0 && !autoplay">
|
||||||
<app-downloads style="width: 80%; min-width: 350px; margin-bottom: 10px" [uids]="download_uids"></app-downloads>
|
<app-downloads style="width: 80%; margin-bottom: 10px" [uids]="download_uids"></app-downloads>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="cachedFileManagerEnabled || fileManagerEnabled">
|
<ng-container *ngIf="cachedFileManagerEnabled || fileManagerEnabled">
|
||||||
|
|||||||
@@ -37,15 +37,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
bottom: -2px;
|
bottom: 1px;
|
||||||
left: 6px;
|
left: 2px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-button {
|
.save-button {
|
||||||
right: 25px;
|
right: 25px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -89,6 +85,13 @@
|
|||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner-div {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 12px;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.skip-ad-button {
|
.skip-ad-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
|
|||||||
@@ -22,22 +22,20 @@
|
|||||||
</p>
|
</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!db_file || !db_file['description']">
|
<ng-container *ngIf="!db_file || !db_file['description']">
|
||||||
<p i18n="No description" style="text-align: center;">
|
<p style="text-align: center;">
|
||||||
No description available.
|
No description available.
|
||||||
</p>
|
</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<span class="buttons" *ngIf="db_playlist">
|
<ng-container *ngIf="db_playlist">
|
||||||
<button (click)="downloadContent()" [disabled]="downloading" mat-icon-button><mat-icon>save</mat-icon></button>
|
<button (click)="downloadContent()" [disabled]="downloading" mat-icon-button><mat-icon>save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner></button>
|
||||||
<mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner>
|
|
||||||
<button *ngIf="(!postsService.isLoggedIn || postsService.permissions.includes('sharing')) && !auto" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
<button *ngIf="(!postsService.isLoggedIn || postsService.permissions.includes('sharing')) && !auto" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
||||||
</span>
|
</ng-container>
|
||||||
<span class="buttons" *ngIf="db_file">
|
<ng-container *ngIf="db_file">
|
||||||
<button (click)="downloadFile()" [disabled]="downloading" mat-icon-button><mat-icon>cloud_download</mat-icon></button>
|
<button (click)="downloadFile()" [disabled]="downloading" mat-icon-button><mat-icon>cloud_download</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner></button>
|
||||||
<mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner>
|
|
||||||
<button *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('sharing')" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
<button *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('sharing')" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
||||||
</span>
|
</ng-container>
|
||||||
<ng-container *ngIf="db_file || playlist[currentIndex]"></ng-container>
|
<ng-container *ngIf="db_file || playlist[currentIndex]"></ng-container>
|
||||||
<button (click)="openFileInfoDialog()" *ngIf="db_file || db_playlist" mat-icon-button><mat-icon>info</mat-icon></button>
|
<button (click)="openFileInfoDialog()" *ngIf="db_file || db_playlist" mat-icon-button><mat-icon>info</mat-icon></button>
|
||||||
<button *ngIf="db_file && db_file.url.includes('twitch.tv')" (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
|
<button *ngIf="db_file && db_file.url.includes('twitch.tv')" (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
|
||||||
@@ -58,6 +56,14 @@
|
|||||||
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime" [sub]="subscription"></app-twitch-chat>
|
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime" [sub]="subscription"></app-twitch-chat>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-drawer>
|
</mat-drawer>
|
||||||
|
|
||||||
|
<!-- <div class="update-playlist-button-div" *ngIf="id && playlistChanged()">
|
||||||
|
<div class="spinner-div">
|
||||||
|
<mat-spinner *ngIf="playlist_updating" [diameter]="25"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
<button color="primary" [disabled]="playlist_updating" (click)="updatePlaylist()" mat-raised-button><ng-container i18n="Playlist save changes button">Save changes</ng-container> <mat-icon>update</mat-icon></button>
|
||||||
|
|
||||||
|
</div> -->
|
||||||
</mat-drawer-container>
|
</mat-drawer-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
url = null;
|
url = null;
|
||||||
name = null;
|
name = null;
|
||||||
|
|
||||||
|
innerWidth: number;
|
||||||
|
|
||||||
downloading = false;
|
downloading = false;
|
||||||
|
|
||||||
save_volume_timer = null;
|
save_volume_timer = null;
|
||||||
@@ -68,7 +70,14 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
@ViewChild('twitchchat') twitchChat: TwitchChatComponent;
|
@ViewChild('twitchchat') twitchChat: TwitchChatComponent;
|
||||||
|
|
||||||
|
@HostListener('window:resize', ['$event'])
|
||||||
|
onResize(): void {
|
||||||
|
this.innerWidth = window.innerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.innerWidth = window.innerWidth;
|
||||||
|
|
||||||
this.playlist_id = this.route.snapshot.paramMap.get('playlist_id');
|
this.playlist_id = this.route.snapshot.paramMap.get('playlist_id');
|
||||||
this.uid = this.route.snapshot.paramMap.get('uid');
|
this.uid = this.route.snapshot.paramMap.get('uid');
|
||||||
this.sub_id = this.route.snapshot.paramMap.get('sub_id');
|
this.sub_id = this.route.snapshot.paramMap.get('sub_id');
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ import {
|
|||||||
Subscription,
|
Subscription,
|
||||||
RestartDownloadResponse
|
RestartDownloadResponse
|
||||||
} from '../api-types';
|
} from '../api-types';
|
||||||
import { isoLangs } from './dialogs/user-profile-dialog/locales_list';
|
import { isoLangs } from './settings/locales_list';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { MatDrawerMode } from '@angular/material/sidenav';
|
import { MatDrawerMode } from '@angular/material/sidenav';
|
||||||
|
|
||||||
@@ -734,7 +734,7 @@ export class PostsService implements CanActivate {
|
|||||||
this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
if (err === 'Unauthorized') {
|
if (err.status === 401) {
|
||||||
this.sendToLogin();
|
this.sendToLogin();
|
||||||
this.token = null;
|
this.token = null;
|
||||||
this.resetHttpParams();
|
this.resetHttpParams();
|
||||||
|
|||||||
@@ -78,6 +78,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<div *ngIf="new_config" class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mt-3">
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<mat-label><ng-container i18n="Language select label">Language</ng-container></mat-label>
|
||||||
|
<mat-select (selectionChange)="localeSelectChanged($event.value)" [(value)]="initialLocale">
|
||||||
|
<mat-option *ngFor="let locale of supported_locales" [value]="locale">
|
||||||
|
<ng-container *ngIf="all_locales[locale]">
|
||||||
|
{{all_locales[locale]['nativeName']}}
|
||||||
|
</ng-container>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<!-- Downloader -->
|
<!-- Downloader -->
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, EventEmitter } from '@angular/core';
|
import { Component, OnInit, EventEmitter } from '@angular/core';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { isoLangs } from './locales_list';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import {DomSanitizer} from '@angular/platform-browser';
|
import {DomSanitizer} from '@angular/platform-browser';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
@@ -21,6 +22,10 @@ import { GenerateRssUrlComponent } from 'app/dialogs/generate-rss-url/generate-r
|
|||||||
styleUrls: ['./settings.component.scss']
|
styleUrls: ['./settings.component.scss']
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent implements OnInit {
|
||||||
|
all_locales = isoLangs;
|
||||||
|
supported_locales = ['en', 'es', 'de', 'fr', 'nl', 'pt', 'it', 'ca', 'cs', 'nb', 'ru', 'zh', 'ko', 'id', 'en-GB'];
|
||||||
|
initialLocale = localStorage.getItem('locale');
|
||||||
|
|
||||||
initial_config = null;
|
initial_config = null;
|
||||||
new_config = null
|
new_config = null
|
||||||
loading_config = false;
|
loading_config = false;
|
||||||
@@ -78,6 +83,16 @@ export class SettingsComponent implements OnInit {
|
|||||||
|
|
||||||
const tab = this.route.snapshot.paramMap.get('tab');
|
const tab = this.route.snapshot.paramMap.get('tab');
|
||||||
this.tabIndex = tab && this.TAB_TO_INDEX[tab] ? this.TAB_TO_INDEX[tab] : 0;
|
this.tabIndex = tab && this.TAB_TO_INDEX[tab] ? this.TAB_TO_INDEX[tab] : 0;
|
||||||
|
|
||||||
|
this.postsService.getSupportedLocales().subscribe(res => {
|
||||||
|
if (res && res['supported_locales']) {
|
||||||
|
this.supported_locales = ['en', 'en-GB']; // required
|
||||||
|
this.supported_locales = this.supported_locales.concat(res['supported_locales']);
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
console.error(`Failed to retrieve list of supported languages! You may need to run: 'node src/postbuild.mjs'. Error below:`);
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig(): void {
|
getConfig(): void {
|
||||||
@@ -192,6 +207,11 @@ export class SettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localeSelectChanged(new_val: string): void {
|
||||||
|
localStorage.setItem('locale', new_val);
|
||||||
|
this.postsService.openSnackBar($localize`Language successfully changed! Reload to update the page.`)
|
||||||
|
}
|
||||||
|
|
||||||
generateBookmarklet(): void {
|
generateBookmarklet(): void {
|
||||||
this.bookmarksite('YTDL-Material', this.generated_bookmarklet_code);
|
this.bookmarksite('YTDL-Material', this.generated_bookmarklet_code);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2577,7 +2577,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2560364143605631750" datatype="html">
|
<trans-unit id="2560364143605631750" datatype="html">
|
||||||
<source>Error for <x id="url" equiv-text="download['url']"/></source>
|
<source>Error for<x id="url" equiv-text="download['url']"/></source>
|
||||||
<target state="translated">Error para <x id="url" equiv-text="download['url']"/></target>
|
<target state="translated">Error para <x id="url" equiv-text="download['url']"/></target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
|
||||||
@@ -2733,8 +2733,8 @@
|
|||||||
<note priority="1" from="description">Clear missing files from DB</note>
|
<note priority="1" from="description">Clear missing files from DB</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="39921032161993566" datatype="html">
|
<trans-unit id="39921032161993566" datatype="html">
|
||||||
<source>Successfully created playlist!</source>
|
<source>Playlist created.</source>
|
||||||
<target state="translated">¡Lista de reproducción creada con éxito!</target>
|
<target state="translated">Lista de reproducción creada.</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
|
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
|
||||||
<context context-type="linenumber">56</context>
|
<context context-type="linenumber">56</context>
|
||||||
@@ -3092,848 +3092,6 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Generate NFO files setting</note>
|
<note priority="1" from="description">Generate NFO files setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="c748ac656af9f13998206ef2c52018dd418b0483" datatype="html">
|
|
||||||
<source>Archives</source>
|
|
||||||
<target state="translated">Archivos</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/app.component.html</context>
|
|
||||||
<context context-type="linenumber">26</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Archives menu label</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22" datatype="html">
|
|
||||||
<source>Filter</source>
|
|
||||||
<target state="translated">Filtros</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">3</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Filter</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="45cc8ca94b5a50842a9a8ef804a5ab089a38ae5c" datatype="html">
|
|
||||||
<source>ID</source>
|
|
||||||
<target state="translated">ID</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">47</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">ID</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c150a30bbbdb175b4d08820196a9acb66b167653" datatype="html">
|
|
||||||
<source>Archives empty</source>
|
|
||||||
<target state="translated">Archivos vacíos</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">72</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Archives empty</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="51a161ce175abcd44f6c6cbd0e996681bf553ac3" datatype="html">
|
|
||||||
<source>Delete selected</source>
|
|
||||||
<target state="translated">Eliminar seleccionado</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">77</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Delete selected</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c41475a25c9f9d9639db9efa56637603a77528b4" datatype="html">
|
|
||||||
<source>Download archive</source>
|
|
||||||
<target state="translated">Descargar archivo</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">80</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Download archive</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="a2f14a73f7a6e94479f67423cc51102da8d6f524" datatype="html">
|
|
||||||
<source>None</source>
|
|
||||||
<target state="translated">Ninguno</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">84</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">126</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">27</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">36</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">None</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="4b3972c3e9485218508a95f7a4ce7758e3f09ced" datatype="html">
|
|
||||||
<source>Upload</source>
|
|
||||||
<target state="translated">Cargado</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">137</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
|
|
||||||
<context context-type="linenumber">30</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Upload</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="6549265851868599441" datatype="html">
|
|
||||||
<source>Video</source>
|
|
||||||
<target state="translated">Video</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">40</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="347407180135731058" datatype="html">
|
|
||||||
<source>Audio</source>
|
|
||||||
<target state="translated">Audio</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">44</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8953483585652369683" datatype="html">
|
|
||||||
<source>Archive successfully imported!</source>
|
|
||||||
<target state="translated">¡Archivo importado con éxito!</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">130</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="3159807825117518005" datatype="html">
|
|
||||||
<source>Delete archives</source>
|
|
||||||
<target state="translated">Borrar los archivos</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">152</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="7022070615528435141" datatype="html">
|
|
||||||
<source>Delete</source>
|
|
||||||
<target state="translated">Borrar</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">154</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
|
||||||
<context context-type="linenumber">175</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="2525880134753073592" datatype="html">
|
|
||||||
<source>Successfully deleted archive items!</source>
|
|
||||||
<target state="translated">¡Elementos del archivo eliminados correctamente!</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">172</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8224301330941792118" datatype="html">
|
|
||||||
<source>Failed to delete archive items!</source>
|
|
||||||
<target state="translated">¡No se pudieron eliminar los elementos del archivo!</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">174</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="019d4bd6a5690f0cfa0ecf346a4e6bf7f0d8debb" datatype="html">
|
|
||||||
<source>Remove</source>
|
|
||||||
<target state="translated">Quitar</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.html</context>
|
|
||||||
<context context-type="linenumber">23</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Remove</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="6219551536751479443" datatype="html">
|
|
||||||
<source>Finished downloading</source>
|
|
||||||
<target state="translated">Descarga finalizada</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
|
||||||
<context context-type="linenumber">17</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5947241266456580665" datatype="html">
|
|
||||||
<source>Download failed</source>
|
|
||||||
<target state="translated">La descarga fracasó</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
|
||||||
<context context-type="linenumber">18</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8443034725057696949" datatype="html">
|
|
||||||
<source>Task finished</source>
|
|
||||||
<target state="translated">Tarea terminada</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
|
||||||
<context context-type="linenumber">19</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5709555629190115111" datatype="html">
|
|
||||||
<source>View task</source>
|
|
||||||
<target state="translated">Ver la tarea</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
|
||||||
<context context-type="linenumber">33</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5a105e7bd7e7db6ea211fe950fc9f317379acceb" datatype="html">
|
|
||||||
<source>No notifications available</source>
|
|
||||||
<target state="translated">No hay notificaciones disponibles</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.html</context>
|
|
||||||
<context context-type="linenumber">1</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">No notifications available</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="6876310993601590130" datatype="html">
|
|
||||||
<source>Download completed</source>
|
|
||||||
<target state="translated">Descarga completa</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
|
||||||
<context context-type="linenumber">23</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="6437411876967154040" datatype="html">
|
|
||||||
<source>Audio only</source>
|
|
||||||
<target state="translated">Solo el audio</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
|
|
||||||
<context context-type="linenumber">60</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="4665451070906079743" datatype="html">
|
|
||||||
<source>Favorited</source>
|
|
||||||
<target state="translated">Favorito</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
|
|
||||||
<context context-type="linenumber">65</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="6268070779441507380" datatype="html">
|
|
||||||
<source>Download Date</source>
|
|
||||||
<target state="translated">Fecha de la descarga</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
|
||||||
<context context-type="linenumber">13</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="3533826530554274875" datatype="html">
|
|
||||||
<source>Upload Date</source>
|
|
||||||
<target state="translated">Fecha en la que se subió</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
|
||||||
<context context-type="linenumber">17</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8953033926734869941" datatype="html">
|
|
||||||
<source>Name</source>
|
|
||||||
<target state="translated">Nombre</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
|
||||||
<context context-type="linenumber">21</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="2492098975665776610" datatype="html">
|
|
||||||
<source>File Size</source>
|
|
||||||
<target state="translated">Tamaño del archivo</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
|
||||||
<context context-type="linenumber">25</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="7410432243549869948" datatype="html">
|
|
||||||
<source>Duration</source>
|
|
||||||
<target state="translated">Duración</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
|
|
||||||
<context context-type="linenumber">29</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="ea2b65121b93921fe54692025da9b9e3ce779ad5" datatype="html">
|
|
||||||
<source>Task settings - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></source>
|
|
||||||
<target state="translated">Configuración de las tareas - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
|
||||||
<context context-type="linenumber">1</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Task settings</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c65dd978b3c7566551c0ebefb234c2d41942b847" datatype="html">
|
|
||||||
<source>Delete files older than</source>
|
|
||||||
<target state="translated">Eliminar los archivos anteriores a</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
|
||||||
<context context-type="linenumber">6</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Delete files older than</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="56b1a3c93fb95fed1805005c561a5e431d57ffae" datatype="html">
|
|
||||||
<source>Blacklist all files</source>
|
|
||||||
<target state="translated">Lista negra de todos los archivos</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
|
||||||
<context context-type="linenumber">11</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Blacklist deleted files</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="9aa1b4779a515170b297d2c0507e6ff9d2e3e0e0" datatype="html">
|
|
||||||
<source>Blacklist deleted subscription files</source>
|
|
||||||
<target state="translated">Lista negra de los archivos de la suscripción eliminados</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
|
||||||
<context context-type="linenumber">14</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Blacklist deleted subscription files</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="cdf5297d8d080a78e8b10debc5c38b7845a3cbe7" datatype="html">
|
|
||||||
<source>Do not ask for confirmation</source>
|
|
||||||
<target state="translated">No pedir confirmación</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
|
|
||||||
<context context-type="linenumber">19</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Do not ask for confirmation</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="9176960997786930103" datatype="html">
|
|
||||||
<source>Error for: <x id="PH" equiv-text="task['title']"/></source>
|
|
||||||
<target state="translated">Error para: <x id="PH" equiv-text="task['title']"/></target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/tasks/tasks.component.ts</context>
|
|
||||||
<context context-type="linenumber">174</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="11a0771f88158a540a54e0e4ec5d25733d65fc0e" datatype="html">
|
|
||||||
<source>Favorite</source>
|
|
||||||
<target state="translated">Favorito</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
|
|
||||||
<context context-type="linenumber">26</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Favorite button</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1d4fa01d25990f60abf21c3a451fa8ba262b7912" datatype="html">
|
|
||||||
<source>Unfavorite</source>
|
|
||||||
<target state="translated">No es favorito</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
|
|
||||||
<context context-type="linenumber">27</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Unfavorite button</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5ac5a0e5ffe8e5623b40696f4c2403c17349271f" datatype="html">
|
|
||||||
<source>Sidepanel mode</source>
|
|
||||||
<target state="translated">Modo del panel lateral</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/about-dialog/about-dialog.component.html</context>
|
|
||||||
<context context-type="linenumber">42</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Sidepanel mode</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1f2809e6a99d511fdb6eaf041d785fe54d0680cc" datatype="html">
|
|
||||||
<source>File card size</source>
|
|
||||||
<target state="translated">Tamaño de la tarjeta del archivo</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/about-dialog/about-dialog.component.html</context>
|
|
||||||
<context context-type="linenumber">54</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">File card size</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="d618f383a0ea2458eeb945a85190d4a002ea394b" datatype="html">
|
|
||||||
<source>Arg</source>
|
|
||||||
<target state="translated">Realidad virtual</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html</context>
|
|
||||||
<context context-type="linenumber">41</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Arg</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c35ef0f03a863d33b04aae6807f140397a50f491" datatype="html">
|
|
||||||
<source>Generate RSS URL</source>
|
|
||||||
<target state="translated">Generar la url para RSS</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">1</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">306</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Generate RSS URL</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1a9b415816364f554ee411020e65219092655271" datatype="html">
|
|
||||||
<source>Title filter</source>
|
|
||||||
<target state="translated">Filtrar por título</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">8</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Title filter</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="9aa62bf1a535a97a4d752bbc5cf1c31af0f0c1f7" datatype="html">
|
|
||||||
<source>Supports regex</source>
|
|
||||||
<target state="translated">Admitir expresiones regulares</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">10</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Supports regex</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="e08a77594f3d89311cdf6da5090044270909c194" datatype="html">
|
|
||||||
<source>User</source>
|
|
||||||
<target state="translated">Usuario</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">25</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">User</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c2faa86201eab08b5b39b5437f96ab9432e125e7" datatype="html">
|
|
||||||
<source>Item limit</source>
|
|
||||||
<target state="translated">Límite del elemento</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">46</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Item limit</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="b78a98bc54259a29cf6250dbaeab5fe11fae91cf" datatype="html">
|
|
||||||
<source>Favorited</source>
|
|
||||||
<target state="translated">Favoritos</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
|
|
||||||
<context context-type="linenumber">51</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Favorited</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8336047719608684263" datatype="html">
|
|
||||||
<source>Unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/></source>
|
|
||||||
<target state="translated">Cancelar la suscripción a <x id="subscription name" equiv-text="this.sub['name']"/></target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
|
|
||||||
<context context-type="linenumber">30</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1698114086921246480" datatype="html">
|
|
||||||
<source>Unsubscribe</source>
|
|
||||||
<target state="translated">Darse de baja</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
|
|
||||||
<context context-type="linenumber">32</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1091872159779006651" datatype="html">
|
|
||||||
<source>You must input a time!</source>
|
|
||||||
<target state="translated">¡Debes ingresar una hora!</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component.ts</context>
|
|
||||||
<context context-type="linenumber">70</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="d57c023a4cf63b2f12c10328c15b636ff18929aa" datatype="html">
|
|
||||||
<source>Best</source>
|
|
||||||
<target state="translated">El mejor</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/main/main.component.html</context>
|
|
||||||
<context context-type="linenumber">24,25</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Best</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="35cf4cdcedc8ef3f94b6100e0d86836e31dbb908" datatype="html">
|
|
||||||
<source>Force autoplay</source>
|
|
||||||
<target state="translated">Forzar la reproducción automática</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">235</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Force autoplay setting</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="37469c9f3e31d95087fa22b6c9c3bc64adf1692d" datatype="html">
|
|
||||||
<source>Enable RSS Feed</source>
|
|
||||||
<target state="translated">Activar la fuente RSS</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">304</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Enable RSS Feed setting</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="61d6b5fa4311b1c617b66dad72496f9dd43b07b4" datatype="html">
|
|
||||||
<source>Be careful enabling this with multi-user mode! User data may be exposed.</source>
|
|
||||||
<target state="translated">¡Ten cuidado al habilitar esto con el modo multiusuario! Los datos del usuario pueden estar expuestos.</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">305</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">RSS Feed prefix</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="fd467148a18f0921c10d116d4e0174fe29452be4" datatype="html">
|
|
||||||
<source>See documentation here.</source>
|
|
||||||
<target state="translated">Consulte la documentación aquí.</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">307</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">RSS feed documentation</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab" datatype="html">
|
|
||||||
<source>Notifications</source>
|
|
||||||
<target state="translated">Notificaciones</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">376</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Notifications settings label</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c40370dc182b5e4333828b70f7478bde58bb5cfe" datatype="html">
|
|
||||||
<source>Enable notifications</source>
|
|
||||||
<target state="translated">Activar las notificaciones</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">382</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Enable notifications setting</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="33a7c6d5ff3515fa237f1fd4e43df8b65373954d" datatype="html">
|
|
||||||
<source>Enable all notifications</source>
|
|
||||||
<target state="translated">Activar toda las notificaciones</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">385</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Enable all notifications setting</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="9c562d26e041390ecc3f49dabc51cc50ebba7469" datatype="html">
|
|
||||||
<source>Allowed notification types</source>
|
|
||||||
<target state="translated">Tipos de notificaciones permitidos</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">389</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Allowed notification types</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c5dc5fbcce45e9b1530e2a5c2baa8ebe722aef4c" datatype="html">
|
|
||||||
<source>Download complete</source>
|
|
||||||
<target state="translated">Descarga completa</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">391</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Download complete</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="3ffd9490f3a4c0b24021d25e1dc71fcfe5d39cd6" datatype="html">
|
|
||||||
<source>Download error</source>
|
|
||||||
<target state="translated">Error en la descarga</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">392</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Download error</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="2361a4f76caaa4574803fbcdca8b0a47c91cc7ed" datatype="html">
|
|
||||||
<source>Task finished</source>
|
|
||||||
<target state="translated">Tarea finalizada</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">393</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Task finished</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="9e766e11a9de375907aaf566897ecc6dac393ebc" datatype="html">
|
|
||||||
<source>Webhook URL</source>
|
|
||||||
<target state="translated">URL del webhook</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">399</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">webhook URL</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8c1bf02206fbc371ff69ab1b7e35a17ba29d169d" datatype="html">
|
|
||||||
<source>Use ntfy API</source>
|
|
||||||
<target state="translated">Utilizar la API de ntfy</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">405</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Use ntfy API setting</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="06f503e492d6dbcf59e7b9c412ca86913d718689" datatype="html">
|
|
||||||
<source>ntfy topic URL</source>
|
|
||||||
<target state="translated">URL del tema ntfy</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">409</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">ntfy topic URL</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="0cfc9cfe7cd8ea14bc053693b28872da739af02c" datatype="html">
|
|
||||||
<source>See docs here.</source>
|
|
||||||
<target state="translated">Consulta la documentación aquí.</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">411</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">421</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">428</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">ntfy API setting hint</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5827fde8fcafdd55ae80921ad3ad4aa01012e203" datatype="html">
|
|
||||||
<source>Use gotify API</source>
|
|
||||||
<target state="translated">Utilizar la Api de gotify</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">415</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Use gotify API setting</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="55f559d6f666b945479f534b0c182f70cd0a8a69" datatype="html">
|
|
||||||
<source>Gotify server URL</source>
|
|
||||||
<target state="translated">URL del servidor Gotify</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">419</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Gotify server URL</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="b770c48628d98cb4633d6a17e3f0ba0265376af5" datatype="html">
|
|
||||||
<source>Gotify app token</source>
|
|
||||||
<target state="translated">Token de la aplicación Gotify</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">426</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Gotify app token</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="38992954440d6afb54aeb58af12ca0123ee5e26e" datatype="html">
|
|
||||||
<source>Use Telegram API</source>
|
|
||||||
<target state="translated">Utilizar la API de Telegram</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">432</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Use Telegram API setting</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="2e076ff9866213d0815961c494aa48b177046b9d" datatype="html">
|
|
||||||
<source>Telegram bot token</source>
|
|
||||||
<target state="translated">Tomen del bot de Telegram</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">436</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Telegram bot token</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="eeb0ba2e4743901d8f5eebd9a3529aa1f236c608" datatype="html">
|
|
||||||
<source>Create bot here.</source>
|
|
||||||
<target state="translated">Crear un bot aquí.</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">438</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Telegram bot create link</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="144e1a21ebe8fa238f88d2ac27515ed711cfc9a0" datatype="html">
|
|
||||||
<source>Telegram chat ID</source>
|
|
||||||
<target state="translated">ID del chat de Telegram</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">443</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Telegram chat ID</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="3e420c675b8f3f3702576d52e8bb6e8e1d3feda0" datatype="html">
|
|
||||||
<source>How do I get the chat ID?</source>
|
|
||||||
<target state="translated">¿Cómo obtengo la identificación del chat?</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">445</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Telegram chat ID help</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="a4ed8eba1e057e67d5c2d87b52230f182b3dae4e" datatype="html">
|
|
||||||
<source>Restart required.</source>
|
|
||||||
<target state="translated">Reinicio requerido.</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">465</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Restart required hint</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="6785427850041119037" datatype="html">
|
|
||||||
<source>Delete category</source>
|
|
||||||
<target state="translated">Borrar la categoría</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
|
||||||
<context context-type="linenumber">173</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="2481374649045841364" datatype="html">
|
|
||||||
<source>Would you like to delete <x id="category name" equiv-text="category['name']"/>?</source>
|
|
||||||
<target state="translated">¿Deseas eliminar <x id="category name" equiv-text="category['name']"/>?</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
|
||||||
<context context-type="linenumber">174</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="3371159074051387771" datatype="html">
|
|
||||||
<source>Failed to delete <x id="category name" equiv-text="category['name']"/>!</source>
|
|
||||||
<target state="translated">¡No se ha podido eliminar <x id="category name" equiv-text="category['name']"/>!</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
|
||||||
<context context-type="linenumber">187</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8c6e24eab969d9f63a8a0e9d617aee3b99e28ae6" datatype="html">
|
|
||||||
<source>Play all</source>
|
|
||||||
<target state="translated">Reproducir todo</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/subscription/subscription/subscription.component.html</context>
|
|
||||||
<context context-type="linenumber">17</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Play all</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="674a999dd48d7da565ffdd105602261b8a4761ea" datatype="html">
|
|
||||||
<source>Download zip</source>
|
|
||||||
<target state="translated">Descargar en un archivo zip</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/subscription/subscription/subscription.component.html</context>
|
|
||||||
<context context-type="linenumber">18</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Download zip</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="0ed98b4c6ec1db6708a963e8a2699478ac97f55c" datatype="html">
|
|
||||||
<source>Add subscription</source>
|
|
||||||
<target state="translated">Añadir suscripción</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/subscriptions/subscriptions.component.html</context>
|
|
||||||
<context context-type="linenumber">60</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Add subscription</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="28da11220a3377ddce3c7948825d33101f142782" datatype="html">
|
|
||||||
<source>Extractor</source>
|
|
||||||
<target state="translated">Extractor</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
|
|
||||||
<context context-type="linenumber">57</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Extractor</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8425787787095143143" datatype="html">
|
|
||||||
<source>Would you like to delete <x id="selected archives amount" equiv-text="this.selection.selected.length"/> archive(s)?</source>
|
|
||||||
<target state="translated">¿ Quieres borrar el(los) archivo(s) de <x id="selected archives amount" equiv-text="this.selection.selected.length"/> ?</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
|
|
||||||
<context context-type="linenumber">153</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8564202903947049539" datatype="html">
|
|
||||||
<source>Play</source>
|
|
||||||
<target state="translated">Reproducir</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
|
||||||
<context context-type="linenumber">30</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8643601595923420698" datatype="html">
|
|
||||||
<source>Retry download</source>
|
|
||||||
<target state="translated">Reintertar la descarga</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
|
||||||
<context context-type="linenumber">31</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="8571838164752006148" datatype="html">
|
|
||||||
<source>View error</source>
|
|
||||||
<target state="translated">Ver el error</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
|
|
||||||
<context context-type="linenumber">32</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1879058637439215882" datatype="html">
|
|
||||||
<source>Download error</source>
|
|
||||||
<target state="translated">Error al descargar</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
|
||||||
<context context-type="linenumber">27</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="7911845622864460134" datatype="html">
|
|
||||||
<source>Video only</source>
|
|
||||||
<target state="translated">Solo el video</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
|
|
||||||
<context context-type="linenumber">55</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="4578192247039196794" datatype="html">
|
|
||||||
<source>Task</source>
|
|
||||||
<target state="translated">Tarea</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
|
||||||
<context context-type="linenumber">31</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5000203534763292992" datatype="html">
|
|
||||||
<source>Download restarted!</source>
|
|
||||||
<target state="translated">¡Descarga reiniciada!</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
|
|
||||||
<context context-type="linenumber">72</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="7b4585a9072f3c1292972c14a3d0e14978fbfc9c" datatype="html">
|
|
||||||
<source>Delete old files:</source>
|
|
||||||
<target state="translated">Eliminar los archivos antiguos:</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/tasks/tasks.component.html</context>
|
|
||||||
<context context-type="linenumber">66</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Delete old files</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="784837056777689544" datatype="html">
|
|
||||||
<source>Would you like to unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/>?</source>
|
|
||||||
<target state="translated">¿Deseas anular tu suscripción a <x id="subscription name" equiv-text="this.sub['name']"/>?</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
|
|
||||||
<context context-type="linenumber">31</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="7332320960988475089" datatype="html">
|
|
||||||
<source>Successfully deleted <x id="category name" equiv-text="category['name']"/>!</source>
|
|
||||||
<target state="translated">¡Se ha eliminado correctamente <x id="category name" equiv-text="category['name']"/>!</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
|
|
||||||
<context context-type="linenumber">183</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="7cedb649779673568447b994463b2882c4e0436a" datatype="html">
|
|
||||||
<source>Slack Webhook URL</source>
|
|
||||||
<target state="translated">URL del webhook de Slack</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">397</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Slack Webhook URL</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="3264d82792954815be755b3da01e2625458711dc" datatype="html">
|
|
||||||
<source>Discord Webhook URL</source>
|
|
||||||
<target state="translated">URL del webhook de Discord</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">390</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Discord Webhook URL</note>
|
|
||||||
</trans-unit>
|
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -3757,33 +3757,6 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Select a version</note>
|
<note priority="1" from="description">Select a version</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2361a4f76caaa4574803fbcdca8b0a47c91cc7ed" datatype="html">
|
|
||||||
<source>Task finished</source>
|
|
||||||
<target state="translated">Zadanie zakończone</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">377</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Task finished</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="c5dc5fbcce45e9b1530e2a5c2baa8ebe722aef4c" datatype="html">
|
|
||||||
<source>Download complete</source>
|
|
||||||
<target state="translated">Pobieranie zakończone</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">375</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Download complete</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="3ffd9490f3a4c0b24021d25e1dc71fcfe5d39cd6" datatype="html">
|
|
||||||
<source>Download error</source>
|
|
||||||
<target state="translated">Błąd pobierania</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">376</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Download error</note>
|
|
||||||
</trans-unit>
|
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
Reference in New Issue
Block a user