From 6f955388348310178acb5b601d129490fca02642 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 15 Feb 2022 17:10:38 +0100 Subject: [PATCH 1/8] Split the build script tasks into separate files Build scripts are now located in the scripts folder. Signed-off-by: Trivernis --- .dockerignore | 5 +- .gitignore | 1 + Dockerfile | 6 +- README.md | 17 ++- build.py | 210 ------------------------------------ mediarepo-daemon/Cargo.lock | 2 +- scripts/build.py | 116 ++++++++++++++++++++ scripts/check.py | 57 ++++++++++ scripts/clean.py | 27 +++++ scripts/lib.py | 57 ++++++++++ 10 files changed, 280 insertions(+), 218 deletions(-) delete mode 100755 build.py create mode 100755 scripts/build.py create mode 100755 scripts/check.py create mode 100755 scripts/clean.py create mode 100644 scripts/lib.py diff --git a/.dockerignore b/.dockerignore index e5c8b1c..ced1b76 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,8 @@ # compiled output out +__pycache__ +target +dist # IDEs and editors mediarepo-api/.idea @@ -36,4 +39,4 @@ mediarepo-daemon/*.folded # api mediarepo-api/.idea -mediarepo-api/target +mediarepo-api/target \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2ca27a3..df4f7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /out-tsc /target /out +__pycache__ # IDEs and editors mediarepo-api/.idea diff --git a/Dockerfile b/Dockerfile index e9caab0..d1dc609 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /usr/src COPY mediarepo-api ./mediarepo-api COPY mediarepo-daemon ./mediarepo-daemon COPY mediarepo-ui ./mediarepo-ui -COPY build.py . +COPY scripts ./scripts RUN apt-get update RUN apt-get install -y \ @@ -37,4 +37,6 @@ RUN apt remove cmdtest -y RUN curl https://sh.rustup.rs -sSf | bash -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" -RUN python3 build.py build +RUN python3 scripts/clean.py +RUN python3 scripts/check.py --install +RUN python3 scripts/build.py all --verbose --ffmpeg diff --git a/README.md b/README.md index dd07f19..a837fd3 100644 --- a/README.md +++ b/README.md @@ -69,23 +69,32 @@ You also need to have a working [python](https://www.python.org/) installation o After all required dependencies are installed and tools are accessible in the `PATH`, you can build the project like follows: -> Note: You might need to make the `build.py` file executable with `chmod +x build.py`. +Check (and install) required tooling: +```sh +$ ./scripts/check.py --install +``` +> Note: this only installs tools that are installable via cargo or npm All Componens: ```sh -$ ./build.py build --ffmpeg +$ ./scripts/build.py all --ffmpeg ``` Daemon only: ```sh -$ ./build.py build --daemon --ffmpeg +$ ./scripts/build.py daemon --ffmpeg ``` If you don't want to build with ffmpeg support omit the `--ffmpeg` flag. UI only: ```sh -$ ./build.py build --ui +$ ./scripts/build.py ui +``` + +Clean the output directory: +```sh +$ ./scripts/clean.py ``` After building the `out` directory contains all the built binaries and bundles. diff --git a/build.py b/build.py deleted file mode 100755 index fc6be03..0000000 --- a/build.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/bin/env python3 -import shutil as shut -import os -import subprocess - -tauri_cli_version = '1.0.0-rc.5' -build_output = 'out' -verbose = False -ffmpeg = False - -windows = os.name == 'nt' - - -def exec(cmd: str, dir: str = None) -> str: - print('Running: {}'.format(cmd)) - child = subprocess.run(cmd, shell=True, cwd=dir) - child.check_returncode() - - -def check_exec(name: str): - print('Checking {}...'.format(name)) - - if shut.which(name) is None: - raise Exception('{} not found'.format(name)) - exec(name + ' --version') - - -def check_yarn(): - print('Checking yarn...') - - if shut.which('yarn') is None: - print('installing yarn...') - npm('install -g yarn') - check_exec('yarn') - exec('yarn --version') - - -def check_ng(): - print('Checking ng...') - - if shut.which('ng') is None: - print('installing ng...') - npm('install -g @angular/cli') - check_exec('ng') - exec('ng --version') - - -def store_artifact(path: str): - print('Storing {}'.format(path)) - if os.path.isdir(path): - shut.copytree(path, os.path.join( - build_output, os.path.basename(path)), dirs_exist_ok=True) - else: - shut.copy(path, build_output) - - -def cargo(cmd: str, dir: str = None): - if verbose: - exec('cargo {} --verbose'.format(cmd), dir) - else: - exec('cargo {}'.format(cmd), dir) - - -def npm(cmd: str, dir: str = None): - exec('npm {}'.format(cmd), dir) - - -def yarn(cmd: str, dir: str = None): - exec('yarn {}'.format(cmd), dir) - - -def build_daemon(): - '''Builds daemon''' - cargo('fetch', 'mediarepo-daemon') - - if not ffmpeg: - cargo('build --release --frozen --no-default-features', 'mediarepo-daemon') - else: - cargo('build --release --frozen', 'mediarepo-daemon') - - if windows: - store_artifact('mediarepo-daemon/target/release/mediarepo-daemon.exe') - else: - store_artifact('mediarepo-daemon/target/release/mediarepo-daemon') - - -def build_ui(): - '''Builds UI''' - cargo('install tauri-cli --version ^{}'.format(tauri_cli_version)) - yarn('install', 'mediarepo-ui') - cargo('tauri build', 'mediarepo-ui') - - if windows: - store_artifact( - 'mediarepo-ui/src-tauri/target/release/mediarepo-ui.exe') - else: - store_artifact('mediarepo-ui/src-tauri/target/release/mediarepo-ui') - - store_artifact('mediarepo-ui/src-tauri/target/release/bundle/') - - -def check_daemon(): - '''Checks dependencies for daemon''' - check_exec('clang') - check_exec('cargo') - - -def check_ui(): - '''Checks dependencies for UI''' - - if not windows: - check_exec('wget') - check_exec('curl') - check_exec('file') - - check_exec('clang') - check_exec('cargo') - check_exec('node') - check_exec('npm') - check_yarn() - check_ng() - - -def check(): - '''Checks dependencies''' - check_daemon() - check_ui() - print('All checks passed') - - -def create_output_dir(): - '''Creates build output directory''' - if not os.path.exists(build_output): - os.mkdir(build_output) - - -def clean(): - '''Removes build output''' - if os.path.exists(build_output): - shut.rmtree(build_output) - print('Cleaned') - - -def build(daemon=True, ui=True): - '''Builds both daemon and UI''' - clean() - create_output_dir() - - if daemon: - check_daemon() - build_daemon() - - if ui: - check_ui() - build_ui() - - print('Build complete') - - -def parse_args(): - import argparse - parser = argparse.ArgumentParser(description='Build mediarepo') - subparsers = parser.add_subparsers(dest='command') - subparsers.required = True - - subparsers.add_parser('check') - - build_parser = subparsers.add_parser('build') - build_parser.add_argument( - '--daemon', action='store_true', help='Build daemon') - build_parser.add_argument('--ui', action='store_true', help='Build UI') - build_parser.add_argument( - '--verbose', action='store_true', help='Verbose build') - build_parser.add_argument( - '--output', action='store', help='Build output directory') - build_parser.add_argument( - '--ffmpeg', action='store_true', help='Build with ffmpeg') - - subparsers.add_parser('clean') - args = parser.parse_args() - return args - - -def main(): - opts = parse_args() - - if opts.command == 'build': - global build_output - build_output = opts.output if opts.output else build_output - - global verbose - verbose = opts.verbose - - global ffmpeg - ffmpeg = opts.ffmpeg - - if opts.daemon: - build(True, False) - elif opts.ui: - build(False, True) - else: - build() - elif opts.command == 'check': - check() - elif opts.command == 'clean': - clean() - - -if __name__ == '__main__': - main() diff --git a/mediarepo-daemon/Cargo.lock b/mediarepo-daemon/Cargo.lock index 09f9992..6b809a2 100644 --- a/mediarepo-daemon/Cargo.lock +++ b/mediarepo-daemon/Cargo.lock @@ -1398,7 +1398,7 @@ dependencies = [ [[package]] name = "mediarepo-daemon" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "console-subscriber", "glob", diff --git a/scripts/build.py b/scripts/build.py new file mode 100755 index 0000000..84ef686 --- /dev/null +++ b/scripts/build.py @@ -0,0 +1,116 @@ +#!/bin/env python3 +import shutil as shut +import os +from lib import * +import json +from clean import clean +from check import check_daemon_depends, check_ui_depends + + +build_output = 'out' +verbose = False +ffmpeg = False +install_deps = False + +windows = os.name == 'nt' + + +def main(): + opts = parse_args() + + global install_deps + global build_output + global verbose + global ffmpeg + global install_deps + + build_output = opts.output if opts.output else build_output + verbose = opts.verbose + ffmpeg = opts.ffmpeg + install_deps = opts.install_deps + + build(opts.component) + + +def parse_args(): + import argparse + parser = argparse.ArgumentParser(description='Build mediarepo') + parser.add_argument( + 'component', type=str, nargs='?', default='all', choices=['daemon', 'ui', 'all']) + parser.add_argument( + '--verbose', action='store_true', help='Verbose build') + parser.add_argument('--install-deps', action='store_true', + help='Install dependencies') + parser.add_argument( + '--output', action='store', help='Build output directory') + parser.add_argument( + '--ffmpeg', action='store_true', help='Build with ffmpeg') + + args = parser.parse_args() + return args + + +def build(component: str): + '''Builds the selected component''' + clean() + create_output_dir() + + if component == 'daemon' or component == 'all': + check_daemon_depends() + build_daemon() + elif component == 'ui' or component == 'all': + check_ui_depends(install_deps) + build_ui() + else: + raise Exception('Unknown component: {}'.format(component)) + + print('Build complete') + + +def build_daemon(): + '''Builds daemon''' + cargo('fetch', 'mediarepo-daemon') + + if not ffmpeg: + cargo('build --release --frozen --no-default-features', 'mediarepo-daemon') + else: + cargo('build --release --frozen', 'mediarepo-daemon') + + if windows: + store_artifact('mediarepo-daemon/target/release/mediarepo-daemon.exe') + else: + store_artifact('mediarepo-daemon/target/release/mediarepo-daemon') + + +def build_ui(): + '''Builds UI''' + yarn('install', 'mediarepo-ui') + cargo('tauri build', 'mediarepo-ui') + + if windows: + store_artifact( + 'mediarepo-ui/src-tauri/target/release/mediarepo-ui.exe') + else: + store_artifact('mediarepo-ui/src-tauri/target/release/mediarepo-ui') + + store_artifact('mediarepo-ui/src-tauri/target/release/bundle/') + + +def create_output_dir(): + '''Creates build output directory''' + if not os.path.exists(build_output): + os.mkdir(build_output) + + +def store_artifact(path: str): + '''Stores a build artifact''' + print('Storing {}'.format(path)) + if os.path.isdir(path): + shut.copytree(path, os.path.join( + build_output, os.path.basename(path)), dirs_exist_ok=True) + else: + shut.copy(path, build_output) + + +if __name__ == '__main__': + main() diff --git a/scripts/check.py b/scripts/check.py new file mode 100755 index 0000000..36374b7 --- /dev/null +++ b/scripts/check.py @@ -0,0 +1,57 @@ +#!/bin/env python3 +from lib import * +import argparse +import os + + +tauri_cli_version = '1.0.0-rc.5' +windows = os.name == 'nt' + + +def main(): + opts = parse_args() + check(opts.install_deps) + + +def parse_args(): + '''Parses command line arguments''' + args = argparse.ArgumentParser(description='Build mediarepo') + args.add_argument('--install-deps', action='store_true', + help='Install dependencies that can be installed automatically') + return args.parse_args() + + +def check(install_deps: bool = False): + '''Checks dependencies''' + check_daemon_depends() + check_ui_depends(install_deps) + print('All checks passed') + + +def check_daemon_depends(): + '''Checks dependencies for daemon''' + check_exec('clang') + check_exec('cargo') + + +def check_ui_depends(install_deps: bool = False): + '''Checks dependencies for UI''' + + if not windows: + check_exec('wget') + check_exec('curl') + check_exec('file') + + check_exec('clang') + check_exec('cargo') + check_exec('node') + check_exec('npm') + check_yarn(install_deps) + check_ng(install_deps) + + if install_deps: + install_tauri_cli(tauri_cli_version) + + +if __name__ == '__main__': + main() diff --git a/scripts/clean.py b/scripts/clean.py new file mode 100755 index 0000000..6e87d7a --- /dev/null +++ b/scripts/clean.py @@ -0,0 +1,27 @@ +#!/bin/env python3 +import os +import shutil as shut +import argparse + + +def main(): + opts = parse_args() + clean(opts.output if opts.output else 'out') + + +def parse_args(): + '''Parses command line arguments''' + args = argparse.ArgumentParser(description='Build mediarepo') + args.add_argument('--output', action='store', help='Build output directory') + return args.parse_args() + + +def clean(build_output: str = 'out'): + '''Removes build output''' + if os.path.exists(build_output): + shut.rmtree(build_output) + print('Cleaned') + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/scripts/lib.py b/scripts/lib.py new file mode 100644 index 0000000..8e14c02 --- /dev/null +++ b/scripts/lib.py @@ -0,0 +1,57 @@ +import subprocess +import shutil as shut + + +def install_tauri_cli(version: str): + cargo('install tauri-cli --version ^{}'.format(version)) + + +def check_ng(install: bool = False): + '''Checks if ng is available and installs it + if the install flag is set''' + if not check_exec('ng'): + if install: + npm('install -g @angular/cli') + else: + raise Exception('ng not found') + + +def check_yarn(install: bool = False): + '''Checks if yarn is available and installs it + if the install flag is set''' + if not check_exec('yarn'): + if install: + npm('install yarn') + else: + raise Exception('yarn not found') + + +def yarn(cmd: str, dir: str = None) -> str: + '''Executes yarn in a given directory''' + exec('yarn {}'.format(cmd), dir) + + +def cargo(cmd: str, dir: str = None): + '''Executes cargo in a given directory''' + exec('cargo {}'.format(cmd), dir) + + +def npm(cmd: str, dir: str = None) -> str: + '''Executes npm in a given directory''' + exec('npm {}'.format(cmd), dir) + + +def check_exec(name: str) -> bool: + '''Checks if a command is available''' + if shut.which(name) is None: + print('{} not found'.format(name)) + return False + exec('{} --version'.format(name)) + return True + + +def exec(cmd: str, dir: str = None) -> str: + '''Executes a command in a given directory''' + print('Running: {}'.format(cmd)) + child = subprocess.run(cmd, shell=True, cwd=dir) + child.check_returncode() \ No newline at end of file From 76a0b13e5bb362e9de1ec82675243a111f031cf2 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 15 Feb 2022 18:45:52 +0100 Subject: [PATCH 2/8] Fix buildscript Signed-off-by: trivernis --- mediarepo-ui/src-tauri/Cargo.lock | 2 +- scripts/build.py | 46 ++++++++++++++++++------------- scripts/check.py | 22 +++++++-------- scripts/lib.py | 4 +-- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock index 40b6e09..a999486 100644 --- a/mediarepo-ui/src-tauri/Cargo.lock +++ b/mediarepo-ui/src-tauri/Cargo.lock @@ -40,7 +40,7 @@ checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "app" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" dependencies = [ "mediarepo-api", "serde", diff --git a/scripts/build.py b/scripts/build.py index 84ef686..f3b66f9 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -2,15 +2,15 @@ import shutil as shut import os from lib import * -import json from clean import clean -from check import check_daemon_depends, check_ui_depends +from check import check, check_daemon_tooling, check_ui_tooling +from typing import List build_output = 'out' verbose = False ffmpeg = False -install_deps = False +install_tooling = False windows = os.name == 'nt' @@ -18,18 +18,18 @@ windows = os.name == 'nt' def main(): opts = parse_args() - global install_deps + global install_tooling global build_output global verbose global ffmpeg - global install_deps + global install_tooling build_output = opts.output if opts.output else build_output verbose = opts.verbose ffmpeg = opts.ffmpeg - install_deps = opts.install_deps + install_tooling = opts.install_tooling - build(opts.component) + build(opts.component, opts.bundles) def parse_args(): @@ -39,30 +39,34 @@ def parse_args(): 'component', type=str, nargs='?', default='all', choices=['daemon', 'ui', 'all']) parser.add_argument( '--verbose', action='store_true', help='Verbose build') - parser.add_argument('--install-deps', action='store_true', - help='Install dependencies') parser.add_argument( '--output', action='store', help='Build output directory') parser.add_argument( '--ffmpeg', action='store_true', help='Build with ffmpeg') + parser.add_argument('--install-tooling', + action='store_true', help='Install tooling') + parser.add_argument('--bundles', nargs='+', + help='UI bundles to build') args = parser.parse_args() return args -def build(component: str): +def build(component: str, bundles: List[str] = None): '''Builds the selected component''' clean() create_output_dir() - if component == 'daemon' or component == 'all': - check_daemon_depends() + if component == 'all': + check(install_tooling) build_daemon() - elif component == 'ui' or component == 'all': - check_ui_depends(install_deps) - build_ui() - else: - raise Exception('Unknown component: {}'.format(component)) + build_ui(bundles) + elif component == 'daemon': + check_daemon_tooling(install_tooling) + build_daemon() + elif component == 'ui': + check_ui_tooling() + build_ui(bundles) print('Build complete') @@ -82,10 +86,14 @@ def build_daemon(): store_artifact('mediarepo-daemon/target/release/mediarepo-daemon') -def build_ui(): +def build_ui(bundles: List[str] = None): '''Builds UI''' yarn('install', 'mediarepo-ui') - cargo('tauri build', 'mediarepo-ui') + + if bundles is not None: + cargo('tauri build --bundles ' + ' '.join(bundles), 'mediarepo-ui') + else: + cargo('tauri build ', 'mediarepo-ui') if windows: store_artifact( diff --git a/scripts/check.py b/scripts/check.py index 36374b7..340dde9 100755 --- a/scripts/check.py +++ b/scripts/check.py @@ -10,31 +10,31 @@ windows = os.name == 'nt' def main(): opts = parse_args() - check(opts.install_deps) + check(opts.install) def parse_args(): '''Parses command line arguments''' args = argparse.ArgumentParser(description='Build mediarepo') - args.add_argument('--install-deps', action='store_true', - help='Install dependencies that can be installed automatically') + args.add_argument('--install', action='store_true', + help='Install tools that can be installed automatically') return args.parse_args() -def check(install_deps: bool = False): +def check(install_tooling: bool = False): '''Checks dependencies''' - check_daemon_depends() - check_ui_depends(install_deps) + check_daemon_tooling() + check_ui_tooling(install_tooling) print('All checks passed') -def check_daemon_depends(): +def check_daemon_tooling(): '''Checks dependencies for daemon''' check_exec('clang') check_exec('cargo') -def check_ui_depends(install_deps: bool = False): +def check_ui_tooling(install_tooling: bool = False): '''Checks dependencies for UI''' if not windows: @@ -46,10 +46,10 @@ def check_ui_depends(install_deps: bool = False): check_exec('cargo') check_exec('node') check_exec('npm') - check_yarn(install_deps) - check_ng(install_deps) + check_yarn(install_tooling) + check_ng(install_tooling) - if install_deps: + if install_tooling: install_tauri_cli(tauri_cli_version) diff --git a/scripts/lib.py b/scripts/lib.py index 8e14c02..8721381 100644 --- a/scripts/lib.py +++ b/scripts/lib.py @@ -21,7 +21,7 @@ def check_yarn(install: bool = False): if the install flag is set''' if not check_exec('yarn'): if install: - npm('install yarn') + npm('install -g yarn') else: raise Exception('yarn not found') @@ -54,4 +54,4 @@ def exec(cmd: str, dir: str = None) -> str: '''Executes a command in a given directory''' print('Running: {}'.format(cmd)) child = subprocess.run(cmd, shell=True, cwd=dir) - child.check_returncode() \ No newline at end of file + child.check_returncode() From ed8ddf4d9fcd95e38ecb3244757bfe2bd5106179 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 15 Feb 2022 19:09:36 +0100 Subject: [PATCH 3/8] Update workflows to use new buildscript location Signed-off-by: trivernis --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- scripts/build.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a29a7f..3135cc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,7 +91,7 @@ jobs: python-version: '^3.7' - name: Build - run: python build.py build --daemon --verbose + run: python scripts/build.py daemon --verbose --install-tooling - name: Upload artifacts if: ${{ !env.ACT }} @@ -150,7 +150,7 @@ jobs: DEBIAN_FRONTEND=noninteractive sudo apt-get install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev -y - name: Build project - run: python build.py build --ui --verbose + run: python scripts/build.py ui --verbose --install-tooling - name: Upload artifacts if: ${{ !env.ACT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 757ceb7..81f77d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: python-version: '^3.7' - name: Build Daemon - run: python build.py build --daemon --verbose + run: python scripts/build.py daemon --verbose --install-tooling - uses: vimtor/action-zip@v1 with: @@ -113,7 +113,7 @@ jobs: DEBIAN_FRONTEND=noninteractive sudo apt-get install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev -y - name: Build project - run: python build.py build --ui --verbose + run: python scripts/build.py ui --verbose --install-tooling - uses: vimtor/action-zip@v1 with: diff --git a/scripts/build.py b/scripts/build.py index f3b66f9..d5fe6c0 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -62,10 +62,10 @@ def build(component: str, bundles: List[str] = None): build_daemon() build_ui(bundles) elif component == 'daemon': - check_daemon_tooling(install_tooling) + check_daemon_tooling() build_daemon() elif component == 'ui': - check_ui_tooling() + check_ui_tooling(install_tooling) build_ui(bundles) print('Build complete') @@ -116,7 +116,7 @@ def store_artifact(path: str): if os.path.isdir(path): shut.copytree(path, os.path.join( build_output, os.path.basename(path)), dirs_exist_ok=True) - else: + elif os.path.isfile(path): shut.copy(path, build_output) From bb2a63c7036993288d0a61b9a040da384914ba0a Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 18 Feb 2022 17:45:05 +0100 Subject: [PATCH 4/8] Move repository overview to separate component Signed-off-by: trivernis --- .../src/app/components/core/core.module.ts | 2 + .../repositories-tab.component.html | 20 +-- .../repositories-tab.component.scss | 40 +---- .../repositories-tab.component.ts | 158 +---------------- .../repository-overview.component.html | 15 ++ .../repository-overview.component.scss | 42 +++++ .../repository-overview.component.spec.ts | 25 +++ .../repository-overview.component.ts | 159 ++++++++++++++++++ 8 files changed, 254 insertions(+), 207 deletions(-) create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.scss create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.spec.ts create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.ts diff --git a/mediarepo-ui/src/app/components/core/core.module.ts b/mediarepo-ui/src/app/components/core/core.module.ts index a514aba..067bb3a 100644 --- a/mediarepo-ui/src/app/components/core/core.module.ts +++ b/mediarepo-ui/src/app/components/core/core.module.ts @@ -40,6 +40,7 @@ import { RepositoryDetailsViewComponent } from "./repositories-tab/repository-details-view/repository-details-view.component"; import { EmptyTabComponent } from './empty-tab/empty-tab.component'; +import { RepositoryOverviewComponent } from './repositories-tab/repository-overview/repository-overview.component'; @NgModule({ @@ -54,6 +55,7 @@ import { EmptyTabComponent } from './empty-tab/empty-tab.component'; DownloadDaemonDialogComponent, RepositoryDetailsViewComponent, EmptyTabComponent, + RepositoryOverviewComponent, ], exports: [ CoreComponent, diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html index 48622cd..4093b2a 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.html @@ -1,18 +1,6 @@ -
-
- -
-
-
- -
- -

There are no repositories yet. You can create a repository or add an existing one.

- -
-
+
+
-
- +
+
diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss index 4e2ab92..2e3c1d6 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.scss @@ -1,41 +1,3 @@ -.repository-container { - margin: 1em; -} - -.repo-page-content { - margin: 0 10%; - height: calc(100% - 2em); -} - -.add-repo-tools { - height: 5em; - display: flex; - flex-direction: row-reverse; - - button { - margin: 1em; - } -} - -.repository-list { - display: flex; - flex-direction: column; - overflow-y: auto; - height: calc(100% - 5em); -} - -app-repository-card { - position: relative; -} - -app-repository-details-view, .repo-details { +app-repository-details-view { height: 100%; } - -.add-repository-prompt { - button { - font-size: 1.5em; - padding: 0.5em 1em; - border-radius: 0.5em; - } -} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts index 3c4f421..4b17e68 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repositories-tab.component.ts @@ -1,164 +1,18 @@ -import {AfterViewInit, Component, OnInit} from "@angular/core"; -import {Repository} from "../../../../api/models/Repository"; +import {Component} from "@angular/core"; import {RepositoryService} from "../../../services/repository/repository.service"; -import {MatDialog, MatDialogRef} from "@angular/material/dialog"; -import {DownloadDaemonDialogComponent} from "./download-daemon-dialog/download-daemon-dialog.component"; -import { - AddRepositoryDialogComponent -} from "../../shared/repository/repository/add-repository-dialog/add-repository-dialog.component"; -import {LoggingService} from "../../../services/logging/logging.service"; -import {BehaviorSubject} from "rxjs"; -import {BusyDialogComponent} from "../../shared/app-common/busy-dialog/busy-dialog.component"; -import {JobService} from "../../../services/job/job.service"; -import {StateService} from "../../../services/state/state.service"; +import {Repository} from "../../../../api/models/Repository"; -type BusyDialogContext = { message: BehaviorSubject, dialog: MatDialogRef }; @Component({ selector: "app-repositories-tab", templateUrl: "./repositories-tab.component.html", styleUrls: ["./repositories-tab.component.scss"] }) -export class RepositoriesTabComponent implements OnInit, AfterViewInit { - public repositories: Repository[] = []; - public selectedRepository?: Repository; - - constructor( - private logger: LoggingService, - private repoService: RepositoryService, - private jobService: JobService, - private stateService: StateService, - public dialog: MatDialog - ) { - } - - ngOnInit(): void { - this.repoService.repositories.subscribe({ - next: (repos) => { - this.repositories = repos; - } - }); - this.repoService.selectedRepository.subscribe( - repo => this.selectedRepository = repo); - } +export class RepositoriesTabComponent { - public async ngAfterViewInit() { - await this.checkAndPromptDaemonExecutable(); - } - - public async startDaemonAndSelectRepository(repository: Repository) { - try { - let dialogContext = this.openStartupDialog(repository); - let daemonRunning = await this.repoService.checkDaemonRunning( - repository.path!); - if (!daemonRunning) { - dialogContext.message.next("Starting repository daemon..."); - await this.repoService.startDaemon(repository.path!); - - await new Promise((res, _) => { - setTimeout(res, 2000); // wait for the daemon to start - }); - } - await this.selectRepository(repository, dialogContext); - } catch (err: any) { - this.logger.error(err); - } - } - - public async selectRepository(repository: Repository, dialogContext?: BusyDialogContext) { - dialogContext = dialogContext ?? this.openStartupDialog(repository); - try { - dialogContext.message.next("Opening repository..."); - await this.repoService.setRepository(repository); - await this.runRepositoryStartupTasks(dialogContext); - dialogContext.message.next("Restoring previous tabs..."); - await this.repoService.loadRepositories(); - dialogContext.dialog.close(true); - } catch (err: any) { - this.logger.error(err); - dialogContext.message.next( - "Failed to open repository: " + err.toString()); - await this.forceCloseRepository(); - setTimeout(() => dialogContext!.dialog.close(true), 1000); - } - } - - public openAddRepositoryDialog() { - this.dialog.open(AddRepositoryDialogComponent, { - disableClose: true, - minWidth: "30%", - minHeight: "30%", - }); - } - - public async onOpenRepository(repository: Repository) { - if (!repository.local) { - await this.selectRepository(repository); - } else { - await this.startDaemonAndSelectRepository(repository); - } - } - - private async forceCloseRepository() { - try { - await this.repoService.closeSelectedRepository(); - } catch { - } - try { - await this.repoService.disconnectSelectedRepository(); - } catch { - } - } - - private async runRepositoryStartupTasks(dialogContext: BusyDialogContext): Promise { - dialogContext.message.next("Checking integrity..."); - await this.jobService.runJob("CheckIntegrity"); - dialogContext.message.next("Running a vacuum on the database..."); - await this.jobService.runJob("Vacuum"); - dialogContext.message.next( - "Migrating content descriptors to new format..."); - await this.jobService.runJob("MigrateContentDescriptors"); - dialogContext.message.next("Calculating repository sizes..."); - await this.jobService.runJob("CalculateSizes", false); - dialogContext.message.next("Generating missing thumbnails..."); - await this.jobService.runJob("GenerateThumbnails"); - dialogContext.message.next("Finished repository startup"); - } - - private openStartupDialog(repository: Repository): BusyDialogContext { - const dialogMessage = new BehaviorSubject( - "Opening repository..."); - let dialog = this.dialog.open(BusyDialogComponent, { - data: { - title: `Opening repository '${repository.name}'`, - message: dialogMessage, - allowCancel: true, - }, disableClose: true, - minWidth: "30%", - minHeight: "30%", - }); - dialog.afterClosed().subscribe(async (result) => { - if (!result) { - await this.forceCloseRepository(); - } - }); - - return { message: dialogMessage, dialog }; - } + public selectedRepository?: Repository; - private async checkAndPromptDaemonExecutable() { - if (!await this.repoService.checkDameonConfigured()) { - const result = await this.dialog.open( - DownloadDaemonDialogComponent, - { - disableClose: true, - } - ).afterClosed().toPromise(); - if (result) { - // recursion avoidance - setTimeout( - async () => await this.checkAndPromptDaemonExecutable(), 0); - } - } + constructor(private repositoryService: RepositoryService) { + const sub = this.repositoryService.selectedRepository.subscribe(repo => this.selectedRepository = repo); } } diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html new file mode 100644 index 0000000..6b9d51f --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html @@ -0,0 +1,15 @@ +
+
+ +
+
+
+ +
+ +

There are no repositories yet. You can create a repository or add an existing one.

+ +
+
+
diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.scss b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.scss new file mode 100644 index 0000000..4f97bb8 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.scss @@ -0,0 +1,42 @@ +.repository-container { + margin: 1em; + display: block; + width: 600px; + float: left; +} + +.repo-page-content { + margin: 0 10%; + height: calc(100% - 2em); +} + +.add-repo-tools { + height: 5em; + display: flex; + flex-direction: row-reverse; + + button { + margin: 1em; + } +} + +.repository-list { + display: block; + overflow-y: auto; + overflow-x: hidden; + height: calc(100% - 5em); +} + +app-repository-card { + display: block; + position: relative; +} + + +.add-repository-prompt { + button { + font-size: 1.5em; + padding: 0.5em 1em; + border-radius: 0.5em; + } +} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.spec.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.spec.ts new file mode 100644 index 0000000..89b6b32 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RepositoryOverviewComponent } from './repository-overview.component'; + +describe('RepositoryOverviewComponent', () => { + let component: RepositoryOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RepositoryOverviewComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RepositoryOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.ts new file mode 100644 index 0000000..3b109f0 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.ts @@ -0,0 +1,159 @@ +import {AfterViewInit, ChangeDetectionStrategy, Component, OnInit} from "@angular/core"; +import {Repository} from "../../../../../api/models/Repository"; +import {LoggingService} from "../../../../services/logging/logging.service"; +import {RepositoryService} from "../../../../services/repository/repository.service"; +import {JobService} from "../../../../services/job/job.service"; +import {StateService} from "../../../../services/state/state.service"; +import {MatDialog, MatDialogRef} from "@angular/material/dialog"; +import { + AddRepositoryDialogComponent +} from "../../../shared/repository/repository/add-repository-dialog/add-repository-dialog.component"; +import {BehaviorSubject} from "rxjs"; +import {BusyDialogComponent} from "../../../shared/app-common/busy-dialog/busy-dialog.component"; +import {DownloadDaemonDialogComponent} from "../download-daemon-dialog/download-daemon-dialog.component"; + +type BusyDialogContext = { message: BehaviorSubject, dialog: MatDialogRef }; + +@Component({ + selector: "app-repository-overview", + templateUrl: "./repository-overview.component.html", + styleUrls: ["./repository-overview.component.scss"], + changeDetection: ChangeDetectionStrategy.Default +}) +export class RepositoryOverviewComponent implements OnInit, AfterViewInit { + + public repositories: Repository[] = []; + + constructor( + private logger: LoggingService, + private repoService: RepositoryService, + private jobService: JobService, + private stateService: StateService, + public dialog: MatDialog + ) { + } + + ngOnInit(): void { + this.repoService.repositories.subscribe(repos => this.repositories = repos); + } + + public async ngAfterViewInit() { + await this.checkAndPromptDaemonExecutable(); + } + + public async startDaemonAndSelectRepository(repository: Repository) { + try { + let dialogContext = this.openStartupDialog(repository); + let daemonRunning = await this.repoService.checkDaemonRunning( + repository.path!); + if (!daemonRunning) { + dialogContext.message.next("Starting repository daemon..."); + await this.repoService.startDaemon(repository.path!); + + await new Promise((res, _) => { + setTimeout(res, 2000); // wait for the daemon to start + }); + } + await this.selectRepository(repository, dialogContext); + } catch (err: any) { + this.logger.error(err); + } + } + + public async selectRepository(repository: Repository, dialogContext?: BusyDialogContext) { + dialogContext = dialogContext ?? this.openStartupDialog(repository); + try { + dialogContext.message.next("Opening repository..."); + await this.repoService.setRepository(repository); + await this.runRepositoryStartupTasks(dialogContext); + dialogContext.message.next("Restoring previous tabs..."); + await this.repoService.loadRepositories(); + dialogContext.dialog.close(true); + } catch (err: any) { + this.logger.error(err); + dialogContext.message.next( + "Failed to open repository: " + err.toString()); + await this.forceCloseRepository(); + setTimeout(() => dialogContext!.dialog.close(true), 1000); + } + } + + public openAddRepositoryDialog() { + this.dialog.open(AddRepositoryDialogComponent, { + disableClose: true, + minWidth: "30%", + minHeight: "30%", + }); + } + + public async onOpenRepository(repository: Repository) { + if (!repository.local) { + await this.selectRepository(repository); + } else { + await this.startDaemonAndSelectRepository(repository); + } + } + + private async forceCloseRepository() { + try { + await this.repoService.closeSelectedRepository(); + } catch { + } + try { + await this.repoService.disconnectSelectedRepository(); + } catch { + } + } + + private async runRepositoryStartupTasks(dialogContext: BusyDialogContext): Promise { + dialogContext.message.next("Checking integrity..."); + await this.jobService.runJob("CheckIntegrity"); + dialogContext.message.next("Running a vacuum on the database..."); + await this.jobService.runJob("Vacuum"); + dialogContext.message.next( + "Migrating content descriptors to new format..."); + await this.jobService.runJob("MigrateContentDescriptors"); + dialogContext.message.next("Calculating repository sizes..."); + await this.jobService.runJob("CalculateSizes", false); + dialogContext.message.next("Generating missing thumbnails..."); + await this.jobService.runJob("GenerateThumbnails"); + dialogContext.message.next("Finished repository startup"); + } + + private openStartupDialog(repository: Repository): BusyDialogContext { + const dialogMessage = new BehaviorSubject( + "Opening repository..."); + let dialog = this.dialog.open(BusyDialogComponent, { + data: { + title: `Opening repository '${repository.name}'`, + message: dialogMessage, + allowCancel: true, + }, disableClose: true, + minWidth: "30%", + minHeight: "30%", + }); + dialog.afterClosed().subscribe(async (result) => { + if (!result) { + await this.forceCloseRepository(); + } + }); + + return { message: dialogMessage, dialog }; + } + + private async checkAndPromptDaemonExecutable() { + if (!await this.repoService.checkDameonConfigured()) { + const result = await this.dialog.open( + DownloadDaemonDialogComponent, + { + disableClose: true, + } + ).afterClosed().toPromise(); + if (result) { + // recursion avoidance + setTimeout( + async () => await this.checkAndPromptDaemonExecutable(), 0); + } + } + } +} From 77d288d9051e737d03b60b4726e8b9219c098946 Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 18 Feb 2022 18:41:16 +0100 Subject: [PATCH 5/8] Add about dialog Signed-off-by: trivernis --- .../app/components/core/core.component.html | 2 +- .../src/app/components/core/core.module.ts | 2 ++ .../about-dialog/about-dialog.component.html | 24 ++++++++++++++ .../about-dialog/about-dialog.component.scss | 20 ++++++++++++ .../about-dialog.component.spec.ts | 25 +++++++++++++++ .../about-dialog/about-dialog.component.ts | 31 +++++++++++++++++++ .../repository-overview.component.html | 9 ++++-- .../repository-overview.component.scss | 19 ++++++++++++ .../repository-overview.component.ts | 8 +++++ .../shared/app-common/app-common.module.ts | 3 ++ .../external-url/external-url.component.html | 3 ++ .../external-url/external-url.component.scss | 15 +++++++++ .../external-url.component.spec.ts | 25 +++++++++++++++ .../external-url/external-url.component.ts | 31 +++++++++++++++++++ .../metadata-entry.component.html | 4 ++- .../metadata-entry.component.scss | 24 +++++++++++++- .../metadata-entry.component.ts | 7 +++-- 17 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.html create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.scss create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.spec.ts create mode 100644 mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.ts create mode 100644 mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.html create mode 100644 mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.scss create mode 100644 mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.spec.ts create mode 100644 mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.ts diff --git a/mediarepo-ui/src/app/components/core/core.component.html b/mediarepo-ui/src/app/components/core/core.component.html index def7489..94f56f3 100644 --- a/mediarepo-ui/src/app/components/core/core.component.html +++ b/mediarepo-ui/src/app/components/core/core.component.html @@ -1,7 +1,7 @@
- + diff --git a/mediarepo-ui/src/app/components/core/core.module.ts b/mediarepo-ui/src/app/components/core/core.module.ts index 067bb3a..feb3733 100644 --- a/mediarepo-ui/src/app/components/core/core.module.ts +++ b/mediarepo-ui/src/app/components/core/core.module.ts @@ -41,6 +41,7 @@ import { } from "./repositories-tab/repository-details-view/repository-details-view.component"; import { EmptyTabComponent } from './empty-tab/empty-tab.component'; import { RepositoryOverviewComponent } from './repositories-tab/repository-overview/repository-overview.component'; +import { AboutDialogComponent } from './repositories-tab/repository-overview/about-dialog/about-dialog.component'; @NgModule({ @@ -56,6 +57,7 @@ import { RepositoryOverviewComponent } from './repositories-tab/repository-overv RepositoryDetailsViewComponent, EmptyTabComponent, RepositoryOverviewComponent, + AboutDialogComponent, ], exports: [ CoreComponent, diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.html b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.html new file mode 100644 index 0000000..9b57198 --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.html @@ -0,0 +1,24 @@ +

+ About mediarepo +

+
+ + {{version | async}} + + + {{tauriVersion | async}} + +
+ +

Built with

+
+ + {{lib[0]}} + +
+
+
+ +
diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.scss b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.scss new file mode 100644 index 0000000..607eefd --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.scss @@ -0,0 +1,20 @@ +.title { + text-align: center; +} + +.actions { + display: block; + + button { + float: right; + } +} + +.content { + text-align: center; +} + +.lib-list { + display: flex; + flex-direction: column; +} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.spec.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.spec.ts new file mode 100644 index 0000000..e95648e --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import {ComponentFixture, TestBed} from "@angular/core/testing"; + +import {AboutDialogComponent} from "./about-dialog.component"; + +describe("AboutDialogComponent", () => { + let component: AboutDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AboutDialogComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AboutDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.ts b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.ts new file mode 100644 index 0000000..e4c49ed --- /dev/null +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/about-dialog/about-dialog.component.ts @@ -0,0 +1,31 @@ +import {ChangeDetectionStrategy, Component, OnInit} from "@angular/core"; +import {MatDialogRef} from "@angular/material/dialog"; +import {app} from "@tauri-apps/api"; +import {Subject} from "rxjs"; + +@Component({ + selector: "app-about-dialog", + templateUrl: "./about-dialog.component.html", + styleUrls: ["./about-dialog.component.scss"], + changeDetection: ChangeDetectionStrategy.Default +}) +export class AboutDialogComponent implements OnInit { + + public version = new Subject(); + public tauriVersion = new Subject(); + public usedLibs = [ + ["Tauri", "https://tauri.studio"], + ["Angular", "https://angular.io/"], + ["SeaORM", "https://www.sea-ql.org"], + ["Tokio", "https://tokio.rs/"], + ["bromine", "https://github.com/Trivernis/bromine"] + ]; + + constructor(public dialogRef: MatDialogRef) { + } + + public async ngOnInit() { + this.version.next(await app.getVersion()); + this.tauriVersion.next(await app.getTauriVersion()); + } +} diff --git a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html index 6b9d51f..8220df4 100644 --- a/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html +++ b/mediarepo-ui/src/app/components/core/repositories-tab/repository-overview/repository-overview.component.html @@ -1,7 +1,10 @@ + +

All Repositories

+ + +
-
- -
, dialog: MatDialogRef }; @@ -94,6 +95,13 @@ export class RepositoryOverviewComponent implements OnInit, AfterViewInit { } } + public openAboutDialog(): void { + this.dialog.open(AboutDialogComponent, { + minWidth: "30%", + minHeight: "50%", + }); + } + private async forceCloseRepository() { try { await this.repoService.closeSelectedRepository(); diff --git a/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts b/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts index a556c14..0a25e48 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts +++ b/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts @@ -25,6 +25,7 @@ import {MatRippleModule} from "@angular/material/core"; import {FlapButtonComponent} from "./flap-button/flap-button.component"; import {MiddleCenteredComponent} from "./middle-centered/middle-centered.component"; import {FormatBytesPipe} from "./pipes/format-bytes.pipe"; +import {ExternalUrlComponent} from "./external-url/external-url.component"; @NgModule({ @@ -44,6 +45,7 @@ import {FormatBytesPipe} from "./pipes/format-bytes.pipe"; FlapButtonComponent, MiddleCenteredComponent, FormatBytesPipe, + ExternalUrlComponent, ], exports: [ ConfirmDialogComponent, @@ -60,6 +62,7 @@ import {FormatBytesPipe} from "./pipes/format-bytes.pipe"; FlapButtonComponent, MiddleCenteredComponent, FormatBytesPipe, + ExternalUrlComponent, ], imports: [ CommonModule, diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.html b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.html new file mode 100644 index 0000000..7148987 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.html @@ -0,0 +1,3 @@ + + + diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.scss b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.scss new file mode 100644 index 0000000..3fccf25 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.scss @@ -0,0 +1,15 @@ +@import "src/colors"; + +.highlight { + cursor: pointer; + color: $primary-lighter-20; + text-decoration: underline; + + &:hover { + color: $primary-lighter-30; + } + + &:active { + color: $accent; + } +} diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.spec.ts b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.spec.ts new file mode 100644 index 0000000..77018c4 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.spec.ts @@ -0,0 +1,25 @@ +import {ComponentFixture, TestBed} from "@angular/core/testing"; + +import {ExternalUrlComponent} from "./external-url.component"; + +describe("ExternalUrlComponent", () => { + let component: ExternalUrlComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ExternalUrlComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ExternalUrlComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.ts b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.ts new file mode 100644 index 0000000..7d3c10b --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/external-url/external-url.component.ts @@ -0,0 +1,31 @@ +import {ChangeDetectionStrategy, Component, Input} from "@angular/core"; +import {shell} from "@tauri-apps/api"; + +@Component({ + selector: "app-external-url", + templateUrl: "./external-url.component.html", + styleUrls: ["./external-url.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ExternalUrlComponent { + + @Input() href!: string; + private opening = false; + + constructor() { + } + + public async openUrl() { + if (this.opening) { + return; + } + this.opening = true; + try { + await shell.open(this.href); + } catch (err) { + console.error(err); + } finally { + this.opening = false; + } + } +} diff --git a/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html b/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html index 9edc4cb..22dfb2f 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html +++ b/mediarepo-ui/src/app/components/shared/app-common/metadata-entry/metadata-entry.component.html @@ -1,4 +1,6 @@ -