mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-08 21:15:09 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b0232a4d7 | |||
| b7836d7cfd | |||
| 0ed4f3cba6 | |||
| 411e945b3a | |||
| 873cd131cf | |||
| 8b11fcc02d | |||
| 3c3d7b3b82 | |||
| 9c4a31c729 | |||
| 9cb64ebce7 | |||
| a10878ca88 | |||
| c7b391f905 | |||
| 825c91f584 | |||
| c9dbef3439 | |||
| 0ce6e5d706 | |||
| a4ccdb6a50 | |||
| 8e6ebf26f0 | |||
| 06b822fd78 | |||
| fed326445f | |||
| 401f4b2e78 | |||
| af7f575986 | |||
| 9a148d2a56 | |||
| 51c7ecc689 | |||
| c0d039d1c8 | |||
| 66d9408227 | |||
| 92ac57f4f0 | |||
| f622c91d53 | |||
| 78332192b0 | |||
| e3c4c0ef7e | |||
| cdfb67f71b | |||
| c3a2794a01 | |||
| 4dd84d7367 | |||
| 2f3e409a2e | |||
| b1e81615b0 | |||
| 1233a33b66 | |||
| f2bbd8bb65 | |||
| 5a93d46a0c | |||
| cca4d3dbb7 | |||
| 2b48ebaa59 | |||
| da23edc9eb | |||
| 872465ee8a | |||
| 3755070e1f | |||
| 0c5cfb2254 | |||
| f3edc1ac36 | |||
| 2caa6ff43f | |||
| 56cc2146cd | |||
| 040eacc189 | |||
| 31d28d87db | |||
| 8ad321b743 | |||
| ea1102b7f2 | |||
| 627875b1cf | |||
| 7ffaba6aac | |||
| fe81f3ee98 | |||
| ed9372c808 | |||
| b804da0140 |
@@ -24,7 +24,7 @@ jobs:
|
|||||||
backend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.backend == 'true' }}
|
backend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.backend == 'true' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -87,7 +87,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Start containers
|
- name: Start containers
|
||||||
@@ -96,11 +96,11 @@ jobs:
|
|||||||
docker compose --file docker/compose/docker-compose.ci-test.yml up --detach
|
docker compose --file docker/compose/docker-compose.ci-test.yml up --detach
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "${{ matrix.python-version }}"
|
python-version: "${{ matrix.python-version }}"
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
@@ -141,13 +141,13 @@ jobs:
|
|||||||
pytest
|
pytest
|
||||||
- name: Upload test results to Codecov
|
- name: Upload test results to Codecov
|
||||||
if: always()
|
if: always()
|
||||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||||
with:
|
with:
|
||||||
flags: backend-python-${{ matrix.python-version }}
|
flags: backend-python-${{ matrix.python-version }}
|
||||||
files: junit.xml
|
files: junit.xml
|
||||||
report_type: test_results
|
report_type: test_results
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||||
with:
|
with:
|
||||||
flags: backend-python-${{ matrix.python-version }}
|
flags: backend-python-${{ matrix.python-version }}
|
||||||
files: coverage.xml
|
files: coverage.xml
|
||||||
@@ -169,16 +169,16 @@ jobs:
|
|||||||
PAPERLESS_SECRET_KEY: "ci-typing-not-a-real-secret"
|
PAPERLESS_SECRET_KEY: "ci-typing-not-a-real-secret"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "${{ env.DEFAULT_PYTHON }}"
|
python-version: "${{ env.DEFAULT_PYTHON }}"
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
@@ -202,7 +202,7 @@ jobs:
|
|||||||
check \
|
check \
|
||||||
src/
|
src/
|
||||||
- name: Cache Mypy
|
- name: Cache Mypy
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: .mypy_cache
|
path: .mypy_cache
|
||||||
# Keyed by OS, Python version, and dependency hashes
|
# Keyed by OS, Python version, and dependency hashes
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
ref-name: ${{ steps.ref.outputs.name }}
|
ref-name: ${{ steps.ref.outputs.name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Determine ref name
|
- name: Determine ref name
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
docs_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.docs == 'true' }}
|
docs_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.docs == 'true' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -69,16 +69,16 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
frontend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.frontend == 'true' }}
|
frontend_changed: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.frontend == 'true' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -77,11 +77,11 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
- name: Cache frontend dependencies
|
- name: Cache frontend dependencies
|
||||||
id: cache-frontend-deps
|
id: cache-frontend-deps
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.pnpm-store
|
~/.pnpm-store
|
||||||
@@ -109,11 +109,11 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
- name: Cache frontend dependencies
|
- name: Cache frontend dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.pnpm-store
|
~/.pnpm-store
|
||||||
@@ -148,11 +148,11 @@ jobs:
|
|||||||
shard-count: [4]
|
shard-count: [4]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
@@ -162,7 +162,7 @@ jobs:
|
|||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
- name: Cache frontend dependencies
|
- name: Cache frontend dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.pnpm-store
|
~/.pnpm-store
|
||||||
@@ -174,13 +174,13 @@ jobs:
|
|||||||
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||||
- name: Upload test results to Codecov
|
- name: Upload test results to Codecov
|
||||||
if: always()
|
if: always()
|
||||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||||
with:
|
with:
|
||||||
flags: frontend-node-${{ matrix.node-version }}
|
flags: frontend-node-${{ matrix.node-version }}
|
||||||
directory: src-ui/
|
directory: src-ui/
|
||||||
report_type: test_results
|
report_type: test_results
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||||
with:
|
with:
|
||||||
flags: frontend-node-${{ matrix.node-version }}
|
flags: frontend-node-${{ matrix.node-version }}
|
||||||
directory: src-ui/coverage/
|
directory: src-ui/coverage/
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
container: mcr.microsoft.com/playwright:v1.60.0-noble
|
container: mcr.microsoft.com/playwright:v1.61.1-noble
|
||||||
env:
|
env:
|
||||||
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
|
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
||||||
@@ -203,11 +203,11 @@ jobs:
|
|||||||
shard-count: [2]
|
shard-count: [2]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
@@ -217,7 +217,7 @@ jobs:
|
|||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
- name: Cache frontend dependencies
|
- name: Cache frontend dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.pnpm-store
|
~/.pnpm-store
|
||||||
@@ -239,12 +239,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
@@ -254,7 +254,7 @@ jobs:
|
|||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
- name: Cache frontend dependencies
|
- name: Cache frontend dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.pnpm-store
|
~/.pnpm-store
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ jobs:
|
|||||||
runs-on: ubuntu-slim
|
runs-on: ubuntu-slim
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install Python
|
- name: Install Python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.14"
|
python-version: "3.14"
|
||||||
- name: Run prek
|
- name: Run prek
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
statuses: read
|
statuses: read
|
||||||
steps:
|
steps:
|
||||||
- name: Wait for Docker build
|
- name: Wait for Docker build
|
||||||
uses: lewagon/wait-on-check-action@9312864dfbc9fd208e9c0417843430751c042800 # v1.7.0
|
uses: lewagon/wait-on-check-action@96d9100b431964d10e0136aff8b9ccb92470505e # v1.8.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.sha }}
|
ref: ${{ github.sha }}
|
||||||
check-name: 'Merge and Push Manifest'
|
check-name: 'Merge and Push Manifest'
|
||||||
@@ -34,12 +34,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# ---- Frontend Build ----
|
# ---- Frontend Build ----
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
@@ -54,11 +54,11 @@ jobs:
|
|||||||
# ---- Backend Setup ----
|
# ---- Backend Setup ----
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
enable-cache: false
|
enable-cache: false
|
||||||
@@ -170,7 +170,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- name: Create release and changelog
|
- name: Create release and changelog
|
||||||
id: create-release
|
id: create-release
|
||||||
uses: release-drafter/release-drafter@693d20e7c1ce1a81d3a41962f85914253b518449 # v7.3.1
|
uses: release-drafter/release-drafter@4d75298e00d9e34c483e5ff8c68d0ea1c1940c1e # v7.5.1
|
||||||
with:
|
with:
|
||||||
name: Paperless-ngx ${{ steps.get-version.outputs.version }}
|
name: Paperless-ngx ${{ steps.get-version.outputs.version }}
|
||||||
tag: ${{ steps.get-version.outputs.version }}
|
tag: ${{ steps.get-version.outputs.version }}
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Upload release archive
|
- name: Upload release archive
|
||||||
uses: shogo82148/actions-upload-release-asset@ee2ae851dc5d938b90075b3ef12c540abfd1ee72 # v1.10.1
|
uses: shogo82148/actions-upload-release-asset@394b3c11c3cfc038b5396ad265c074065cf875c3 # v1.10.2
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||||
@@ -201,17 +201,17 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
persist-credentials: true # for pushing changelog branch
|
persist-credentials: true # for pushing changelog branch
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
enable-cache: false
|
enable-cache: false
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run zizmor
|
- name: Run zizmor
|
||||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
|
||||||
semgrep:
|
semgrep:
|
||||||
name: Semgrep CE
|
name: Semgrep CE
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
@@ -38,13 +38,13 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run Semgrep
|
- name: Run Semgrep
|
||||||
run: semgrep scan --config auto --sarif-output results.sarif
|
run: semgrep scan --config auto --sarif-output results.sarif
|
||||||
- name: Upload results to GitHub code scanning
|
- name: Upload results to GitHub code scanning
|
||||||
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ jobs:
|
|||||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -47,4 +47,4 @@ jobs:
|
|||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ jobs:
|
|||||||
environment: translation-sync
|
environment: translation-sync
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PNGX_BOT_PAT }}
|
token: ${{ secrets.PNGX_BOT_PAT }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: crowdin action
|
- name: crowdin action
|
||||||
uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2
|
uses: crowdin/github-action@52aa776766211d83d975df51f3b9c53c2f8ba35f # v2.16.3
|
||||||
with:
|
with:
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
||||||
steps:
|
steps:
|
||||||
- name: Label PR with release-drafter
|
- name: Label PR with release-drafter
|
||||||
uses: release-drafter/release-drafter@693d20e7c1ce1a81d3a41962f85914253b518449 # v7.3.1
|
uses: release-drafter/release-drafter@4d75298e00d9e34c483e5ff8c68d0ea1c1940c1e # v7.5.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
env:
|
env:
|
||||||
GH_REF: ${{ github.ref }} # sonar rule:githubactions:S7630 - avoid injection
|
GH_REF: ${{ github.ref }} # sonar rule:githubactions:S7630 - avoid injection
|
||||||
with:
|
with:
|
||||||
@@ -23,13 +23,13 @@ jobs:
|
|||||||
persist-credentials: true # for pushing translation branch
|
persist-credentials: true # for pushing translation branch
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
id: setup-python
|
id: setup-python
|
||||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -qq
|
sudo apt-get update -qq
|
||||||
sudo apt-get install -qq --no-install-recommends gettext
|
sudo apt-get install -qq --no-install-recommends gettext
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.DEFAULT_UV_VERSION }}
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
PAPERLESS_SECRET_KEY: "ci-translate-not-a-real-secret"
|
PAPERLESS_SECRET_KEY: "ci-translate-not-a-real-secret"
|
||||||
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
|
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10
|
||||||
- name: Use Node.js 24
|
- name: Use Node.js 24
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
|
||||||
- name: Cache frontend dependencies
|
- name: Cache frontend dependencies
|
||||||
id: cache-frontend-deps
|
id: cache-frontend-deps
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.pnpm-store
|
~/.pnpm-store
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ dependencies = [
|
|||||||
"llama-index-llms-ollama>=0.9.1",
|
"llama-index-llms-ollama>=0.9.1",
|
||||||
"llama-index-llms-openai-like>=0.7.1",
|
"llama-index-llms-openai-like>=0.7.1",
|
||||||
"nltk~=3.9.1",
|
"nltk~=3.9.1",
|
||||||
"ocrmypdf~=17.7.0",
|
"ocrmypdf~=17.4.2",
|
||||||
"openai>=2.32",
|
"openai>=2.32",
|
||||||
"pathvalidate~=3.3.1",
|
"pathvalidate~=3.3.1",
|
||||||
"pdf2image~=1.17.0",
|
"pdf2image~=1.17.0",
|
||||||
|
|||||||
+3
-1
@@ -155,7 +155,9 @@
|
|||||||
"builder": "@angular-builders/jest:run",
|
"builder": "@angular-builders/jest:run",
|
||||||
"options": {
|
"options": {
|
||||||
"tsConfig": "tsconfig.spec.json",
|
"tsConfig": "tsconfig.spec.json",
|
||||||
"zoneless": false
|
"zoneless": true,
|
||||||
|
"watch": false,
|
||||||
|
"coverage": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
|
|||||||
+24
-5
@@ -3,6 +3,27 @@ const angularTemplatePlugin = require('@angular-eslint/eslint-plugin-template')
|
|||||||
const angularTemplateParser = require('@angular-eslint/template-parser')
|
const angularTemplateParser = require('@angular-eslint/template-parser')
|
||||||
const tsParser = require('@typescript-eslint/parser')
|
const tsParser = require('@typescript-eslint/parser')
|
||||||
|
|
||||||
|
const angularTsRecommendedRules = {
|
||||||
|
'@angular-eslint/contextual-lifecycle': 'error',
|
||||||
|
'@angular-eslint/no-empty-lifecycle-method': 'error',
|
||||||
|
'@angular-eslint/no-input-rename': 'error',
|
||||||
|
'@angular-eslint/no-inputs-metadata-property': 'error',
|
||||||
|
'@angular-eslint/no-output-native': 'error',
|
||||||
|
'@angular-eslint/no-output-on-prefix': 'error',
|
||||||
|
'@angular-eslint/no-output-rename': 'error',
|
||||||
|
'@angular-eslint/no-outputs-metadata-property': 'error',
|
||||||
|
'@angular-eslint/prefer-inject': 'error',
|
||||||
|
'@angular-eslint/prefer-standalone': 'error',
|
||||||
|
'@angular-eslint/use-pipe-transform-interface': 'error',
|
||||||
|
'@angular-eslint/use-lifecycle-interface': 'warn',
|
||||||
|
}
|
||||||
|
const angularTemplateRecommendedRules = {
|
||||||
|
'@angular-eslint/template/banana-in-box': 'error',
|
||||||
|
'@angular-eslint/template/eqeqeq': 'error',
|
||||||
|
'@angular-eslint/template/no-negated-async': 'error',
|
||||||
|
'@angular-eslint/template/prefer-control-flow': 'error',
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
ignores: ['projects/**/*', 'src/app/components/common/pdf-viewer/**'],
|
ignores: ['projects/**/*', 'src/app/components/common/pdf-viewer/**'],
|
||||||
@@ -12,8 +33,6 @@ module.exports = [
|
|||||||
languageOptions: {
|
languageOptions: {
|
||||||
parser: tsParser,
|
parser: tsParser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['tsconfig.json'],
|
|
||||||
createDefaultProgram: true,
|
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
},
|
},
|
||||||
@@ -22,9 +41,9 @@ module.exports = [
|
|||||||
'@angular-eslint': angularEslintPlugin,
|
'@angular-eslint': angularEslintPlugin,
|
||||||
'@angular-eslint/template': angularTemplatePlugin,
|
'@angular-eslint/template': angularTemplatePlugin,
|
||||||
},
|
},
|
||||||
processor: '@angular-eslint/template/extract-inline-html',
|
processor: angularTemplatePlugin.processors['extract-inline-html'],
|
||||||
rules: {
|
rules: {
|
||||||
...angularEslintPlugin.configs.recommended.rules,
|
...angularTsRecommendedRules,
|
||||||
'@angular-eslint/directive-selector': [
|
'@angular-eslint/directive-selector': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
@@ -52,7 +71,7 @@ module.exports = [
|
|||||||
'@angular-eslint/template': angularTemplatePlugin,
|
'@angular-eslint/template': angularTemplatePlugin,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...angularTemplatePlugin.configs.recommended.rules,
|
...angularTemplateRecommendedRules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
+49
-49
@@ -1277,7 +1277,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1762</context>
|
<context context-type="linenumber">1760</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1577733187050997705" datatype="html">
|
<trans-unit id="1577733187050997705" datatype="html">
|
||||||
@@ -2177,7 +2177,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">637</context>
|
<context context-type="linenumber">635</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-version-dropdown/document-version-dropdown.component.html</context>
|
||||||
@@ -3080,11 +3080,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1381</context>
|
<context context-type="linenumber">1379</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1763</context>
|
<context context-type="linenumber">1761</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
@@ -3655,7 +3655,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1334</context>
|
<context context-type="linenumber">1332</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
@@ -3767,7 +3767,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1816</context>
|
<context context-type="linenumber">1814</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6661109599266152398" datatype="html">
|
<trans-unit id="6661109599266152398" datatype="html">
|
||||||
@@ -3778,7 +3778,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1817</context>
|
<context context-type="linenumber">1815</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5162686434580248853" datatype="html">
|
<trans-unit id="5162686434580248853" datatype="html">
|
||||||
@@ -3789,7 +3789,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1818</context>
|
<context context-type="linenumber">1816</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8157388568390631653" datatype="html">
|
<trans-unit id="8157388568390631653" datatype="html">
|
||||||
@@ -5691,7 +5691,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1338</context>
|
<context context-type="linenumber">1336</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
@@ -7933,88 +7933,88 @@
|
|||||||
<source>Enter Password</source>
|
<source>Enter Password</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||||
<context context-type="linenumber">512</context>
|
<context context-type="linenumber">513</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5758784066858623886" datatype="html">
|
<trans-unit id="5758784066858623886" datatype="html">
|
||||||
<source>Error retrieving metadata</source>
|
<source>Error retrieving metadata</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">411</context>
|
<context context-type="linenumber">409</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2218903673684131427" datatype="html">
|
<trans-unit id="2218903673684131427" datatype="html">
|
||||||
<source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source>
|
<source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">512,514</context>
|
<context context-type="linenumber">510,512</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">959,961</context>
|
<context context-type="linenumber">957,959</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6357361810318120957" datatype="html">
|
<trans-unit id="6357361810318120957" datatype="html">
|
||||||
<source>Document was updated</source>
|
<source>Document was updated</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">632</context>
|
<context context-type="linenumber">630</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5154064822428631306" datatype="html">
|
<trans-unit id="5154064822428631306" datatype="html">
|
||||||
<source>Document was updated at <x id="PH" equiv-text="formattedModified"/>.</source>
|
<source>Document was updated at <x id="PH" equiv-text="formattedModified"/>.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">633</context>
|
<context context-type="linenumber">631</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8462497568316256794" datatype="html">
|
<trans-unit id="8462497568316256794" datatype="html">
|
||||||
<source>Reload to discard your local unsaved edits and load the latest remote version.</source>
|
<source>Reload to discard your local unsaved edits and load the latest remote version.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">634</context>
|
<context context-type="linenumber">632</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7967484035994732534" datatype="html">
|
<trans-unit id="7967484035994732534" datatype="html">
|
||||||
<source>Reload</source>
|
<source>Reload</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">636</context>
|
<context context-type="linenumber">634</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2907037627372942104" datatype="html">
|
<trans-unit id="2907037627372942104" datatype="html">
|
||||||
<source>Document reloaded with latest changes.</source>
|
<source>Document reloaded with latest changes.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">692</context>
|
<context context-type="linenumber">690</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6435639868943916539" datatype="html">
|
<trans-unit id="6435639868943916539" datatype="html">
|
||||||
<source>Document reloaded.</source>
|
<source>Document reloaded.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">703</context>
|
<context context-type="linenumber">701</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6142395741265832184" datatype="html">
|
<trans-unit id="6142395741265832184" datatype="html">
|
||||||
<source>Next document</source>
|
<source>Next document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">805</context>
|
<context context-type="linenumber">803</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="651985345816518480" datatype="html">
|
<trans-unit id="651985345816518480" datatype="html">
|
||||||
<source>Previous document</source>
|
<source>Previous document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">815</context>
|
<context context-type="linenumber">813</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2885986061416655600" datatype="html">
|
<trans-unit id="2885986061416655600" datatype="html">
|
||||||
<source>Close document</source>
|
<source>Close document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">823</context>
|
<context context-type="linenumber">821</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
|
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
|
||||||
@@ -8025,67 +8025,67 @@
|
|||||||
<source>Save document</source>
|
<source>Save document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">830</context>
|
<context context-type="linenumber">828</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1784543155727940353" datatype="html">
|
<trans-unit id="1784543155727940353" datatype="html">
|
||||||
<source>Save and close / next</source>
|
<source>Save and close / next</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">839</context>
|
<context context-type="linenumber">837</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7427704425579737895" datatype="html">
|
<trans-unit id="7427704425579737895" datatype="html">
|
||||||
<source>Error retrieving version content</source>
|
<source>Error retrieving version content</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">943</context>
|
<context context-type="linenumber">941</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3456881259945295697" datatype="html">
|
<trans-unit id="3456881259945295697" datatype="html">
|
||||||
<source>Error retrieving suggestions.</source>
|
<source>Error retrieving suggestions.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1002</context>
|
<context context-type="linenumber">1000</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2194092841814123758" datatype="html">
|
<trans-unit id="2194092841814123758" datatype="html">
|
||||||
<source>Document "<x id="PH" equiv-text="newValues.title"/>" saved successfully.</source>
|
<source>Document "<x id="PH" equiv-text="newValues.title"/>" saved successfully.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1214</context>
|
<context context-type="linenumber">1212</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1241</context>
|
<context context-type="linenumber">1239</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6626387786259219838" datatype="html">
|
<trans-unit id="6626387786259219838" datatype="html">
|
||||||
<source>Error saving document "<x id="PH" equiv-text="this.document.title"/>"</source>
|
<source>Error saving document "<x id="PH" equiv-text="this.document.title"/>"</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1247</context>
|
<context context-type="linenumber">1245</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="448882439049417053" datatype="html">
|
<trans-unit id="448882439049417053" datatype="html">
|
||||||
<source>Error saving document</source>
|
<source>Error saving document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1302</context>
|
<context context-type="linenumber">1300</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8410796510716511826" datatype="html">
|
<trans-unit id="8410796510716511826" datatype="html">
|
||||||
<source>Do you really want to move the document "<x id="PH" equiv-text="this.document.title"/>" to the trash?</source>
|
<source>Do you really want to move the document "<x id="PH" equiv-text="this.document.title"/>" to the trash?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1335</context>
|
<context context-type="linenumber">1333</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="282586936710748252" datatype="html">
|
<trans-unit id="282586936710748252" datatype="html">
|
||||||
<source>Documents can be restored prior to permanent deletion.</source>
|
<source>Documents can be restored prior to permanent deletion.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1336</context>
|
<context context-type="linenumber">1334</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
@@ -8096,14 +8096,14 @@
|
|||||||
<source>Error deleting document</source>
|
<source>Error deleting document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1357</context>
|
<context context-type="linenumber">1355</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="619486176823357521" datatype="html">
|
<trans-unit id="619486176823357521" datatype="html">
|
||||||
<source>Reprocess confirm</source>
|
<source>Reprocess confirm</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1377</context>
|
<context context-type="linenumber">1375</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
@@ -8114,102 +8114,102 @@
|
|||||||
<source>This operation will permanently recreate the archive file for this document.</source>
|
<source>This operation will permanently recreate the archive file for this document.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1378</context>
|
<context context-type="linenumber">1376</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="302054111564709516" datatype="html">
|
<trans-unit id="302054111564709516" datatype="html">
|
||||||
<source>The archive file will be re-generated with the current settings.</source>
|
<source>The archive file will be re-generated with the current settings.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1379</context>
|
<context context-type="linenumber">1377</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4700389117298802932" datatype="html">
|
<trans-unit id="4700389117298802932" datatype="html">
|
||||||
<source>Reprocess operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source>
|
<source>Reprocess operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1389</context>
|
<context context-type="linenumber">1387</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4409560272830824468" datatype="html">
|
<trans-unit id="4409560272830824468" datatype="html">
|
||||||
<source>Error executing operation</source>
|
<source>Error executing operation</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1400</context>
|
<context context-type="linenumber">1398</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6030453331794586802" datatype="html">
|
<trans-unit id="6030453331794586802" datatype="html">
|
||||||
<source>Error downloading document</source>
|
<source>Error downloading document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1463</context>
|
<context context-type="linenumber">1461</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4458954481601077369" datatype="html">
|
<trans-unit id="4458954481601077369" datatype="html">
|
||||||
<source>Page Fit</source>
|
<source>Page Fit</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1543</context>
|
<context context-type="linenumber">1541</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4663705961777238777" datatype="html">
|
<trans-unit id="4663705961777238777" datatype="html">
|
||||||
<source>PDF edit operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source>
|
<source>PDF edit operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1783</context>
|
<context context-type="linenumber">1781</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9043972994040261999" datatype="html">
|
<trans-unit id="9043972994040261999" datatype="html">
|
||||||
<source>Error executing PDF edit operation</source>
|
<source>Error executing PDF edit operation</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1795</context>
|
<context context-type="linenumber">1793</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6172690334763056188" datatype="html">
|
<trans-unit id="6172690334763056188" datatype="html">
|
||||||
<source>Please enter the current password before attempting to remove it.</source>
|
<source>Please enter the current password before attempting to remove it.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1806</context>
|
<context context-type="linenumber">1804</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="968660764814228922" datatype="html">
|
<trans-unit id="968660764814228922" datatype="html">
|
||||||
<source>Password removal operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source>
|
<source>Password removal operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1840</context>
|
<context context-type="linenumber">1838</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2282118435712883014" datatype="html">
|
<trans-unit id="2282118435712883014" datatype="html">
|
||||||
<source>Error executing password removal operation</source>
|
<source>Error executing password removal operation</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1854</context>
|
<context context-type="linenumber">1852</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3740891324955700797" datatype="html">
|
<trans-unit id="3740891324955700797" datatype="html">
|
||||||
<source>Print failed.</source>
|
<source>Print failed.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1900</context>
|
<context context-type="linenumber">1898</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6457245677384603573" datatype="html">
|
<trans-unit id="6457245677384603573" datatype="html">
|
||||||
<source>Error loading document for printing.</source>
|
<source>Error loading document for printing.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1913</context>
|
<context context-type="linenumber">1911</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6085793215710522488" datatype="html">
|
<trans-unit id="6085793215710522488" datatype="html">
|
||||||
<source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source>
|
<source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1978</context>
|
<context context-type="linenumber">1976</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
<context context-type="linenumber">1982</context>
|
<context context-type="linenumber">1980</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4958946940233632319" datatype="html">
|
<trans-unit id="4958946940233632319" datatype="html">
|
||||||
|
|||||||
+38
-48
@@ -6,22 +6,22 @@
|
|||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"test": "ng test --no-watch --coverage",
|
"test": "ng test",
|
||||||
"lint": "ng lint"
|
"lint": "ng lint"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/cdk": "^21.2.12",
|
"@angular/cdk": "^22.0.3",
|
||||||
"@angular/common": "~21.2.17",
|
"@angular/common": "~22.0.5",
|
||||||
"@angular/compiler": "~21.2.17",
|
"@angular/compiler": "~22.0.5",
|
||||||
"@angular/core": "~21.2.17",
|
"@angular/core": "~22.0.5",
|
||||||
"@angular/forms": "~21.2.14",
|
"@angular/forms": "~22.0.5",
|
||||||
"@angular/localize": "~21.2.14",
|
"@angular/localize": "~22.0.5",
|
||||||
"@angular/platform-browser": "~21.2.14",
|
"@angular/platform-browser": "~22.0.5",
|
||||||
"@angular/platform-browser-dynamic": "~21.2.14",
|
"@angular/platform-browser-dynamic": "~22.0.5",
|
||||||
"@angular/router": "~21.2.14",
|
"@angular/router": "~22.0.5",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^20.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^21.0.0",
|
||||||
"@ng-select/ng-select": "^21.8.2",
|
"@ng-select/ng-select": "^23.2.0",
|
||||||
"@ngneat/dirty-check-forms": "^3.0.3",
|
"@ngneat/dirty-check-forms": "^3.0.3",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
@@ -29,56 +29,46 @@
|
|||||||
"mime-names": "^1.0.0",
|
"mime-names": "^1.0.0",
|
||||||
"ngx-bootstrap-icons": "^1.9.3",
|
"ngx-bootstrap-icons": "^1.9.3",
|
||||||
"ngx-color": "^10.1.0",
|
"ngx-color": "^10.1.0",
|
||||||
"ngx-cookie-service": "^21.3.1",
|
"ngx-cookie-service": "^22.0.0",
|
||||||
"ngx-device-detector": "^11.0.0",
|
"ngx-device-detector": "^12.0.0",
|
||||||
"ngx-ui-tour-ng-bootstrap": "^18.0.0",
|
"ngx-ui-tour-ng-bootstrap": "^19.0.0",
|
||||||
"normalize-diacritics": "^5.0.0",
|
"normalize-diacritics": "^5.0.0",
|
||||||
"pdfjs-dist": "^5.7.284",
|
"pdfjs-dist": "^6.0.227",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"utif": "^3.1.0",
|
"utif": "^3.1.0",
|
||||||
"uuid": "^14.0.0",
|
"uuid": "^14.0.1"
|
||||||
"zone.js": "^0.16.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "^21.0.3",
|
"@angular-builders/custom-webpack": "^22.0.1",
|
||||||
"@angular-builders/jest": "^21.0.3",
|
"@angular-builders/jest": "^22.0.1",
|
||||||
"@angular-devkit/core": "^21.2.12",
|
"@angular-devkit/core": "^22.0.5",
|
||||||
"@angular-devkit/schematics": "^21.2.12",
|
"@angular-devkit/schematics": "^22.0.5",
|
||||||
"@angular-eslint/builder": "21.4.0",
|
"@angular-eslint/builder": "22.0.0",
|
||||||
"@angular-eslint/eslint-plugin": "21.4.0",
|
"@angular-eslint/eslint-plugin": "22.0.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "21.4.0",
|
"@angular-eslint/eslint-plugin-template": "22.0.0",
|
||||||
"@angular-eslint/schematics": "21.4.0",
|
"@angular-eslint/schematics": "22.0.0",
|
||||||
"@angular-eslint/template-parser": "21.4.0",
|
"@angular-eslint/template-parser": "22.0.0",
|
||||||
"@angular/build": "^21.2.12",
|
"@angular/build": "^22.0.5",
|
||||||
"@angular/cli": "~21.2.12",
|
"@angular/cli": "~22.0.5",
|
||||||
"@angular/compiler-cli": "~21.2.14",
|
"@angular/compiler-cli": "~22.0.5",
|
||||||
"@codecov/webpack-plugin": "^2.0.1",
|
"@codecov/webpack-plugin": "^2.0.1",
|
||||||
"@playwright/test": "^1.60.0",
|
"@playwright/test": "^1.61.1",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^25.9.1",
|
"@types/node": "^26.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.60.0",
|
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||||
"@typescript-eslint/parser": "^8.60.0",
|
"@typescript-eslint/parser": "^8.62.0",
|
||||||
"@typescript-eslint/utils": "^8.60.0",
|
"@typescript-eslint/utils": "^8.62.0",
|
||||||
"eslint": "^10.4.0",
|
"eslint": "^10.5.0",
|
||||||
"jest": "30.4.2",
|
"jest": "30.4.2",
|
||||||
"jest-environment-jsdom": "^30.4.1",
|
"jest-environment-jsdom": "^30.4.1",
|
||||||
"jest-junit": "^17.0.0",
|
"jest-junit": "^17.0.0",
|
||||||
"jest-preset-angular": "^16.1.5",
|
"jest-preset-angular": "^17.0.0",
|
||||||
"jest-websocket-mock": "^2.5.0",
|
"jest-websocket-mock": "^2.5.0",
|
||||||
"prettier-plugin-organize-imports": "^4.3.0",
|
"prettier-plugin-organize-imports": "^4.3.0",
|
||||||
"ts-node": "~10.9.1",
|
"ts-node": "~10.9.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^6.0.3",
|
||||||
"webpack": "^5.107.2"
|
"webpack": "^5.107.2"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.17.1",
|
"packageManager": "pnpm@10.17.1"
|
||||||
"pnpm": {
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"@parcel/watcher",
|
|
||||||
"canvas",
|
|
||||||
"esbuild",
|
|
||||||
"lmdb",
|
|
||||||
"msgpackr-extract"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2283
-2890
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
|||||||
|
packages:
|
||||||
|
- "."
|
||||||
|
allowBuilds:
|
||||||
|
"@parcel/watcher": true
|
||||||
|
canvas: true
|
||||||
|
esbuild: true
|
||||||
|
lmdb: true
|
||||||
|
msgpackr-extract: true
|
||||||
|
normalize-diacritics: false
|
||||||
|
unrs-resolver: false
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- "@parcel/watcher"
|
||||||
|
- canvas
|
||||||
|
- esbuild
|
||||||
|
- lmdb
|
||||||
|
- msgpackr-extract
|
||||||
|
ignoredBuiltDependencies:
|
||||||
|
- normalize-diacritics
|
||||||
|
- unrs-resolver
|
||||||
@@ -11,30 +11,30 @@
|
|||||||
type="number"
|
type="number"
|
||||||
min="100"
|
min="100"
|
||||||
step="100"
|
step="100"
|
||||||
[(ngModel)]="limit"
|
[ngModel]="limit()"
|
||||||
(ngModelChange)="onLimitChange($event)"
|
(ngModelChange)="limit.set($event); onLimitChange($event)"
|
||||||
style="width: 100px;">
|
style="width: 100px;">
|
||||||
<span class="input-group-text text-muted" i18n>lines</span>
|
<span class="input-group-text text-muted" i18n>lines</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch mt-1">
|
<div class="form-check form-switch mt-1">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
|
<input class="form-check-input" type="checkbox" role="switch" [ngModel]="autoRefreshEnabled()" (ngModelChange)="autoRefreshEnabled.set($event)">
|
||||||
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
|
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
|
|
||||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeLog" (activeIdChange)="reloadLogs()" class="nav-tabs">
|
<ul ngbNav #nav="ngbNav" [activeId]="activeLog()" (activeIdChange)="activeLog.set($event); reloadLogs()" class="nav-tabs">
|
||||||
@for (logFile of logFiles; track logFile) {
|
@for (logFile of logFiles(); track logFile) {
|
||||||
<li [ngbNavItem]="logFile">
|
<li [ngbNavItem]="logFile">
|
||||||
<a ngbNavLink>
|
<a ngbNavLink>
|
||||||
{{logFile}}.log
|
{{logFile}}.log
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
@if (loading || !logFiles.length) {
|
@if (loading || !logFiles().length) {
|
||||||
<div class="ps-2 d-flex align-items-center">
|
<div class="ps-2 d-flex align-items-center">
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
@if (!logFiles.length) {
|
@if (!logFiles().length) {
|
||||||
<ng-container i18n>Loading...</ng-container>
|
<ng-container i18n>Loading...</ng-container>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -42,13 +42,13 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div #logContainer class="bg-dark text-light font-monospace log-container p-3" (scroll)="onScroll()">
|
<div #logContainer class="bg-dark text-light font-monospace log-container p-3" (scroll)="onScroll()">
|
||||||
@if (loading && !logFiles.length) {
|
@if (loading && !logFiles().length) {
|
||||||
<div>
|
<div>
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
<ng-container i18n>Loading...</ng-container>
|
<ng-container i18n>Loading...</ng-container>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
@for (log of logs; track log) {
|
@for (log of logs(); track log) {
|
||||||
<p class="m-0 p-0" [ngClass]="'log-entry-' + log.level">{{log.message}}</p>
|
<p class="m-0 p-0" [ngClass]="'log-entry-' + log.level">{{log.message}}</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-secondary jump-to-bottom position-fixed bottom-0 end-0 m-5"
|
class="btn btn-sm btn-secondary jump-to-bottom position-fixed bottom-0 end-0 m-5"
|
||||||
[class.visible]="showJumpToBottom"
|
[class.visible]="showJumpToBottom()"
|
||||||
(click)="scrollToBottom()"
|
(click)="scrollToBottom()"
|
||||||
>
|
>
|
||||||
↓ <span i18n>Jump to bottom</span>
|
↓ <span i18n>Jump to bottom</span>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ describe('LogsComponent', () => {
|
|||||||
throwError(() => new Error('error getting logs'))
|
throwError(() => new Error('error getting logs'))
|
||||||
)
|
)
|
||||||
component.reloadLogs()
|
component.reloadLogs()
|
||||||
expect(component.logs).toHaveLength(0)
|
expect(component.logs()).toHaveLength(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should auto refresh, allow toggle', () => {
|
it('should auto refresh, allow toggle', () => {
|
||||||
@@ -97,7 +97,7 @@ describe('LogsComponent', () => {
|
|||||||
jest.advanceTimersByTime(6000)
|
jest.advanceTimersByTime(6000)
|
||||||
expect(reloadSpy).toHaveBeenCalledTimes(2)
|
expect(reloadSpy).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
component.autoRefreshEnabled = false
|
component.autoRefreshEnabled.set(false)
|
||||||
jest.advanceTimersByTime(6000)
|
jest.advanceTimersByTime(6000)
|
||||||
expect(reloadSpy).toHaveBeenCalledTimes(2)
|
expect(reloadSpy).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
@@ -112,9 +112,9 @@ describe('LogsComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should update jump to bottom visibility on scroll', () => {
|
it('should update jump to bottom visibility on scroll', () => {
|
||||||
component.showJumpToBottom = false
|
component.showJumpToBottom.set(false)
|
||||||
jest.spyOn(component as any, 'isNearBottom').mockReturnValue(false)
|
jest.spyOn(component as any, 'isNearBottom').mockReturnValue(false)
|
||||||
component.onScroll()
|
component.onScroll()
|
||||||
expect(component.showJumpToBottom).toBe(true)
|
expect(component.showJumpToBottom()).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
@@ -34,17 +35,17 @@ export class LogsComponent
|
|||||||
private logService = inject(LogService)
|
private logService = inject(LogService)
|
||||||
private changedetectorRef = inject(ChangeDetectorRef)
|
private changedetectorRef = inject(ChangeDetectorRef)
|
||||||
|
|
||||||
public logs: Array<{ message: string; level: number }> = []
|
readonly logs = signal<Array<{ message: string; level: number }>>([])
|
||||||
|
|
||||||
public logFiles: string[] = []
|
readonly logFiles = signal<string[]>([])
|
||||||
|
|
||||||
public activeLog: string
|
readonly activeLog = signal<string>(undefined)
|
||||||
|
|
||||||
public autoRefreshEnabled: boolean = true
|
readonly autoRefreshEnabled = signal<boolean>(true)
|
||||||
|
|
||||||
public limit: number = 5000
|
readonly limit = signal<number>(5000)
|
||||||
|
|
||||||
public showJumpToBottom = false
|
readonly showJumpToBottom = signal<boolean>(false)
|
||||||
|
|
||||||
private readonly limitChange$ = new Subject<number>()
|
private readonly limitChange$ = new Subject<number>()
|
||||||
|
|
||||||
@@ -59,15 +60,15 @@ export class LogsComponent
|
|||||||
.list()
|
.list()
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((result) => {
|
.subscribe((result) => {
|
||||||
this.logFiles = result
|
this.logFiles.set(result)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
if (this.logFiles.length > 0) {
|
if (this.logFiles().length > 0) {
|
||||||
this.activeLog = this.logFiles[0]
|
this.activeLog.set(this.logFiles()[0])
|
||||||
this.reloadLogs()
|
this.reloadLogs()
|
||||||
}
|
}
|
||||||
timer(5000, 5000)
|
timer(5000, 5000)
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(() => this.autoRefreshEnabled),
|
filter(() => this.autoRefreshEnabled()),
|
||||||
takeUntil(this.unsubscribeNotifier)
|
takeUntil(this.unsubscribeNotifier)
|
||||||
)
|
)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
@@ -88,16 +89,16 @@ export class LogsComponent
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
const shouldStickToBottom = this.isNearBottom()
|
const shouldStickToBottom = this.isNearBottom()
|
||||||
this.logService
|
this.logService
|
||||||
.get(this.activeLog, this.limit)
|
.get(this.activeLog(), this.limit())
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (result) => {
|
next: (result) => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
const parsed = this.parseLogsWithLevel(result)
|
const parsed = this.parseLogsWithLevel(result)
|
||||||
const hasChanges =
|
const hasChanges =
|
||||||
parsed.length !== this.logs.length ||
|
parsed.length !== this.logs().length ||
|
||||||
parsed.some((log, idx) => {
|
parsed.some((log, idx) => {
|
||||||
const current = this.logs[idx]
|
const current = this.logs()[idx]
|
||||||
return (
|
return (
|
||||||
!current ||
|
!current ||
|
||||||
current.message !== log.message ||
|
current.message !== log.message ||
|
||||||
@@ -105,15 +106,15 @@ export class LogsComponent
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
this.logs = parsed
|
this.logs.set(parsed)
|
||||||
if (shouldStickToBottom) {
|
if (shouldStickToBottom) {
|
||||||
this.scrollToBottom()
|
this.scrollToBottom()
|
||||||
}
|
}
|
||||||
this.showJumpToBottom = !shouldStickToBottom
|
this.showJumpToBottom.set(!shouldStickToBottom)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
this.logs = []
|
this.logs.set([])
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -149,7 +150,7 @@ export class LogsComponent
|
|||||||
}
|
}
|
||||||
this.changedetectorRef.detectChanges()
|
this.changedetectorRef.detectChanges()
|
||||||
viewport.scrollTop = viewport.scrollHeight
|
viewport.scrollTop = viewport.scrollHeight
|
||||||
this.showJumpToBottom = false
|
this.showJumpToBottom.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private isNearBottom(): boolean {
|
private isNearBottom(): boolean {
|
||||||
@@ -162,6 +163,6 @@ export class LogsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
onScroll(): void {
|
onScroll(): void {
|
||||||
this.showJumpToBottom = !this.isNearBottom()
|
this.showJumpToBottom.set(!this.isNearBottom())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<button class="btn btn-sm btn-outline-primary me-2" (click)="dismissTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="visibleTasks.length === 0">
|
<button class="btn btn-sm btn-outline-primary me-2" (click)="dismissTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="visibleTasks.length === 0">
|
||||||
<i-bs name="check2-all" class="me-1"></i-bs>{{dismissButtonText}}
|
<i-bs name="check2-all" class="me-1"></i-bs>{{dismissButtonText}}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-primary me-2" (click)="dismissAllTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="totalTasks === 0">
|
<button class="btn btn-sm btn-outline-primary me-2" (click)="dismissAllTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="totalTasks() === 0">
|
||||||
<i-bs name="check2-all" class="me-1"></i-bs><ng-container i18n>Dismiss all</ng-container>
|
<i-bs name="check2-all" class="me-1"></i-bs><ng-container i18n>Dismiss all</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<div class="form-check form-switch mb-0 ms-2">
|
<div class="form-check form-switch mb-0 ms-2">
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
|
|
||||||
<ngb-pagination
|
<ngb-pagination
|
||||||
[pageSize]="pageSize"
|
[pageSize]="pageSize"
|
||||||
[collectionSize]="totalTasks"
|
[collectionSize]="totalTasks()"
|
||||||
[page]="page"
|
[page]="page"
|
||||||
[maxSize]="5"
|
[maxSize]="5"
|
||||||
[rotate]="true"
|
[rotate]="true"
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ describe('TasksComponent', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
req.flush({ count: 2, results: [tasks[0], tasks[1]] })
|
req.flush({ count: 2, results: [tasks[0], tasks[1]] })
|
||||||
expect(component.totalTasks).toBe(2)
|
expect(component.totalTasks()).toBe(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply task type and trigger source filters to the server-side task query', () => {
|
it('should apply task type and trigger source filters to the server-side task query', () => {
|
||||||
@@ -435,8 +435,8 @@ describe('TasksComponent', () => {
|
|||||||
.flush(pageTwoTasks)
|
.flush(pageTwoTasks)
|
||||||
|
|
||||||
expect(component.page).toBe(2)
|
expect(component.page).toBe(2)
|
||||||
expect(component.totalTasks).toBe(30)
|
expect(component.totalTasks()).toBe(30)
|
||||||
expect(component.pagedTasks).toEqual([tasks[0]])
|
expect(component.pagedTasks()).toEqual([tasks[0]])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not replace section counts with current-page counts', () => {
|
it('should not replace section counts with current-page counts', () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { JsonPipe, NgTemplateOutlet } from '@angular/common'
|
import { JsonPipe, NgTemplateOutlet } from '@angular/common'
|
||||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core'
|
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { Router, RouterLink } from '@angular/router'
|
import { Router, RouterLink } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
@@ -166,14 +166,14 @@ export class TasksComponent
|
|||||||
public autoRefreshEnabled: boolean = true
|
public autoRefreshEnabled: boolean = true
|
||||||
public readonly pageSize = 25
|
public readonly pageSize = 25
|
||||||
public page: number = 1
|
public page: number = 1
|
||||||
public totalTasks: number = 0
|
readonly totalTasks = signal(0)
|
||||||
public sectionCounts: Record<TaskSection, number> = {
|
readonly sectionCounts = signal<Record<TaskSection, number>>({
|
||||||
[TaskSection.All]: 0,
|
[TaskSection.All]: 0,
|
||||||
[TaskSection.NeedsAttention]: 0,
|
[TaskSection.NeedsAttention]: 0,
|
||||||
[TaskSection.InProgress]: 0,
|
[TaskSection.InProgress]: 0,
|
||||||
[TaskSection.Completed]: 0,
|
[TaskSection.Completed]: 0,
|
||||||
}
|
})
|
||||||
public pagedTasks: PaperlessTask[] = []
|
readonly pagedTasks = signal<PaperlessTask[]>([])
|
||||||
public selectedSection: TaskSection = TaskSection.All
|
public selectedSection: TaskSection = TaskSection.All
|
||||||
public selectedTaskType: PaperlessTaskType | null = null
|
public selectedTaskType: PaperlessTaskType | null = null
|
||||||
public selectedTriggerSource: PaperlessTaskTriggerSource | null = null
|
public selectedTriggerSource: PaperlessTaskTriggerSource | null = null
|
||||||
@@ -346,7 +346,7 @@ export class TasksComponent
|
|||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Confirm Dismiss All`
|
modal.componentInstance.title = $localize`Confirm Dismiss All`
|
||||||
modal.componentInstance.messageBold = $localize`Dismiss all ${this.totalTasks} tasks?`
|
modal.componentInstance.messageBold = $localize`Dismiss all ${this.totalTasks()} tasks?`
|
||||||
modal.componentInstance.btnClass = 'btn-warning'
|
modal.componentInstance.btnClass = 'btn-warning'
|
||||||
modal.componentInstance.btnCaption = $localize`Dismiss`
|
modal.componentInstance.btnCaption = $localize`Dismiss`
|
||||||
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
@@ -465,7 +465,7 @@ export class TasksComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasksForSection(section: TaskSection): PaperlessTask[] {
|
tasksForSection(section: TaskSection): PaperlessTask[] {
|
||||||
let tasks = this.pagedTasks.filter((task) =>
|
let tasks = this.pagedTasks().filter((task) =>
|
||||||
this.taskBelongsToSection(task, section)
|
this.taskBelongsToSection(task, section)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -477,7 +477,14 @@ export class TasksComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
sectionCount(section: TaskSection): number {
|
sectionCount(section: TaskSection): number {
|
||||||
return this.sectionCounts[section]
|
return this.sectionCounts()[section]
|
||||||
|
}
|
||||||
|
|
||||||
|
private setSectionCount(section: TaskSection, count: number) {
|
||||||
|
this.sectionCounts.update((counts) => ({
|
||||||
|
...counts,
|
||||||
|
[section]: count,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionShowsResults(section: TaskSection): boolean {
|
sectionShowsResults(section: TaskSection): boolean {
|
||||||
@@ -652,7 +659,7 @@ export class TasksComponent
|
|||||||
? this.sections
|
? this.sections
|
||||||
: [this.selectedSection]
|
: [this.selectedSection]
|
||||||
|
|
||||||
return this.pagedTasks.filter(
|
return this.pagedTasks().filter(
|
||||||
(task) =>
|
(task) =>
|
||||||
sections.some((section) => this.taskBelongsToSection(task, section)) &&
|
sections.some((section) => this.taskBelongsToSection(task, section)) &&
|
||||||
this.taskMatchesFilters(task, { taskType, triggerSource })
|
this.taskMatchesFilters(task, { taskType, triggerSource })
|
||||||
@@ -664,10 +671,12 @@ export class TasksComponent
|
|||||||
.statusCounts(this.getParamsForSection(TaskSection.All))
|
.statusCounts(this.getParamsForSection(TaskSection.All))
|
||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((counts) => {
|
.subscribe((counts) => {
|
||||||
this.sectionCounts[TaskSection.All] = counts.all
|
this.sectionCounts.set({
|
||||||
this.sectionCounts[TaskSection.NeedsAttention] = counts.needs_attention
|
[TaskSection.All]: counts.all,
|
||||||
this.sectionCounts[TaskSection.InProgress] = counts.in_progress
|
[TaskSection.NeedsAttention]: counts.needs_attention,
|
||||||
this.sectionCounts[TaskSection.Completed] = counts.completed
|
[TaskSection.InProgress]: counts.in_progress,
|
||||||
|
[TaskSection.Completed]: counts.completed,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,17 +742,17 @@ export class TasksComponent
|
|||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (result) => {
|
next: (result) => {
|
||||||
this.pagedTasks = result.results
|
this.pagedTasks.set(result.results)
|
||||||
this.totalTasks = result.count
|
this.totalTasks.set(result.count)
|
||||||
this.sectionCounts[TaskSection.All] = result.count
|
this.setSectionCount(TaskSection.All, result.count)
|
||||||
if (this.selectedSection !== TaskSection.All) {
|
if (this.selectedSection !== TaskSection.All) {
|
||||||
this.sectionCounts[this.selectedSection] = result.count
|
this.setSectionCount(this.selectedSection, result.count)
|
||||||
}
|
}
|
||||||
this.loading = false
|
this.loading = false
|
||||||
if (
|
if (
|
||||||
this.page > 1 &&
|
this.page > 1 &&
|
||||||
this.pagedTasks.length === 0 &&
|
this.pagedTasks().length === 0 &&
|
||||||
this.totalTasks > 0
|
this.totalTasks() > 0
|
||||||
) {
|
) {
|
||||||
this.page -= 1
|
this.page -= 1
|
||||||
this.reloadPage()
|
this.reloadPage()
|
||||||
|
|||||||
@@ -4,22 +4,22 @@
|
|||||||
info="Manage trashed documents that are pending deletion."
|
info="Manage trashed documents that are pending deletion."
|
||||||
i18n-info
|
i18n-info
|
||||||
infoLink="usage/#document-trash">
|
infoLink="usage/#document-trash">
|
||||||
<button class="btn btn-sm btn-outline-secondary" (click)="clearSelection()" [hidden]="selectedDocuments.size === 0">
|
<button class="btn btn-sm btn-outline-secondary" (click)="clearSelection()" [hidden]="selectedDocuments().size === 0">
|
||||||
<i-bs name="x" class="me-1"></i-bs><ng-container i18n>Clear selection</ng-container>
|
<i-bs name="x" class="me-1"></i-bs><ng-container i18n>Clear selection</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="restoreAll(selectedDocuments)" [disabled]="selectedDocuments.size === 0">
|
<button type="button" class="btn btn-sm btn-outline-primary" (click)="restoreAll(selectedDocuments())" [disabled]="selectedDocuments().size === 0">
|
||||||
<i-bs name="arrow-counterclockwise" class="me-1"></i-bs><ng-container i18n>Restore selected</ng-container>
|
<i-bs name="arrow-counterclockwise" class="me-1"></i-bs><ng-container i18n>Restore selected</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash(selectedDocuments)" [disabled]="selectedDocuments.size === 0">
|
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash(selectedDocuments())" [disabled]="selectedDocuments().size === 0">
|
||||||
<i-bs name="trash" class="me-1"></i-bs><ng-container i18n>Delete selected</ng-container>
|
<i-bs name="trash" class="me-1"></i-bs><ng-container i18n>Delete selected</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash()" [disabled]="documentsInTrash.length === 0">
|
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash()" [disabled]="documentsInTrash().length === 0">
|
||||||
<i-bs name="trash" class="me-1"></i-bs><ng-container i18n>Empty trash</ng-container>
|
<i-bs name="trash" class="me-1"></i-bs><ng-container i18n>Empty trash</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="totalDocuments" [(page)]="page" [maxSize]="5" (pageChange)="reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="totalDocuments()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border table-responsive mb-3">
|
<div class="card border table-responsive mb-3">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
<div class="form-check m-0 ms-2 me-n2">
|
<div class="form-check m-0 ms-2 me-n2">
|
||||||
<input type="checkbox" class="form-check-input" id="all-objects" [(ngModel)]="allToggled" [disabled]="documentsInTrash.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
<input type="checkbox" class="form-check-input" id="all-objects" [ngModel]="allToggled()" (ngModelChange)="allToggled.set($event)" [disabled]="documentsInTrash().length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||||
<label class="form-check-label" for="all-objects"></label>
|
<label class="form-check-label" for="all-objects"></label>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@@ -46,11 +46,11 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@for (document of documentsInTrash; track document.id) {
|
@for (document of documentsInTrash(); track document.id) {
|
||||||
<tr (click)="toggleSelected(document); $event.stopPropagation();" (mouseleave)="popupPreview.close()" class="data-row fade" [class.show]="show">
|
<tr (click)="toggleSelected(document); $event.stopPropagation();" (mouseleave)="popupPreview.close()" class="data-row fade" [class.show]="show">
|
||||||
<td>
|
<td>
|
||||||
<div class="form-check m-0 ms-2 me-n2">
|
<div class="form-check m-0 ms-2 me-n2">
|
||||||
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments.has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">
|
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments().has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">
|
||||||
<label class="form-check-label" for="{{document.id}}"></label>
|
<label class="form-check-label" for="{{document.id}}"></label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -91,13 +91,13 @@
|
|||||||
@if (!loading) {
|
@if (!loading) {
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-2">
|
||||||
<div>
|
<div>
|
||||||
<ng-container i18n>{totalDocuments, plural, =1 {One document in trash} other {{{totalDocuments || 0}} total documents in trash}}</ng-container>
|
<ng-container i18n>{totalDocuments(), plural, =1 {One document in trash} other {{{totalDocuments() || 0}} total documents in trash}}</ng-container>
|
||||||
@if (selectedDocuments.size > 0) {
|
@if (selectedDocuments().size > 0) {
|
||||||
({{selectedDocuments.size}} selected)
|
({{selectedDocuments().size}} selected)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (documentsInTrash.length > 20) {
|
@if (documentsInTrash().length > 20) {
|
||||||
<ngb-pagination class="ms-auto" [pageSize]="25" [collectionSize]="totalDocuments" [(page)]="page" [maxSize]="5" (pageChange)="reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
<ngb-pagination class="ms-auto" [pageSize]="25" [collectionSize]="totalDocuments()" [page]="page()" [maxSize]="5" (pageChange)="page.set($event); reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ describe('TrashComponent', () => {
|
|||||||
component.reload()
|
component.reload()
|
||||||
jest.advanceTimersByTime(100)
|
jest.advanceTimersByTime(100)
|
||||||
expect(trashSpy).toHaveBeenCalled()
|
expect(trashSpy).toHaveBeenCalled()
|
||||||
expect(component.documentsInTrash).toEqual(documentsInTrash)
|
expect(component.documentsInTrash()).toEqual(documentsInTrash)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support delete document, show error if needed', () => {
|
it('should support delete document, show error if needed', () => {
|
||||||
@@ -179,8 +179,8 @@ describe('TrashComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should support toggle all items in view', () => {
|
it('should support toggle all items in view', () => {
|
||||||
component.documentsInTrash = documentsInTrash
|
component.documentsInTrash.set(documentsInTrash)
|
||||||
expect(component.selectedDocuments.size).toEqual(0)
|
expect(component.selectedDocuments().size).toEqual(0)
|
||||||
const toggleAllSpy = jest.spyOn(component, 'toggleAll')
|
const toggleAllSpy = jest.spyOn(component, 'toggleAll')
|
||||||
const checkButton = fixture.debugElement.queryAll(
|
const checkButton = fixture.debugElement.queryAll(
|
||||||
By.css('input.form-check-input')
|
By.css('input.form-check-input')
|
||||||
@@ -189,21 +189,21 @@ describe('TrashComponent', () => {
|
|||||||
checkButton.nativeElement.checked = true
|
checkButton.nativeElement.checked = true
|
||||||
checkButton.nativeElement.dispatchEvent(new Event('click'))
|
checkButton.nativeElement.dispatchEvent(new Event('click'))
|
||||||
expect(toggleAllSpy).toHaveBeenCalled()
|
expect(toggleAllSpy).toHaveBeenCalled()
|
||||||
expect(component.selectedDocuments.size).toEqual(documentsInTrash.length)
|
expect(component.selectedDocuments().size).toEqual(documentsInTrash.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support toggle item', () => {
|
it('should support toggle item', () => {
|
||||||
component.selectedDocuments = new Set([1])
|
component.selectedDocuments.set(new Set([1]))
|
||||||
component.toggleSelected(documentsInTrash[0])
|
component.toggleSelected(documentsInTrash[0])
|
||||||
expect(component.selectedDocuments.size).toEqual(0)
|
expect(component.selectedDocuments().size).toEqual(0)
|
||||||
component.toggleSelected(documentsInTrash[0])
|
component.toggleSelected(documentsInTrash[0])
|
||||||
expect(component.selectedDocuments.size).toEqual(1)
|
expect(component.selectedDocuments().size).toEqual(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support clear selection', () => {
|
it('should support clear selection', () => {
|
||||||
component.selectedDocuments = new Set([1])
|
component.selectedDocuments.set(new Set([1]))
|
||||||
component.clearSelection()
|
component.clearSelection()
|
||||||
expect(component.selectedDocuments.size).toEqual(0)
|
expect(component.selectedDocuments().size).toEqual(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should correctly display days remaining', () => {
|
it('should correctly display days remaining', () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnDestroy, inject } from '@angular/core'
|
import { Component, OnDestroy, inject, signal } from '@angular/core'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
NgbPaginationModule,
|
NgbPaginationModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { delay, takeUntil, tap } from 'rxjs'
|
import { takeUntil, tap } from 'rxjs'
|
||||||
import { Document } from 'src/app/data/document'
|
import { Document } from 'src/app/data/document'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
@@ -42,11 +42,11 @@ export class TrashComponent
|
|||||||
private settingsService = inject(SettingsService)
|
private settingsService = inject(SettingsService)
|
||||||
private router = inject(Router)
|
private router = inject(Router)
|
||||||
|
|
||||||
public documentsInTrash: Document[] = []
|
readonly documentsInTrash = signal<Document[]>([])
|
||||||
public selectedDocuments: Set<number> = new Set()
|
readonly selectedDocuments = signal<Set<number>>(new Set())
|
||||||
public allToggled: boolean = false
|
readonly allToggled = signal(false)
|
||||||
public page: number = 1
|
readonly page = signal(1)
|
||||||
public totalDocuments: number
|
readonly totalDocuments = signal<number>(undefined)
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
@@ -56,15 +56,14 @@ export class TrashComponent
|
|||||||
reload() {
|
reload() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.trashService
|
this.trashService
|
||||||
.getTrash(this.page)
|
.getTrash(this.page())
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((r) => {
|
tap((r) => {
|
||||||
this.documentsInTrash = r.results
|
this.documentsInTrash.set(r.results)
|
||||||
this.totalDocuments = r.count
|
this.totalDocuments.set(r.count)
|
||||||
this.selectedDocuments.clear()
|
this.selectedDocuments.set(new Set())
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}),
|
})
|
||||||
delay(100)
|
|
||||||
)
|
)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.show = true
|
this.show = true
|
||||||
@@ -122,7 +121,7 @@ export class TrashComponent
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.toastService.showInfo($localize`Document(s) deleted`)
|
this.toastService.showInfo($localize`Document(s) deleted`)
|
||||||
this.allToggled = false
|
this.allToggled.set(false)
|
||||||
modal.close()
|
modal.close()
|
||||||
this.reload()
|
this.reload()
|
||||||
},
|
},
|
||||||
@@ -165,7 +164,7 @@ export class TrashComponent
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.toastService.showInfo($localize`Document(s) restored`)
|
this.toastService.showInfo($localize`Document(s) restored`)
|
||||||
this.allToggled = false
|
this.allToggled.set(false)
|
||||||
this.reload()
|
this.reload()
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -179,24 +178,29 @@ export class TrashComponent
|
|||||||
|
|
||||||
toggleAll(event: PointerEvent) {
|
toggleAll(event: PointerEvent) {
|
||||||
if ((event.target as HTMLInputElement).checked) {
|
if ((event.target as HTMLInputElement).checked) {
|
||||||
this.selectedDocuments = new Set(this.documentsInTrash.map((t) => t.id))
|
this.selectedDocuments.set(
|
||||||
|
new Set(this.documentsInTrash().map((t) => t.id))
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
this.clearSelection()
|
this.clearSelection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSelected(object: Document) {
|
toggleSelected(object: Document) {
|
||||||
this.selectedDocuments.has(object.id)
|
const selectedDocuments = new Set(this.selectedDocuments())
|
||||||
? this.selectedDocuments.delete(object.id)
|
selectedDocuments.has(object.id)
|
||||||
: this.selectedDocuments.add(object.id)
|
? selectedDocuments.delete(object.id)
|
||||||
|
: selectedDocuments.add(object.id)
|
||||||
|
this.selectedDocuments.set(selectedDocuments)
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.allToggled = false
|
this.allToggled.set(false)
|
||||||
this.selectedDocuments.clear()
|
this.selectedDocuments.set(new Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
getDaysRemaining(document: Document): number {
|
getDaysRemaining(document: Document): number {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
const delay = this.settingsService.get(SETTINGS_KEYS.EMPTY_TRASH_DELAY)
|
const delay = this.settingsService.get(SETTINGS_KEYS.EMPTY_TRASH_DELAY)
|
||||||
const diff = new Date().getTime() - new Date(document.deleted_at).getTime()
|
const diff = new Date().getTime() - new Date(document.deleted_at).getTime()
|
||||||
const days = Math.ceil(diff / (1000 * 3600 * 24))
|
const days = Math.ceil(diff / (1000 * 3600 * 24))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
|
|
||||||
@if (canViewUsers && users) {
|
@if (canViewUsers && users()) {
|
||||||
<h4 class="d-flex">
|
<h4 class="d-flex">
|
||||||
<ng-container i18n>Users</ng-container>
|
<ng-container i18n>Users</ng-container>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editUser()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }">
|
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editUser()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }">
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="col" i18n>Actions</div>
|
<div class="col" i18n>Actions</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@for (user of users; track user) {
|
@for (user of users(); track user) {
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col d-flex align-items-center" [class.opacity-50]="!user.is_active"><button class="btn btn-link p-0 text-start" type="button" (click)="editUser(user)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.User)">{{user.username}}</button></div>
|
<div class="col d-flex align-items-center" [class.opacity-50]="!user.is_active"><button class="btn btn-link p-0 text-start" type="button" (click)="editUser(user)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.User)">{{user.username}}</button></div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (canViewGroups && groups) {
|
@if (canViewGroups && groups()) {
|
||||||
<h4 class="mt-4 d-flex">
|
<h4 class="mt-4 d-flex">
|
||||||
<ng-container i18n>Groups</ng-container>
|
<ng-container i18n>Groups</ng-container>
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
|
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<div class="col" i18n>Actions</div>
|
<div class="col" i18n>Actions</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@for (group of groups; track group) {
|
@for (group of groups(); track group) {
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
|
||||||
@@ -80,13 +80,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
@if (groups.length === 0) {
|
@if (groups().length === 0) {
|
||||||
<li class="list-group-item" i18n>No groups defined</li>
|
<li class="list-group-item" i18n>No groups defined</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if ((canViewUsers && !users) || (canViewGroups && !groups)) {
|
@if ((canViewUsers && !users()) || (canViewGroups && !groups())) {
|
||||||
<div>
|
<div>
|
||||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||||
<div class="visually-hidden" i18n>Loading...</div>
|
<div class="visually-hidden" i18n>Loading...</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { Subject, first, takeUntil } from 'rxjs'
|
import { Subject, first, takeUntil } from 'rxjs'
|
||||||
@@ -43,8 +43,8 @@ export class UsersAndGroupsComponent
|
|||||||
permissionsService = inject(PermissionsService)
|
permissionsService = inject(PermissionsService)
|
||||||
private settings = inject(SettingsService)
|
private settings = inject(SettingsService)
|
||||||
|
|
||||||
users: User[]
|
readonly users = signal<User[]>(null)
|
||||||
groups: Group[]
|
readonly groups = signal<Group[]>(null)
|
||||||
|
|
||||||
unsubscribeNotifier: Subject<any> = new Subject()
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export class UsersAndGroupsComponent
|
|||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (r) => {
|
next: (r) => {
|
||||||
this.users = r.results
|
this.users.set(r.results)
|
||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
this.toastService.showError($localize`Error retrieving users`, e)
|
this.toastService.showError($localize`Error retrieving users`, e)
|
||||||
@@ -83,7 +83,7 @@ export class UsersAndGroupsComponent
|
|||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (r) => {
|
next: (r) => {
|
||||||
this.groups = r.results
|
this.groups.set(r.results)
|
||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
this.toastService.showError($localize`Error retrieving groups`, e)
|
this.toastService.showError($localize`Error retrieving groups`, e)
|
||||||
@@ -125,7 +125,7 @@ export class UsersAndGroupsComponent
|
|||||||
$localize`Saved user "${newUser.username}".`
|
$localize`Saved user "${newUser.username}".`
|
||||||
)
|
)
|
||||||
this.usersService.listAll().subscribe((r) => {
|
this.usersService.listAll().subscribe((r) => {
|
||||||
this.users = r.results
|
this.users.set(r.results)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -152,7 +152,7 @@ export class UsersAndGroupsComponent
|
|||||||
modal.close()
|
modal.close()
|
||||||
this.toastService.showInfo($localize`Deleted user "${user.username}"`)
|
this.toastService.showInfo($localize`Deleted user "${user.username}"`)
|
||||||
this.usersService.listAll().subscribe((r) => {
|
this.usersService.listAll().subscribe((r) => {
|
||||||
this.users = r.results
|
this.users.set(r.results)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
@@ -179,7 +179,7 @@ export class UsersAndGroupsComponent
|
|||||||
.subscribe((newGroup) => {
|
.subscribe((newGroup) => {
|
||||||
this.toastService.showInfo($localize`Saved group "${newGroup.name}".`)
|
this.toastService.showInfo($localize`Saved group "${newGroup.name}".`)
|
||||||
this.groupsService.listAll().subscribe((r) => {
|
this.groupsService.listAll().subscribe((r) => {
|
||||||
this.groups = r.results
|
this.groups.set(r.results)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
modal.componentInstance.failed
|
modal.componentInstance.failed
|
||||||
@@ -205,7 +205,7 @@ export class UsersAndGroupsComponent
|
|||||||
modal.close()
|
modal.close()
|
||||||
this.toastService.showInfo($localize`Deleted group "${group.name}"`)
|
this.toastService.showInfo($localize`Deleted group "${group.name}"`)
|
||||||
this.groupsService.listAll().subscribe((r) => {
|
this.groupsService.listAll().subscribe((r) => {
|
||||||
this.groups = r.results
|
this.groups.set(r.results)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
error: (e) => {
|
error: (e) => {
|
||||||
@@ -219,6 +219,6 @@ export class UsersAndGroupsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
getGroupName(id: number): string {
|
getGroupName(id: number): string {
|
||||||
return this.groups?.find((g) => g.id === id)?.name ?? ''
|
return this.groups()?.find((g) => g.id === id)?.name ?? ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
moveItemInArray,
|
moveItemInArray,
|
||||||
} from '@angular/cdk/drag-drop'
|
} from '@angular/cdk/drag-drop'
|
||||||
import { NgClass } from '@angular/common'
|
import { NgClass } from '@angular/common'
|
||||||
import { Component, HostListener, inject, OnInit } from '@angular/core'
|
import { Component, HostListener, inject, OnInit, signal } from '@angular/core'
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router'
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
NgbCollapseModule,
|
NgbCollapseModule,
|
||||||
@@ -90,16 +90,48 @@ export class AppFrameComponent
|
|||||||
permissionsService = inject(PermissionsService)
|
permissionsService = inject(PermissionsService)
|
||||||
private djangoMessagesService = inject(DjangoMessagesService)
|
private djangoMessagesService = inject(DjangoMessagesService)
|
||||||
|
|
||||||
appRemoteVersion: AppRemoteVersion
|
private appRemoteVersionSignal = signal<AppRemoteVersion>(null)
|
||||||
|
|
||||||
isMenuCollapsed: boolean = true
|
private isMenuCollapsedSignal = signal(true)
|
||||||
|
|
||||||
slimSidebarAnimating: boolean = false
|
private slimSidebarAnimatingSignal = signal(false)
|
||||||
|
|
||||||
public mobileSearchHidden: boolean = false
|
private mobileSearchHiddenSignal = signal(false)
|
||||||
|
|
||||||
private lastScrollY: number = 0
|
private lastScrollY: number = 0
|
||||||
|
|
||||||
|
get appRemoteVersion(): AppRemoteVersion {
|
||||||
|
return this.appRemoteVersionSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set appRemoteVersion(value: AppRemoteVersion) {
|
||||||
|
this.appRemoteVersionSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get isMenuCollapsed(): boolean {
|
||||||
|
return this.isMenuCollapsedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set isMenuCollapsed(value: boolean) {
|
||||||
|
this.isMenuCollapsedSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get slimSidebarAnimating(): boolean {
|
||||||
|
return this.slimSidebarAnimatingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set slimSidebarAnimating(value: boolean) {
|
||||||
|
this.slimSidebarAnimatingSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get mobileSearchHidden(): boolean {
|
||||||
|
return this.mobileSearchHiddenSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set mobileSearchHidden(value: boolean) {
|
||||||
|
this.mobileSearchHiddenSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
const permissionsService = this.permissionsService
|
const permissionsService = this.permissionsService
|
||||||
@@ -178,10 +210,12 @@ export class AppFrameComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get versionString(): string {
|
get versionString(): string {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return `${environment.appTitle} v${this.settingsService.get(SETTINGS_KEYS.VERSION)}${environment.tag === 'prod' ? '' : ` #${environment.tag}`}`
|
return `${environment.appTitle} v${this.settingsService.get(SETTINGS_KEYS.VERSION)}${environment.tag === 'prod' ? '' : ` #${environment.tag}`}`
|
||||||
}
|
}
|
||||||
|
|
||||||
get customAppTitle(): string {
|
get customAppTitle(): string {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return this.settingsService.get(SETTINGS_KEYS.APP_TITLE)
|
return this.settingsService.get(SETTINGS_KEYS.APP_TITLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +258,7 @@ export class AppFrameComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get slimSidebarEnabled(): boolean {
|
get slimSidebarEnabled(): boolean {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return this.settingsService.get(SETTINGS_KEYS.SLIM_SIDEBAR)
|
return this.settingsService.get(SETTINGS_KEYS.SLIM_SIDEBAR)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +278,7 @@ export class AppFrameComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get attributesSectionsCollapsed(): boolean {
|
get attributesSectionsCollapsed(): boolean {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return this.settingsService
|
return this.settingsService
|
||||||
.get(SETTINGS_KEYS.ATTRIBUTES_SECTIONS_COLLAPSED)
|
.get(SETTINGS_KEYS.ATTRIBUTES_SECTIONS_COLLAPSED)
|
||||||
?.includes(CollapsibleSection.ATTRIBUTES)
|
?.includes(CollapsibleSection.ATTRIBUTES)
|
||||||
@@ -268,6 +304,7 @@ export class AppFrameComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get aiEnabled(): boolean {
|
get aiEnabled(): boolean {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return this.settingsService.get(SETTINGS_KEYS.AI_ENABLED)
|
return this.settingsService.get(SETTINGS_KEYS.AI_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,6 +456,7 @@ export class AppFrameComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get showSidebarCounts(): boolean {
|
get showSidebarCounts(): boolean {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return (
|
return (
|
||||||
this.settingsService.get(SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT) &&
|
this.settingsService.get(SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT) &&
|
||||||
!this.settingsService.organizingSidebarSavedViews
|
!this.settingsService.organizingSidebarSavedViews
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ViewChild,
|
ViewChild,
|
||||||
ViewChildren,
|
ViewChildren,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
@@ -82,12 +83,36 @@ export class GlobalSearchComponent implements OnInit {
|
|||||||
private locationStrategy = inject(LocationStrategy)
|
private locationStrategy = inject(LocationStrategy)
|
||||||
|
|
||||||
public DataType = DataType
|
public DataType = DataType
|
||||||
public query: string
|
private querySignal = signal<string>(null)
|
||||||
public queryDebounce: Subject<string>
|
public queryDebounce: Subject<string>
|
||||||
public searchResults: GlobalSearchResult
|
private searchResultsSignal = signal<GlobalSearchResult>(null)
|
||||||
private currentItemIndex: number = -1
|
private currentItemIndex: number = -1
|
||||||
private domIndex: number = -1
|
private domIndex: number = -1
|
||||||
public loading: boolean = false
|
private loadingSignal = signal(false)
|
||||||
|
|
||||||
|
public get query(): string {
|
||||||
|
return this.querySignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set query(value: string) {
|
||||||
|
this.querySignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get searchResults(): GlobalSearchResult {
|
||||||
|
return this.searchResultsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set searchResults(value: GlobalSearchResult) {
|
||||||
|
this.searchResultsSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get loading(): boolean {
|
||||||
|
return this.loadingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set loading(value: boolean) {
|
||||||
|
this.loadingSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
@ViewChild('searchInput') searchInput: ElementRef
|
@ViewChild('searchInput') searchInput: ElementRef
|
||||||
@ViewChild('resultsDropdown') resultsDropdown: NgbDropdown
|
@ViewChild('resultsDropdown') resultsDropdown: NgbDropdown
|
||||||
@@ -96,6 +121,7 @@ export class GlobalSearchComponent implements OnInit {
|
|||||||
@ViewChildren('secondaryButton') secondaryButtons: QueryList<ElementRef>
|
@ViewChildren('secondaryButton') secondaryButtons: QueryList<ElementRef>
|
||||||
|
|
||||||
get useAdvancedForFullSearch(): boolean {
|
get useAdvancedForFullSearch(): boolean {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return (
|
return (
|
||||||
this.settingsService.get(SETTINGS_KEYS.SEARCH_FULL_TYPE) ===
|
this.settingsService.get(SETTINGS_KEYS.SEARCH_FULL_TYPE) ===
|
||||||
GlobalSearchType.ADVANCED
|
GlobalSearchType.ADVANCED
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
<li ngbDropdown class="nav-item mx-1" (openChange)="onOpenChange($event)">
|
<li ngbDropdown class="nav-item mx-1" (openChange)="onOpenChange($event)">
|
||||||
@if (toasts.length) {
|
@if (toasts().length) {
|
||||||
<span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ toasts.length }}</span>
|
<span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ toasts().length }}</span>
|
||||||
}
|
}
|
||||||
<button class="btn border-0" id="notificationsDropdown" ngbDropdownToggle>
|
<button class="btn border-0" id="notificationsDropdown" ngbDropdownToggle>
|
||||||
<i-bs width="1.3em" height="1.3em" name="bell"></i-bs>
|
<i-bs width="1.3em" height="1.3em" name="bell"></i-bs>
|
||||||
@@ -12,15 +12,15 @@
|
|||||||
<div class="btn-group ms-auto">
|
<div class="btn-group ms-auto">
|
||||||
<button class="btn btn-sm btn-outline-secondary mb-2 ms-auto"
|
<button class="btn btn-sm btn-outline-secondary mb-2 ms-auto"
|
||||||
(click)="toastService.clearToasts()"
|
(click)="toastService.clearToasts()"
|
||||||
[disabled]="toasts.length === 0"
|
[disabled]="toasts().length === 0"
|
||||||
i18n>Clear All</button>
|
i18n>Clear All</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (toasts.length === 0) {
|
@if (toasts().length === 0) {
|
||||||
<p class="text-center mb-0 small text-muted"><em i18n>No notifications</em></p>
|
<p class="text-center mb-0 small text-muted"><em i18n>No notifications</em></p>
|
||||||
}
|
}
|
||||||
<div class="scroll-list">
|
<div class="scroll-list">
|
||||||
@for (toast of toasts; track toast.id) {
|
@for (toast of toasts(); track toast.id) {
|
||||||
<pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (closed)="toastService.closeToast(toast)"></pngx-toast>
|
<pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (closed)="toastService.closeToast(toast)"></pngx-toast>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+9
-14
@@ -42,7 +42,8 @@ describe('ToastsDropdownComponent', () => {
|
|||||||
let component: ToastsDropdownComponent
|
let component: ToastsDropdownComponent
|
||||||
let fixture: ComponentFixture<ToastsDropdownComponent>
|
let fixture: ComponentFixture<ToastsDropdownComponent>
|
||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
let toastsSubject: Subject<Toast[]> = new Subject()
|
let toastsSubject: Subject<Toast[]>
|
||||||
|
let getToastsSpy: jest.SpyInstance
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -56,48 +57,43 @@ describe('ToastsDropdownComponent', () => {
|
|||||||
],
|
],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ToastsDropdownComponent)
|
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
jest.spyOn(toastService, 'getToasts').mockReturnValue(toastsSubject)
|
toastsSubject = new Subject()
|
||||||
|
getToastsSpy = jest
|
||||||
|
.spyOn(toastService, 'getToasts')
|
||||||
|
.mockReturnValue(toastsSubject)
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ToastsDropdownComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call getToasts and return toasts', fakeAsync(() => {
|
it('should call getToasts and return toasts', fakeAsync(() => {
|
||||||
const spy = jest.spyOn(toastService, 'getToasts')
|
|
||||||
|
|
||||||
component.ngOnInit()
|
|
||||||
toastsSubject.next(toasts)
|
toastsSubject.next(toasts)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalled()
|
expect(getToastsSpy).toHaveBeenCalled()
|
||||||
expect(component.toasts).toContainEqual({
|
expect(component.toasts()).toContainEqual({
|
||||||
id: 'abc-123',
|
id: 'abc-123',
|
||||||
content: 'foo bar',
|
content: 'foo bar',
|
||||||
delay: 5000,
|
delay: 5000,
|
||||||
})
|
})
|
||||||
|
|
||||||
component.ngOnDestroy()
|
|
||||||
flush()
|
flush()
|
||||||
discardPeriodicTasks()
|
discardPeriodicTasks()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should show a toast', fakeAsync(() => {
|
it('should show a toast', fakeAsync(() => {
|
||||||
component.ngOnInit()
|
|
||||||
toastsSubject.next(toasts)
|
toastsSubject.next(toasts)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
|
|
||||||
expect(fixture.nativeElement.textContent).toContain('foo bar')
|
expect(fixture.nativeElement.textContent).toContain('foo bar')
|
||||||
|
|
||||||
component.ngOnDestroy()
|
|
||||||
flush()
|
flush()
|
||||||
discardPeriodicTasks()
|
discardPeriodicTasks()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
it('should toggle suppressPopupToasts', fakeAsync((finish) => {
|
it('should toggle suppressPopupToasts', fakeAsync((finish) => {
|
||||||
component.ngOnInit()
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
toastsSubject.next(toasts)
|
toastsSubject.next(toasts)
|
||||||
|
|
||||||
@@ -105,7 +101,6 @@ describe('ToastsDropdownComponent', () => {
|
|||||||
component.onOpenChange(true)
|
component.onOpenChange(true)
|
||||||
expect(spy).toHaveBeenCalledWith(true)
|
expect(spy).toHaveBeenCalledWith(true)
|
||||||
|
|
||||||
component.ngOnDestroy()
|
|
||||||
flush()
|
flush()
|
||||||
discardPeriodicTasks()
|
discardPeriodicTasks()
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
import { Component, inject } from '@angular/core'
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import {
|
import {
|
||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
NgbProgressbarModule,
|
NgbProgressbarModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { Subscription } from 'rxjs'
|
import { map } from 'rxjs'
|
||||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||||
import { ToastComponent } from '../../common/toast/toast.component'
|
import { ToastComponent } from '../../common/toast/toast.component'
|
||||||
|
|
||||||
@@ -19,22 +20,13 @@ import { ToastComponent } from '../../common/toast/toast.component'
|
|||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ToastsDropdownComponent implements OnInit, OnDestroy {
|
export class ToastsDropdownComponent {
|
||||||
toastService = inject(ToastService)
|
toastService = inject(ToastService)
|
||||||
|
|
||||||
private subscription: Subscription
|
readonly toasts = toSignal(
|
||||||
|
this.toastService.getToasts().pipe(map((toasts) => [...toasts])),
|
||||||
public toasts: Toast[] = []
|
{ initialValue: [] as Toast[] }
|
||||||
|
)
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subscription?.unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.subscription = this.toastService.getToasts().subscribe((toasts) => {
|
|
||||||
this.toasts = [...toasts]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpenChange(open: boolean): void {
|
onOpenChange(open: boolean): void {
|
||||||
this.toastService.suppressPopupToasts = open
|
this.toastService.suppressPopupToasts = open
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
import { Component, EventEmitter, Input, Output, signal } from '@angular/core'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -8,13 +8,26 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
|||||||
imports: [NgxBootstrapIconsModule],
|
imports: [NgxBootstrapIconsModule],
|
||||||
})
|
})
|
||||||
export class ClearableBadgeComponent {
|
export class ClearableBadgeComponent {
|
||||||
constructor() {}
|
private numberSignal = signal<number>(undefined)
|
||||||
|
private selectedSignal = signal<boolean>(undefined)
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
number: number
|
get number(): number {
|
||||||
|
return this.numberSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set number(number: number) {
|
||||||
|
this.numberSignal.set(number)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
selected: boolean
|
get selected(): boolean {
|
||||||
|
return this.selectedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set selected(selected: boolean) {
|
||||||
|
this.selectedSignal.set(selected)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
cleared: EventEmitter<boolean> = new EventEmitter()
|
cleared: EventEmitter<boolean> = new EventEmitter()
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { DecimalPipe } from '@angular/common'
|
import { DecimalPipe } from '@angular/common'
|
||||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
||||||
@@ -13,6 +20,17 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
|||||||
export class ConfirmDialogComponent extends LoadingComponentWithPermissions {
|
export class ConfirmDialogComponent extends LoadingComponentWithPermissions {
|
||||||
activeModal = inject(NgbActiveModal)
|
activeModal = inject(NgbActiveModal)
|
||||||
|
|
||||||
|
private titleSignal = signal($localize`Confirmation`)
|
||||||
|
private messageBoldSignal = signal<string>(undefined)
|
||||||
|
private messageSignal = signal<string>(undefined)
|
||||||
|
private btnClassSignal = signal('btn-primary')
|
||||||
|
private btnCaptionSignal = signal($localize`Confirm`)
|
||||||
|
private alternativeBtnClassSignal = signal('btn-secondary')
|
||||||
|
private alternativeBtnCaptionSignal = signal<string>(undefined)
|
||||||
|
private cancelBtnClassSignal = signal('btn-outline-secondary')
|
||||||
|
private cancelBtnCaptionSignal = signal($localize`Cancel`)
|
||||||
|
private buttonsEnabledSignal = signal(true)
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
public confirmClicked = new EventEmitter()
|
public confirmClicked = new EventEmitter()
|
||||||
|
|
||||||
@@ -20,34 +38,94 @@ export class ConfirmDialogComponent extends LoadingComponentWithPermissions {
|
|||||||
public alternativeClicked = new EventEmitter()
|
public alternativeClicked = new EventEmitter()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
title = $localize`Confirmation`
|
get title(): string {
|
||||||
|
return this.titleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set title(title: string) {
|
||||||
|
this.titleSignal.set(title)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
messageBold
|
get messageBold(): string {
|
||||||
|
return this.messageBoldSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set messageBold(messageBold: string) {
|
||||||
|
this.messageBoldSignal.set(messageBold)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
message
|
get message(): string {
|
||||||
|
return this.messageSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set message(message: string) {
|
||||||
|
this.messageSignal.set(message)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
btnClass = 'btn-primary'
|
get btnClass(): string {
|
||||||
|
return this.btnClassSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set btnClass(btnClass: string) {
|
||||||
|
this.btnClassSignal.set(btnClass)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
btnCaption = $localize`Confirm`
|
get btnCaption(): string {
|
||||||
|
return this.btnCaptionSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set btnCaption(btnCaption: string) {
|
||||||
|
this.btnCaptionSignal.set(btnCaption)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
alternativeBtnClass = 'btn-secondary'
|
get alternativeBtnClass(): string {
|
||||||
|
return this.alternativeBtnClassSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set alternativeBtnClass(alternativeBtnClass: string) {
|
||||||
|
this.alternativeBtnClassSignal.set(alternativeBtnClass)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
alternativeBtnCaption
|
get alternativeBtnCaption(): string {
|
||||||
|
return this.alternativeBtnCaptionSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set alternativeBtnCaption(alternativeBtnCaption: string) {
|
||||||
|
this.alternativeBtnCaptionSignal.set(alternativeBtnCaption)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
cancelBtnClass = 'btn-outline-secondary'
|
get cancelBtnClass(): string {
|
||||||
|
return this.cancelBtnClassSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set cancelBtnClass(cancelBtnClass: string) {
|
||||||
|
this.cancelBtnClassSignal.set(cancelBtnClass)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
cancelBtnCaption = $localize`Cancel`
|
get cancelBtnCaption(): string {
|
||||||
|
return this.cancelBtnCaptionSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set cancelBtnCaption(cancelBtnCaption: string) {
|
||||||
|
this.cancelBtnCaptionSignal.set(cancelBtnCaption)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
buttonsEnabled = true
|
get buttonsEnabled(): boolean {
|
||||||
|
return this.buttonsEnabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set buttonsEnabled(buttonsEnabled: boolean) {
|
||||||
|
this.buttonsEnabledSignal.set(buttonsEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
confirmButtonEnabled = true
|
confirmButtonEnabled = true
|
||||||
alternativeButtonEnabled = true
|
alternativeButtonEnabled = true
|
||||||
|
|||||||
+44
-10
@@ -4,7 +4,7 @@ import {
|
|||||||
moveItemInArray,
|
moveItemInArray,
|
||||||
} from '@angular/cdk/drag-drop'
|
} from '@angular/cdk/drag-drop'
|
||||||
import { AsyncPipe } from '@angular/common'
|
import { AsyncPipe } from '@angular/common'
|
||||||
import { Component, OnInit, inject } from '@angular/core'
|
import { Component, OnInit, inject, signal } from '@angular/core'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { takeUntil } from 'rxjs'
|
import { takeUntil } from 'rxjs'
|
||||||
@@ -36,15 +36,47 @@ export class MergeConfirmDialogComponent
|
|||||||
private documentService = inject(DocumentService)
|
private documentService = inject(DocumentService)
|
||||||
private permissionService = inject(PermissionsService)
|
private permissionService = inject(PermissionsService)
|
||||||
|
|
||||||
public documentIDs: number[] = []
|
private documentIDsSignal = signal<number[]>([])
|
||||||
public archiveFallback: boolean = false
|
private archiveFallbackSignal = signal(false)
|
||||||
public deleteOriginals: boolean = false
|
private deleteOriginalsSignal = signal(false)
|
||||||
private _documents: Document[] = []
|
private documentsSignal = signal<Document[]>([])
|
||||||
get documents(): Document[] {
|
private metadataDocumentIDSignal = signal(-1)
|
||||||
return this._documents
|
|
||||||
|
public get documentIDs(): number[] {
|
||||||
|
return this.documentIDsSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
public metadataDocumentID: number = -1
|
public set documentIDs(documentIDs: number[]) {
|
||||||
|
this.documentIDsSignal.set(documentIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get archiveFallback(): boolean {
|
||||||
|
return this.archiveFallbackSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set archiveFallback(archiveFallback: boolean) {
|
||||||
|
this.archiveFallbackSignal.set(archiveFallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get deleteOriginals(): boolean {
|
||||||
|
return this.deleteOriginalsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set deleteOriginals(deleteOriginals: boolean) {
|
||||||
|
this.deleteOriginalsSignal.set(deleteOriginals)
|
||||||
|
}
|
||||||
|
|
||||||
|
get documents(): Document[] {
|
||||||
|
return this.documentsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public get metadataDocumentID(): number {
|
||||||
|
return this.metadataDocumentIDSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set metadataDocumentID(metadataDocumentID: number) {
|
||||||
|
this.metadataDocumentIDSignal.set(metadataDocumentID)
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
@@ -55,12 +87,14 @@ export class MergeConfirmDialogComponent
|
|||||||
.getFew(this.documentIDs)
|
.getFew(this.documentIDs)
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((r) => {
|
.subscribe((r) => {
|
||||||
this._documents = r.results
|
this.documentsSignal.set(r.results)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop(event: CdkDragDrop<number[]>) {
|
onDrop(event: CdkDragDrop<number[]>) {
|
||||||
moveItemInArray(this.documentIDs, event.previousIndex, event.currentIndex)
|
const documentIDs = this.documentIDs.concat()
|
||||||
|
moveItemInArray(documentIDs, event.previousIndex, event.currentIndex)
|
||||||
|
this.documentIDs = documentIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
getDocument(documentID: number): Document {
|
getDocument(documentID: number): Document {
|
||||||
|
|||||||
+4
-11
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { ConfirmDialogComponent } from '../confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../confirm-dialog.component'
|
||||||
@@ -14,18 +14,11 @@ export class PasswordRemovalConfirmDialogComponent extends ConfirmDialogComponen
|
|||||||
includeMetadata: boolean = true
|
includeMetadata: boolean = true
|
||||||
deleteOriginal: boolean = false
|
deleteOriginal: boolean = false
|
||||||
|
|
||||||
@Input()
|
|
||||||
override title = $localize`Remove password protection`
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
override message =
|
|
||||||
$localize`Create an unprotected copy or replace the existing file.`
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
override btnCaption = $localize`Start`
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
this.title = $localize`Remove password protection`
|
||||||
|
this.message = $localize`Create an unprotected copy or replace the existing file.`
|
||||||
|
this.btnCaption = $localize`Start`
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateDocumentChange(updateDocument: boolean) {
|
onUpdateDocumentChange(updateDocument: boolean) {
|
||||||
|
|||||||
+24
-5
@@ -1,5 +1,5 @@
|
|||||||
import { NgStyle } from '@angular/common'
|
import { NgStyle } from '@angular/common'
|
||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject, signal } from '@angular/core'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { ConfirmDialogComponent } from '../confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../confirm-dialog.component'
|
||||||
@@ -13,11 +13,30 @@ import { ConfirmDialogComponent } from '../confirm-dialog.component'
|
|||||||
export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
|
export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
|
||||||
documentService = inject(DocumentService)
|
documentService = inject(DocumentService)
|
||||||
|
|
||||||
public documentID: number
|
private documentIDSignal = signal<number>(undefined)
|
||||||
public showPDFNote: boolean = true
|
private showPDFNoteSignal = signal(true)
|
||||||
|
private rotationSignal = signal(0)
|
||||||
|
|
||||||
|
public get documentID(): number {
|
||||||
|
return this.documentIDSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set documentID(documentID: number) {
|
||||||
|
this.documentIDSignal.set(documentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get showPDFNote(): boolean {
|
||||||
|
return this.showPDFNoteSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set showPDFNote(showPDFNote: boolean) {
|
||||||
|
this.showPDFNoteSignal.set(showPDFNote)
|
||||||
|
}
|
||||||
|
|
||||||
// animation is better if we dont normalize yet
|
// animation is better if we dont normalize yet
|
||||||
public rotation: number = 0
|
public get rotation(): number {
|
||||||
|
return this.rotationSignal()
|
||||||
|
}
|
||||||
|
|
||||||
public get degrees(): number {
|
public get degrees(): number {
|
||||||
let degrees = this.rotation % 360
|
let degrees = this.rotation % 360
|
||||||
@@ -30,6 +49,6 @@ export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rotate(clockwise: boolean = true) {
|
rotate(clockwise: boolean = true) {
|
||||||
this.rotation += clockwise ? 90 : -90
|
this.rotationSignal.update((rotation) => rotation + (clockwise ? 90 : -90))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
<ng-template #queryAtom let-atom="atom">
|
<ng-template #queryAtom let-atom="atom">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<ng-select
|
<ng-select #fieldSelects
|
||||||
class="paperless-input-select"
|
class="paperless-input-select"
|
||||||
[items]="customFields"
|
[items]="customFields"
|
||||||
[(ngModel)]="atom.field"
|
[(ngModel)]="atom.field"
|
||||||
|
|||||||
+1
-2
@@ -210,13 +210,12 @@ describe('CustomFieldsQueryDropdownComponent', () => {
|
|||||||
expect(component.name).toBe('test_title')
|
expect(component.name).toBe('test_title')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should add a default atom on open and focus the select field', fakeAsync(() => {
|
it('should add a default atom on open', fakeAsync(() => {
|
||||||
expect(component.selectionModel.queries.length).toBe(0)
|
expect(component.selectionModel.queries.length).toBe(0)
|
||||||
component.onOpenChange(true)
|
component.onOpenChange(true)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick()
|
tick()
|
||||||
expect(component.selectionModel.queries.length).toBe(1)
|
expect(component.selectionModel.queries.length).toBe(1)
|
||||||
expect(window.document.activeElement.tagName).toBe('INPUT')
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('CustomFieldQueriesModel', () => {
|
describe('CustomFieldQueriesModel', () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { FormGroup } from '@angular/forms'
|
import { FormGroup } from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
@@ -45,10 +46,20 @@ export abstract class EditDialogComponent<
|
|||||||
protected settingsService = inject(SettingsService)
|
protected settingsService = inject(SettingsService)
|
||||||
protected permissionsService = inject(PermissionsService)
|
protected permissionsService = inject(PermissionsService)
|
||||||
|
|
||||||
users: User[]
|
private usersSignal = signal<User[]>(undefined)
|
||||||
|
|
||||||
|
private dialogModeSignal = signal(EditDialogMode.CREATE, {
|
||||||
|
equal: () => false,
|
||||||
|
})
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
dialogMode: EditDialogMode = EditDialogMode.CREATE
|
get dialogMode(): EditDialogMode {
|
||||||
|
return this.dialogModeSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set dialogMode(dialogMode: EditDialogMode) {
|
||||||
|
this.dialogModeSignal.set(dialogMode)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
object: T
|
object: T
|
||||||
@@ -59,11 +70,43 @@ export abstract class EditDialogComponent<
|
|||||||
@Output()
|
@Output()
|
||||||
failed = new EventEmitter()
|
failed = new EventEmitter()
|
||||||
|
|
||||||
networkActive = false
|
private networkActiveSignal = signal(false)
|
||||||
|
|
||||||
closeEnabled = false
|
private closeEnabledSignal = signal(false)
|
||||||
|
|
||||||
error = null
|
private errorSignal = signal<any>(null)
|
||||||
|
|
||||||
|
get users(): User[] {
|
||||||
|
return this.usersSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set users(users: User[]) {
|
||||||
|
this.usersSignal.set(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
get networkActive(): boolean {
|
||||||
|
return this.networkActiveSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set networkActive(networkActive: boolean) {
|
||||||
|
this.networkActiveSignal.set(networkActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
get closeEnabled(): boolean {
|
||||||
|
return this.closeEnabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set closeEnabled(closeEnabled: boolean) {
|
||||||
|
this.closeEnabledSignal.set(closeEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
get error(): any {
|
||||||
|
return this.errorSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set error(error: any) {
|
||||||
|
this.errorSignal.set(error)
|
||||||
|
}
|
||||||
|
|
||||||
abstract getForm(): FormGroup
|
abstract getForm(): FormGroup
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -1,4 +1,4 @@
|
|||||||
import { Component, ViewChild, inject } from '@angular/core'
|
import { Component, ViewChild, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -38,9 +38,17 @@ const IMAP_SECURITY_OPTIONS = [
|
|||||||
})
|
})
|
||||||
export class MailAccountEditDialogComponent extends EditDialogComponent<MailAccount> {
|
export class MailAccountEditDialogComponent extends EditDialogComponent<MailAccount> {
|
||||||
testActive: boolean = false
|
testActive: boolean = false
|
||||||
testResult: string
|
private testResultSignal = signal<string>(undefined)
|
||||||
alertTimeout
|
alertTimeout
|
||||||
|
|
||||||
|
get testResult(): string {
|
||||||
|
return this.testResultSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set testResult(testResult: string) {
|
||||||
|
this.testResultSignal.set(testResult)
|
||||||
|
}
|
||||||
|
|
||||||
@ViewChild('testResultAlert', { static: false }) testResultAlert: NgbAlert
|
@ViewChild('testResultAlert', { static: false }) testResultAlert: NgbAlert
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
+28
-4
@@ -1,4 +1,4 @@
|
|||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -158,9 +158,33 @@ export class MailRuleEditDialogComponent extends EditDialogComponent<MailRule> {
|
|||||||
private correspondentService: CorrespondentService
|
private correspondentService: CorrespondentService
|
||||||
private documentTypeService: DocumentTypeService
|
private documentTypeService: DocumentTypeService
|
||||||
|
|
||||||
accounts: MailAccount[]
|
private accountsSignal = signal<MailAccount[]>(undefined)
|
||||||
correspondents: Correspondent[]
|
private correspondentsSignal = signal<Correspondent[]>(undefined)
|
||||||
documentTypes: DocumentType[]
|
private documentTypesSignal = signal<DocumentType[]>(undefined)
|
||||||
|
|
||||||
|
get accounts(): MailAccount[] {
|
||||||
|
return this.accountsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set accounts(accounts: MailAccount[]) {
|
||||||
|
this.accountsSignal.set(accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
get correspondents(): Correspondent[] {
|
||||||
|
return this.correspondentsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set correspondents(correspondents: Correspondent[]) {
|
||||||
|
this.correspondentsSignal.set(correspondents)
|
||||||
|
}
|
||||||
|
|
||||||
|
get documentTypes(): DocumentType[] {
|
||||||
|
return this.documentTypesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentTypes(documentTypes: DocumentType[]) {
|
||||||
|
this.documentTypesSignal.set(documentTypes)
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
|||||||
+1
-1
@@ -67,11 +67,11 @@ export class StoragePathEditDialogComponent
|
|||||||
private testDocument: Document
|
private testDocument: Document
|
||||||
public testResult: string
|
public testResult: string
|
||||||
public testFailed: boolean = false
|
public testFailed: boolean = false
|
||||||
public loading = false
|
|
||||||
public testLoading = false
|
public testLoading = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
this.loading = false
|
||||||
this.service = inject(StoragePathService)
|
this.service = inject(StoragePathService)
|
||||||
this.userService = inject(UserService)
|
this.userService = inject(UserService)
|
||||||
this.settingsService = inject(SettingsService)
|
this.settingsService = inject(SettingsService)
|
||||||
|
|||||||
+29
-5
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core'
|
import { Component, OnInit, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -40,9 +40,33 @@ export class UserEditDialogComponent
|
|||||||
private toastService = inject(ToastService)
|
private toastService = inject(ToastService)
|
||||||
private groupsService: GroupService
|
private groupsService: GroupService
|
||||||
|
|
||||||
groups: Group[]
|
private groupsSignal = signal<Group[]>(undefined)
|
||||||
passwordIsSet: boolean = false
|
private passwordIsSetSignal = signal(false)
|
||||||
public totpLoading: boolean = false
|
private totpLoadingSignal = signal(false)
|
||||||
|
|
||||||
|
get groups(): Group[] {
|
||||||
|
return this.groupsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set groups(groups: Group[]) {
|
||||||
|
this.groupsSignal.set(groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
get passwordIsSet(): boolean {
|
||||||
|
return this.passwordIsSetSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set passwordIsSet(passwordIsSet: boolean) {
|
||||||
|
this.passwordIsSetSignal.set(passwordIsSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get totpLoading(): boolean {
|
||||||
|
return this.totpLoadingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set totpLoading(totpLoading: boolean) {
|
||||||
|
this.totpLoadingSignal.set(totpLoading)
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
@@ -103,7 +127,7 @@ export class UserEditDialogComponent
|
|||||||
if (!groupsVal) return []
|
if (!groupsVal) return []
|
||||||
else
|
else
|
||||||
return groupsVal.flatMap(
|
return groupsVal.flatMap(
|
||||||
(id) => this.groups.find((g) => g.id == id)?.permissions
|
(id) => this.groups?.find((g) => g.id == id)?.permissions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+74
-9
@@ -4,7 +4,7 @@ import {
|
|||||||
moveItemInArray,
|
moveItemInArray,
|
||||||
} from '@angular/cdk/drag-drop'
|
} from '@angular/cdk/drag-drop'
|
||||||
import { NgTemplateOutlet } from '@angular/common'
|
import { NgTemplateOutlet } from '@angular/common'
|
||||||
import { Component, OnInit, inject } from '@angular/core'
|
import { Component, OnInit, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
AbstractControl,
|
AbstractControl,
|
||||||
FormArray,
|
FormArray,
|
||||||
@@ -476,17 +476,73 @@ export class WorkflowEditDialogComponent
|
|||||||
private mailRuleService: MailRuleService
|
private mailRuleService: MailRuleService
|
||||||
private customFieldsService: CustomFieldsService
|
private customFieldsService: CustomFieldsService
|
||||||
|
|
||||||
templates: Workflow[]
|
private templatesSignal = signal<Workflow[]>(undefined)
|
||||||
correspondents: Correspondent[]
|
private correspondentsSignal = signal<Correspondent[]>(undefined)
|
||||||
documentTypes: DocumentType[]
|
private documentTypesSignal = signal<DocumentType[]>(undefined)
|
||||||
storagePaths: StoragePath[]
|
private storagePathsSignal = signal<StoragePath[]>(undefined)
|
||||||
mailRules: MailRule[]
|
private mailRulesSignal = signal<MailRule[]>(undefined)
|
||||||
customFields: CustomField[]
|
private customFieldsSignal = signal<CustomField[]>(undefined)
|
||||||
dateCustomFields: CustomField[]
|
private dateCustomFieldsSignal = signal<CustomField[]>(undefined)
|
||||||
|
|
||||||
|
get templates(): Workflow[] {
|
||||||
|
return this.templatesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set templates(templates: Workflow[]) {
|
||||||
|
this.templatesSignal.set(templates)
|
||||||
|
}
|
||||||
|
|
||||||
|
get correspondents(): Correspondent[] {
|
||||||
|
return this.correspondentsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set correspondents(correspondents: Correspondent[]) {
|
||||||
|
this.correspondentsSignal.set(correspondents)
|
||||||
|
}
|
||||||
|
|
||||||
|
get documentTypes(): DocumentType[] {
|
||||||
|
return this.documentTypesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentTypes(documentTypes: DocumentType[]) {
|
||||||
|
this.documentTypesSignal.set(documentTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
get storagePaths(): StoragePath[] {
|
||||||
|
return this.storagePathsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set storagePaths(storagePaths: StoragePath[]) {
|
||||||
|
this.storagePathsSignal.set(storagePaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
get mailRules(): MailRule[] {
|
||||||
|
return this.mailRulesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set mailRules(mailRules: MailRule[]) {
|
||||||
|
this.mailRulesSignal.set(mailRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
get customFields(): CustomField[] {
|
||||||
|
return this.customFieldsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set customFields(customFields: CustomField[]) {
|
||||||
|
this.customFieldsSignal.set(customFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
get dateCustomFields(): CustomField[] {
|
||||||
|
return this.dateCustomFieldsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set dateCustomFields(dateCustomFields: CustomField[]) {
|
||||||
|
this.dateCustomFieldsSignal.set(dateCustomFields)
|
||||||
|
}
|
||||||
|
|
||||||
expandedItem: number = null
|
expandedItem: number = null
|
||||||
|
|
||||||
private allowedActionTypes = []
|
private allowedActionTypesSignal = signal([])
|
||||||
|
|
||||||
private readonly triggerFilterOptionsMap = new WeakMap<
|
private readonly triggerFilterOptionsMap = new WeakMap<
|
||||||
FormArray,
|
FormArray,
|
||||||
@@ -1294,9 +1350,18 @@ export class WorkflowEditDialogComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get actionTypeOptions() {
|
get actionTypeOptions() {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return this.allowedActionTypes
|
return this.allowedActionTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get allowedActionTypes() {
|
||||||
|
return this.allowedActionTypesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set allowedActionTypes(allowedActionTypes) {
|
||||||
|
this.allowedActionTypesSignal.set(allowedActionTypes)
|
||||||
|
}
|
||||||
|
|
||||||
getActionTypeOptionName(type: WorkflowActionType): string {
|
getActionTypeOptionName(type: WorkflowActionType): string {
|
||||||
return this.actionTypeOptions.find((t) => t.id === type)?.name ?? ''
|
return this.actionTypeOptions.find((t) => t.id === type)?.name ?? ''
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-8
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, inject } from '@angular/core'
|
import { Component, Input, inject, signal } from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
@@ -17,23 +17,37 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission
|
|||||||
private documentService = inject(DocumentService)
|
private documentService = inject(DocumentService)
|
||||||
private toastService = inject(ToastService)
|
private toastService = inject(ToastService)
|
||||||
|
|
||||||
@Input()
|
private documentIdsSignal = signal<number[]>(undefined)
|
||||||
documentIds: number[]
|
private hasArchiveVersionSignal = signal(true)
|
||||||
|
private useArchiveVersionSignal = signal(true)
|
||||||
|
|
||||||
private _hasArchiveVersion: boolean = true
|
@Input()
|
||||||
|
get documentIds(): number[] {
|
||||||
|
return this.documentIdsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentIds(documentIds: number[]) {
|
||||||
|
this.documentIdsSignal.set(documentIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
get useArchiveVersion(): boolean {
|
||||||
|
return this.useArchiveVersionSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set useArchiveVersion(useArchiveVersion: boolean) {
|
||||||
|
this.useArchiveVersionSignal.set(useArchiveVersion)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set hasArchiveVersion(value: boolean) {
|
set hasArchiveVersion(value: boolean) {
|
||||||
this._hasArchiveVersion = value
|
this.hasArchiveVersionSignal.set(value)
|
||||||
this.useArchiveVersion = value
|
this.useArchiveVersion = value
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasArchiveVersion(): boolean {
|
get hasArchiveVersion(): boolean {
|
||||||
return this._hasArchiveVersion
|
return this.hasArchiveVersionSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
public useArchiveVersion: boolean = true
|
|
||||||
|
|
||||||
public emailAddress: string = ''
|
public emailAddress: string = ''
|
||||||
public emailSubject: string = ''
|
public emailSubject: string = ''
|
||||||
public emailMessage: string = ''
|
public emailMessage: string = ''
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject, signal } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
const SYMBOLS = {
|
const SYMBOLS = {
|
||||||
@@ -20,9 +20,24 @@ const SYMBOLS = {
|
|||||||
})
|
})
|
||||||
export class HotkeyDialogComponent {
|
export class HotkeyDialogComponent {
|
||||||
activeModal = inject(NgbActiveModal)
|
activeModal = inject(NgbActiveModal)
|
||||||
|
private titleSignal = signal($localize`Keyboard shortcuts`)
|
||||||
|
private hotkeysSignal = signal<Map<string, string>>(new Map())
|
||||||
|
|
||||||
public title: string = $localize`Keyboard shortcuts`
|
public get title(): string {
|
||||||
public hotkeys: Map<string, string> = new Map()
|
return this.titleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set title(title: string) {
|
||||||
|
this.titleSignal.set(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get hotkeys(): Map<string, string> {
|
||||||
|
return this.hotkeysSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set hotkeys(hotkeys: Map<string, string>) {
|
||||||
|
this.hotkeysSignal.set(hotkeys)
|
||||||
|
}
|
||||||
|
|
||||||
public close(): void {
|
public close(): void {
|
||||||
this.activeModal.close()
|
this.activeModal.close()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
|
signal,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ControlValueAccessor } from '@angular/forms'
|
import { ControlValueAccessor } from '@angular/forms'
|
||||||
@@ -61,7 +62,15 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
|||||||
@Output()
|
@Output()
|
||||||
removed: EventEmitter<AbstractInputComponent<any>> = new EventEmitter()
|
removed: EventEmitter<AbstractInputComponent<any>> = new EventEmitter()
|
||||||
|
|
||||||
value: T
|
private valueSignal = signal<T>(undefined)
|
||||||
|
|
||||||
|
get value(): T {
|
||||||
|
return this.valueSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(value: T) {
|
||||||
|
this.valueSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.inputId = uuidv4()
|
this.inputId = uuidv4()
|
||||||
|
|||||||
+10
-2
@@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, inject } from '@angular/core'
|
import { Component, forwardRef, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NG_VALUE_ACCESSOR,
|
NG_VALUE_ACCESSOR,
|
||||||
@@ -24,7 +24,15 @@ import { AbstractInputComponent } from '../../abstract-input'
|
|||||||
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
|
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
|
||||||
})
|
})
|
||||||
export class PermissionsGroupComponent extends AbstractInputComponent<Group> {
|
export class PermissionsGroupComponent extends AbstractInputComponent<Group> {
|
||||||
groups: Group[]
|
private groupsSignal = signal<Group[]>(undefined)
|
||||||
|
|
||||||
|
get groups(): Group[] {
|
||||||
|
return this.groupsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set groups(groups: Group[]) {
|
||||||
|
this.groupsSignal.set(groups)
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const groupService = inject(GroupService)
|
const groupService = inject(GroupService)
|
||||||
|
|||||||
+10
-2
@@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef, inject } from '@angular/core'
|
import { Component, forwardRef, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NG_VALUE_ACCESSOR,
|
NG_VALUE_ACCESSOR,
|
||||||
@@ -24,7 +24,15 @@ import { AbstractInputComponent } from '../../abstract-input'
|
|||||||
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
|
imports: [NgSelectComponent, FormsModule, ReactiveFormsModule],
|
||||||
})
|
})
|
||||||
export class PermissionsUserComponent extends AbstractInputComponent<User[]> {
|
export class PermissionsUserComponent extends AbstractInputComponent<User[]> {
|
||||||
users: User[]
|
private usersSignal = signal<User[]>(undefined)
|
||||||
|
|
||||||
|
get users(): User[] {
|
||||||
|
return this.usersSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set users(users: User[]) {
|
||||||
|
this.usersSignal.set(users)
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const userService = inject(UserService)
|
const userService = inject(UserService)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
|
signal,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
@@ -109,7 +110,15 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
|||||||
|
|
||||||
@ViewChild('tagSelect') select: NgSelectComponent
|
@ViewChild('tagSelect') select: NgSelectComponent
|
||||||
|
|
||||||
value: number[] = []
|
private valueSignal = signal<number[]>([])
|
||||||
|
|
||||||
|
get value(): number[] {
|
||||||
|
return this.valueSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(value: number[]) {
|
||||||
|
this.valueSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
tags: Tag[] = []
|
tags: Tag[] = []
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('LogoComponent', () => {
|
|||||||
|
|
||||||
it('should support extra classes', () => {
|
it('should support extra classes', () => {
|
||||||
expect(fixture.debugElement.queryAll(By.css('.foo'))).toHaveLength(0)
|
expect(fixture.debugElement.queryAll(By.css('.foo'))).toHaveLength(0)
|
||||||
component.extra_classes = 'foo'
|
fixture.componentRef.setInput('extra_classes', 'foo')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.queryAll(By.css('.foo'))).toHaveLength(1)
|
expect(fixture.debugElement.queryAll(By.css('.foo'))).toHaveLength(1)
|
||||||
})
|
})
|
||||||
@@ -37,7 +37,7 @@ describe('LogoComponent', () => {
|
|||||||
expect(fixture.debugElement.query(By.css('svg')).attributes.style).toEqual(
|
expect(fixture.debugElement.query(By.css('svg')).attributes.style).toEqual(
|
||||||
'height:6em'
|
'height:6em'
|
||||||
)
|
)
|
||||||
component.height = '10em'
|
fixture.componentRef.setInput('height', '10em')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.query(By.css('svg')).attributes.style).toEqual(
|
expect(fixture.debugElement.query(By.css('svg')).attributes.style).toEqual(
|
||||||
'height:10em'
|
'height:10em'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, inject } from '@angular/core'
|
import { Component, Input, inject, signal } from '@angular/core'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
@@ -10,12 +10,26 @@ import { environment } from 'src/environments/environment'
|
|||||||
})
|
})
|
||||||
export class LogoComponent {
|
export class LogoComponent {
|
||||||
private settingsService = inject(SettingsService)
|
private settingsService = inject(SettingsService)
|
||||||
|
private extraClassesSignal = signal<string>(undefined)
|
||||||
|
private heightSignal = signal('6em')
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
extra_classes: string
|
get extra_classes(): string {
|
||||||
|
return this.extraClassesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set extra_classes(extraClasses: string) {
|
||||||
|
this.extraClassesSignal.set(extraClasses)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
height = '6em'
|
get height(): string {
|
||||||
|
return this.heightSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set height(height: string) {
|
||||||
|
this.heightSignal.set(height)
|
||||||
|
}
|
||||||
|
|
||||||
get customLogo(): string {
|
get customLogo(): string {
|
||||||
return this.settingsService.get(SETTINGS_KEYS.APP_LOGO)?.length
|
return this.settingsService.get(SETTINGS_KEYS.APP_LOGO)?.length
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Clipboard } from '@angular/cdk/clipboard'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
import { Component, Input, inject } from '@angular/core'
|
import { Component, Input, inject, signal } from '@angular/core'
|
||||||
import { Title } from '@angular/platform-browser'
|
import { Title } from '@angular/platform-browser'
|
||||||
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
@@ -16,34 +16,77 @@ export class PageHeaderComponent {
|
|||||||
private titleService = inject(Title)
|
private titleService = inject(Title)
|
||||||
private clipboard = inject(Clipboard)
|
private clipboard = inject(Clipboard)
|
||||||
|
|
||||||
private _title = ''
|
private titleSignal = signal('')
|
||||||
public copied: boolean = false
|
private idSignal = signal<number>(undefined)
|
||||||
|
private subTitleSignal = signal('')
|
||||||
|
private infoSignal = signal<string>(undefined)
|
||||||
|
private infoLinkSignal = signal<string>(undefined)
|
||||||
|
private loadingSignal = signal(false)
|
||||||
|
private copiedSignal = signal(false)
|
||||||
private copyTimeout: any
|
private copyTimeout: any
|
||||||
|
|
||||||
|
public get copied(): boolean {
|
||||||
|
return this.copiedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set copied(copied: boolean) {
|
||||||
|
this.copiedSignal.set(copied)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set title(title: string) {
|
set title(title: string) {
|
||||||
this._title = title
|
this.titleSignal.set(title)
|
||||||
this.titleService.setTitle(`${this.title} - ${environment.appTitle}`)
|
this.titleService.setTitle(`${this.title} - ${environment.appTitle}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return this._title
|
return this.titleSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
id: number
|
get id(): number {
|
||||||
|
return this.idSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set id(id: number) {
|
||||||
|
this.idSignal.set(id)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
subTitle: string = ''
|
get subTitle(): string {
|
||||||
|
return this.subTitleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set subTitle(subTitle: string) {
|
||||||
|
this.subTitleSignal.set(subTitle)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
info: string
|
get info(): string {
|
||||||
|
return this.infoSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set info(info: string) {
|
||||||
|
this.infoSignal.set(info)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
infoLink: string
|
get infoLink(): string {
|
||||||
|
return this.infoLinkSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set infoLink(infoLink: string) {
|
||||||
|
this.infoLinkSignal.set(infoLink)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
loading: boolean = false
|
get loading(): boolean {
|
||||||
|
return this.loadingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set loading(loading: boolean) {
|
||||||
|
this.loadingSignal.set(loading)
|
||||||
|
}
|
||||||
|
|
||||||
public copyID() {
|
public copyID() {
|
||||||
this.copied = this.clipboard.copy(this.id.toString())
|
this.copied = this.clipboard.copy(this.id.toString())
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
DragDropModule,
|
DragDropModule,
|
||||||
moveItemInArray,
|
moveItemInArray,
|
||||||
} from '@angular/cdk/drag-drop'
|
} from '@angular/cdk/drag-drop'
|
||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject, signal } from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
@@ -45,8 +45,25 @@ export class PDFEditorComponent extends ConfirmDialogComponent {
|
|||||||
private readonly settingsService = inject(SettingsService)
|
private readonly settingsService = inject(SettingsService)
|
||||||
activeModal: NgbActiveModal = inject(NgbActiveModal)
|
activeModal: NgbActiveModal = inject(NgbActiveModal)
|
||||||
|
|
||||||
documentID: number
|
private documentIDSignal = signal<number>(undefined)
|
||||||
versionID?: number
|
private versionIDSignal = signal<number>(undefined)
|
||||||
|
|
||||||
|
get documentID(): number {
|
||||||
|
return this.documentIDSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentID(documentID: number) {
|
||||||
|
this.documentIDSignal.set(documentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
get versionID(): number {
|
||||||
|
return this.versionIDSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set versionID(versionID: number) {
|
||||||
|
this.versionIDSignal.set(versionID)
|
||||||
|
}
|
||||||
|
|
||||||
pages: PageOperation[] = []
|
pages: PageOperation[] = []
|
||||||
totalPages = 0
|
totalPages = 0
|
||||||
editMode: PdfEditorEditMode = this.settingsService.get(
|
editMode: PdfEditorEditMode = this.settingsService.get(
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
} from 'pdfjs-dist/web/pdf_viewer.mjs'
|
} from 'pdfjs-dist/web/pdf_viewer.mjs'
|
||||||
import {
|
import {
|
||||||
PdfRenderMode,
|
PdfRenderMode,
|
||||||
PdfSource,
|
|
||||||
PdfZoomLevel,
|
PdfZoomLevel,
|
||||||
PdfZoomScale,
|
PdfZoomScale,
|
||||||
PngxPdfDocumentProxy,
|
PngxPdfDocumentProxy,
|
||||||
@@ -43,7 +42,8 @@ export class PngxPdfViewerComponent
|
|||||||
{
|
{
|
||||||
private readonly document = inject<Document>(DOCUMENT)
|
private readonly document = inject<Document>(DOCUMENT)
|
||||||
|
|
||||||
@Input() src!: PdfSource
|
@Input() src!: string
|
||||||
|
@Input() password?: string
|
||||||
@Input() page?: number
|
@Input() page?: number
|
||||||
@Output() pageChange = new EventEmitter<number>()
|
@Output() pageChange = new EventEmitter<number>()
|
||||||
@Input() rotation?: number
|
@Input() rotation?: number
|
||||||
@@ -93,7 +93,7 @@ export class PngxPdfViewerComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (changes['src']) {
|
if (changes['src'] || changes['password']) {
|
||||||
this.resetViewerState()
|
this.resetViewerState()
|
||||||
if (this.src) {
|
if (this.src) {
|
||||||
this.loadDocument()
|
this.loadDocument()
|
||||||
@@ -174,8 +174,12 @@ export class PngxPdfViewerComponent
|
|||||||
'assets/js/pdf.worker.min.mjs',
|
'assets/js/pdf.worker.min.mjs',
|
||||||
this.document.baseURI
|
this.document.baseURI
|
||||||
).toString()
|
).toString()
|
||||||
this.loadingTask = getDocument(this.src)
|
let initOptions = {
|
||||||
|
url: this.src,
|
||||||
|
password: this.password,
|
||||||
|
withCredentials: true,
|
||||||
|
}
|
||||||
|
this.loadingTask = getDocument(initOptions)
|
||||||
try {
|
try {
|
||||||
const pdf = await this.loadingTask.promise
|
const pdf = await this.loadingTask.promise
|
||||||
this.pdf = pdf
|
this.pdf = pdf
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ export type PngxPdfDocumentProxy = {
|
|||||||
numPages: number
|
numPages: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PdfSource = string | { url: string; password?: string }
|
|
||||||
|
|
||||||
export enum PdfRenderMode {
|
export enum PdfRenderMode {
|
||||||
Single = 'single',
|
Single = 'single',
|
||||||
All = 'all',
|
All = 'all',
|
||||||
|
|||||||
+41
-5
@@ -1,4 +1,11 @@
|
|||||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -27,21 +34,44 @@ export class PermissionsDialogComponent {
|
|||||||
activeModal = inject(NgbActiveModal)
|
activeModal = inject(NgbActiveModal)
|
||||||
private userService = inject(UserService)
|
private userService = inject(UserService)
|
||||||
|
|
||||||
users: User[]
|
private usersSignal = signal<User[]>(undefined)
|
||||||
|
private titleSignal = signal($localize`Set permissions`)
|
||||||
|
private noteSignal = signal<string>(null)
|
||||||
|
private buttonsEnabledSignal = signal(true)
|
||||||
private o: ObjectWithPermissions = undefined
|
private o: ObjectWithPermissions = undefined
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.userService.listAll().subscribe((r) => (this.users = r.results))
|
this.userService.listAll().subscribe((r) => (this.users = r.results))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get users(): User[] {
|
||||||
|
return this.usersSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set users(users: User[]) {
|
||||||
|
this.usersSignal.set(users)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
public confirmClicked = new EventEmitter()
|
public confirmClicked = new EventEmitter()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
title = $localize`Set permissions`
|
get title(): string {
|
||||||
|
return this.titleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set title(title: string) {
|
||||||
|
this.titleSignal.set(title)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
note: string = null
|
get note(): string {
|
||||||
|
return this.noteSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set note(note: string) {
|
||||||
|
this.noteSignal.set(note)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set object(o: ObjectWithPermissions) {
|
set object(o: ObjectWithPermissions) {
|
||||||
@@ -65,7 +95,13 @@ export class PermissionsDialogComponent {
|
|||||||
merge: new FormControl(true),
|
merge: new FormControl(true),
|
||||||
})
|
})
|
||||||
|
|
||||||
buttonsEnabled: boolean = true
|
get buttonsEnabled(): boolean {
|
||||||
|
return this.buttonsEnabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set buttonsEnabled(buttonsEnabled: boolean) {
|
||||||
|
this.buttonsEnabledSignal.set(buttonsEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
get permissions() {
|
get permissions() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+73
-15
@@ -1,5 +1,5 @@
|
|||||||
import { Clipboard } from '@angular/cdk/clipboard'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
import { Component, OnInit, inject } from '@angular/core'
|
import { Component, OnInit, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -50,8 +50,28 @@ export class ProfileEditDialogComponent
|
|||||||
private toastService = inject(ToastService)
|
private toastService = inject(ToastService)
|
||||||
private clipboard = inject(Clipboard)
|
private clipboard = inject(Clipboard)
|
||||||
|
|
||||||
public networkActive: boolean = false
|
private networkActiveSignal = signal(false)
|
||||||
public error: any
|
private errorSignal = signal<any>(undefined)
|
||||||
|
private showPasswordConfirmSignal = signal(false)
|
||||||
|
private showEmailConfirmSignal = signal(false)
|
||||||
|
private copiedSignal = signal(false)
|
||||||
|
private codesCopiedSignal = signal(false)
|
||||||
|
|
||||||
|
public get networkActive(): boolean {
|
||||||
|
return this.networkActiveSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set networkActive(networkActive: boolean) {
|
||||||
|
this.networkActiveSignal.set(networkActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get error(): any {
|
||||||
|
return this.errorSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set error(error: any) {
|
||||||
|
this.errorSignal.set(error)
|
||||||
|
}
|
||||||
|
|
||||||
public form = new FormGroup({
|
public form = new FormGroup({
|
||||||
email: new FormControl(''),
|
email: new FormControl(''),
|
||||||
@@ -67,13 +87,26 @@ export class ProfileEditDialogComponent
|
|||||||
private currentPassword: string
|
private currentPassword: string
|
||||||
private newPassword: string
|
private newPassword: string
|
||||||
private passwordConfirm: string
|
private passwordConfirm: string
|
||||||
public showPasswordConfirm: boolean = false
|
public get showPasswordConfirm(): boolean {
|
||||||
|
return this.showPasswordConfirmSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set showPasswordConfirm(showPasswordConfirm: boolean) {
|
||||||
|
this.showPasswordConfirmSignal.set(showPasswordConfirm)
|
||||||
|
}
|
||||||
|
|
||||||
public hasUsablePassword: boolean = false
|
public hasUsablePassword: boolean = false
|
||||||
|
|
||||||
private currentEmail: string
|
private currentEmail: string
|
||||||
private newEmail: string
|
private newEmail: string
|
||||||
private emailConfirm: string
|
private emailConfirm: string
|
||||||
public showEmailConfirm: boolean = false
|
public get showEmailConfirm(): boolean {
|
||||||
|
return this.showEmailConfirmSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set showEmailConfirm(showEmailConfirm: boolean) {
|
||||||
|
this.showEmailConfirmSignal.set(showEmailConfirm)
|
||||||
|
}
|
||||||
|
|
||||||
public isTotpEnabled: boolean = false
|
public isTotpEnabled: boolean = false
|
||||||
public totpSettings: TotpSettings
|
public totpSettings: TotpSettings
|
||||||
@@ -81,8 +114,21 @@ export class ProfileEditDialogComponent
|
|||||||
public totpLoading: boolean = false
|
public totpLoading: boolean = false
|
||||||
public recoveryCodes: string[]
|
public recoveryCodes: string[]
|
||||||
|
|
||||||
public copied: boolean = false
|
public get copied(): boolean {
|
||||||
public codesCopied: boolean = false
|
return this.copiedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set copied(copied: boolean) {
|
||||||
|
this.copiedSignal.set(copied)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get codesCopied(): boolean {
|
||||||
|
return this.codesCopiedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set codesCopied(codesCopied: boolean) {
|
||||||
|
this.codesCopiedSignal.set(codesCopied)
|
||||||
|
}
|
||||||
|
|
||||||
public socialAccounts: SocialAccount[] = []
|
public socialAccounts: SocialAccount[] = []
|
||||||
public socialAccountProviders: SocialAccountProvider[] = []
|
public socialAccountProviders: SocialAccountProvider[] = []
|
||||||
@@ -144,14 +190,13 @@ export class ProfileEditDialogComponent
|
|||||||
if (this.showEmailConfirm) {
|
if (this.showEmailConfirm) {
|
||||||
this.form.get('email_confirm').enable()
|
this.form.get('email_confirm').enable()
|
||||||
if (this.newEmail !== this.emailConfirm) {
|
if (this.newEmail !== this.emailConfirm) {
|
||||||
if (!this.error) this.error = {}
|
this.setFieldError('email_confirm', $localize`Emails must match`)
|
||||||
this.error.email_confirm = $localize`Emails must match`
|
|
||||||
} else {
|
} else {
|
||||||
delete this.error?.email_confirm
|
this.clearFieldError('email_confirm')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.form.get('email_confirm').disable()
|
this.form.get('email_confirm').disable()
|
||||||
delete this.error?.email_confirm
|
this.clearFieldError('email_confirm')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,17 +217,30 @@ export class ProfileEditDialogComponent
|
|||||||
if (this.showPasswordConfirm) {
|
if (this.showPasswordConfirm) {
|
||||||
this.form.get('password_confirm').enable()
|
this.form.get('password_confirm').enable()
|
||||||
if (this.newPassword !== this.passwordConfirm) {
|
if (this.newPassword !== this.passwordConfirm) {
|
||||||
if (!this.error) this.error = {}
|
this.setFieldError('password_confirm', $localize`Passwords must match`)
|
||||||
this.error.password_confirm = $localize`Passwords must match`
|
|
||||||
} else {
|
} else {
|
||||||
delete this.error?.password_confirm
|
this.clearFieldError('password_confirm')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.form.get('password_confirm').disable()
|
this.form.get('password_confirm').disable()
|
||||||
delete this.error?.password_confirm
|
this.clearFieldError('password_confirm')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setFieldError(fieldName: string, message: string): void {
|
||||||
|
this.error = {
|
||||||
|
...(this.error ?? {}),
|
||||||
|
[fieldName]: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearFieldError(fieldName: string): void {
|
||||||
|
if (!this.error) return
|
||||||
|
const error = { ...this.error }
|
||||||
|
delete error[fieldName]
|
||||||
|
this.error = Object.keys(error).length ? error : undefined
|
||||||
|
}
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
const passwordChanged =
|
const passwordChanged =
|
||||||
this.newPassword && this.currentPassword !== this.newPassword
|
this.newPassword && this.currentPassword !== this.newPassword
|
||||||
|
|||||||
+26
-9
@@ -1,6 +1,6 @@
|
|||||||
import { Clipboard } from '@angular/cdk/clipboard'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { Component, Input, inject } from '@angular/core'
|
import { Component, Input, inject, signal } from '@angular/core'
|
||||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'
|
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { Document } from 'src/app/data/document'
|
import { Document } from 'src/app/data/document'
|
||||||
@@ -38,10 +38,27 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
|||||||
private readonly clipboard = inject(Clipboard)
|
private readonly clipboard = inject(Clipboard)
|
||||||
private readonly toastService = inject(ToastService)
|
private readonly toastService = inject(ToastService)
|
||||||
|
|
||||||
private _documents: Document[] = []
|
private documentsSignal = signal<Document[]>([])
|
||||||
|
private selectionCountSignal = signal(0)
|
||||||
|
private documentPreviewSignal = signal<Document[]>([])
|
||||||
|
private copiedSignal = signal(false)
|
||||||
|
|
||||||
|
get selectionCount(): number {
|
||||||
|
return this.selectionCountSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
get documentPreview(): Document[] {
|
||||||
|
return this.documentPreviewSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
get copied(): boolean {
|
||||||
|
return this.copiedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set copied(copied: boolean) {
|
||||||
|
this.copiedSignal.set(copied)
|
||||||
|
}
|
||||||
|
|
||||||
selectionCount = 0
|
|
||||||
documentPreview: Document[] = []
|
|
||||||
form: FormGroup = this.formBuilder.group({
|
form: FormGroup = this.formBuilder.group({
|
||||||
shareArchiveVersion: true,
|
shareArchiveVersion: true,
|
||||||
expirationDays: [7],
|
expirationDays: [7],
|
||||||
@@ -51,7 +68,6 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
|||||||
readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
|
readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
|
||||||
|
|
||||||
createdBundle: ShareLinkBundleSummary | null = null
|
createdBundle: ShareLinkBundleSummary | null = null
|
||||||
copied = false
|
|
||||||
onOpenManage?: () => void
|
onOpenManage?: () => void
|
||||||
readonly statuses = ShareLinkBundleStatus
|
readonly statuses = ShareLinkBundleStatus
|
||||||
|
|
||||||
@@ -64,15 +80,16 @@ export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
|||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set documents(docs: Document[]) {
|
set documents(docs: Document[]) {
|
||||||
this._documents = docs.concat()
|
const documents = docs.concat()
|
||||||
this.selectionCount = this._documents.length
|
this.documentsSignal.set(documents)
|
||||||
this.documentPreview = this._documents.slice(0, 10)
|
this.selectionCountSignal.set(documents.length)
|
||||||
|
this.documentPreviewSignal.set(documents.slice(0, 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
if (this.createdBundle) return
|
if (this.createdBundle) return
|
||||||
this.payload = {
|
this.payload = {
|
||||||
document_ids: this._documents.map((doc) => doc.id),
|
document_ids: this.documentsSignal().map((doc) => doc.id),
|
||||||
file_version: this.form.value.shareArchiveVersion
|
file_version: this.form.value.shareArchiveVersion
|
||||||
? FileVersion.Archive
|
? FileVersion.Archive
|
||||||
: FileVersion.Original,
|
: FileVersion.Original,
|
||||||
|
|||||||
+27
-4
@@ -1,6 +1,6 @@
|
|||||||
import { Clipboard } from '@angular/cdk/clipboard'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'
|
||||||
import { NgbActiveModal, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { Subject, catchError, of, switchMap, takeUntil, timer } from 'rxjs'
|
import { Subject, catchError, of, switchMap, takeUntil, timer } from 'rxjs'
|
||||||
@@ -40,10 +40,33 @@ export class ShareLinkBundleManageDialogComponent
|
|||||||
private readonly clipboard = inject(Clipboard)
|
private readonly clipboard = inject(Clipboard)
|
||||||
|
|
||||||
title = $localize`Share link bundles`
|
title = $localize`Share link bundles`
|
||||||
|
private bundlesSignal = signal<ShareLinkBundleSummary[]>([])
|
||||||
|
private errorSignal = signal<string | null>(null)
|
||||||
|
private copiedSlugSignal = signal<string | null>(null)
|
||||||
|
|
||||||
bundles: ShareLinkBundleSummary[] = []
|
get bundles(): ShareLinkBundleSummary[] {
|
||||||
error: string | null = null
|
return this.bundlesSignal()
|
||||||
copiedSlug: string | null = null
|
}
|
||||||
|
|
||||||
|
set bundles(bundles: ShareLinkBundleSummary[]) {
|
||||||
|
this.bundlesSignal.set(bundles)
|
||||||
|
}
|
||||||
|
|
||||||
|
get error(): string | null {
|
||||||
|
return this.errorSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set error(error: string | null) {
|
||||||
|
this.errorSignal.set(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
get copiedSlug(): string | null {
|
||||||
|
return this.copiedSlugSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set copiedSlug(copiedSlug: string | null) {
|
||||||
|
this.copiedSlugSignal.set(copiedSlug)
|
||||||
|
}
|
||||||
|
|
||||||
readonly statuses = ShareLinkBundleStatus
|
readonly statuses = ShareLinkBundleStatus
|
||||||
readonly fileVersions = FileVersion
|
readonly fileVersions = FileVersion
|
||||||
|
|||||||
+38
-14
@@ -1,5 +1,5 @@
|
|||||||
import { Clipboard } from '@angular/cdk/clipboard'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
import { Component, Input, OnInit, inject } from '@angular/core'
|
import { Component, Input, OnInit, inject, signal } from '@angular/core'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
@@ -27,50 +27,74 @@ export class ShareLinksDialogComponent implements OnInit {
|
|||||||
|
|
||||||
readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
|
readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
|
||||||
|
|
||||||
@Input()
|
private titleSignal = signal($localize`Share Links`)
|
||||||
title = $localize`Share Links`
|
private documentIdSignal = signal<number>(undefined)
|
||||||
|
private hasArchiveVersionSignal = signal(true)
|
||||||
|
private shareLinksSignal = signal<ShareLink[]>(undefined)
|
||||||
|
private copiedSignal = signal<number>(undefined)
|
||||||
|
|
||||||
_documentId: number
|
@Input()
|
||||||
|
get title(): string {
|
||||||
|
return this.titleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set title(title: string) {
|
||||||
|
this.titleSignal.set(title)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set documentId(id: number) {
|
set documentId(id: number) {
|
||||||
if (id !== undefined) {
|
if (id !== undefined) {
|
||||||
this._documentId = id
|
this.documentIdSignal.set(id)
|
||||||
this.refresh()
|
this.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hasArchiveVersion: boolean = true
|
get documentId(): number {
|
||||||
|
return this.documentIdSignal()
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set hasArchiveVersion(value: boolean) {
|
set hasArchiveVersion(value: boolean) {
|
||||||
this._hasArchiveVersion = value
|
this.hasArchiveVersionSignal.set(value)
|
||||||
this.useArchiveVersion = value
|
this.useArchiveVersion = value
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasArchiveVersion(): boolean {
|
get hasArchiveVersion(): boolean {
|
||||||
return this._hasArchiveVersion
|
return this.hasArchiveVersionSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
shareLinks: ShareLink[]
|
get shareLinks(): ShareLink[] {
|
||||||
|
return this.shareLinksSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set shareLinks(shareLinks: ShareLink[]) {
|
||||||
|
this.shareLinksSignal.set(shareLinks)
|
||||||
|
}
|
||||||
|
|
||||||
loading: boolean = false
|
loading: boolean = false
|
||||||
|
|
||||||
copied: number
|
get copied(): number {
|
||||||
|
return this.copiedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set copied(copied: number) {
|
||||||
|
this.copiedSignal.set(copied)
|
||||||
|
}
|
||||||
|
|
||||||
expirationDays: number = 7
|
expirationDays: number = 7
|
||||||
|
|
||||||
useArchiveVersion: boolean = true
|
useArchiveVersion: boolean = true
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this._documentId !== undefined) this.refresh()
|
if (this.documentId !== undefined) this.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
if (this._documentId === undefined) return
|
if (this.documentId === undefined) return
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.shareLinkService
|
this.shareLinkService
|
||||||
.getLinksForDocument(this._documentId)
|
.getLinksForDocument(this.documentId)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (results) => {
|
next: (results) => {
|
||||||
@@ -141,7 +165,7 @@ export class ShareLinksDialogComponent implements OnInit {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
this.shareLinkService
|
this.shareLinkService
|
||||||
.createLinkForDocument(
|
.createLinkForDocument(
|
||||||
this._documentId,
|
this.documentId,
|
||||||
this.useArchiveVersion ? FileVersion.Archive : FileVersion.Original,
|
this.useArchiveVersion ? FileVersion.Archive : FileVersion.Original,
|
||||||
expiration
|
expiration
|
||||||
)
|
)
|
||||||
|
|||||||
+33
-4
@@ -4,6 +4,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
@@ -20,18 +21,46 @@ export class SuggestionsDropdownComponent {
|
|||||||
public popperOptions = pngxPopperOptions
|
public popperOptions = pngxPopperOptions
|
||||||
|
|
||||||
@ViewChild('dropdown') dropdown: NgbDropdown
|
@ViewChild('dropdown') dropdown: NgbDropdown
|
||||||
|
private suggestionsSignal = signal<DocumentSuggestions>(null)
|
||||||
|
private aiEnabledSignal = signal(false)
|
||||||
|
private loadingSignal = signal(false)
|
||||||
|
private disabledSignal = signal(false)
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
suggestions: DocumentSuggestions = null
|
get suggestions(): DocumentSuggestions {
|
||||||
|
return this.suggestionsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set suggestions(suggestions: DocumentSuggestions) {
|
||||||
|
this.suggestionsSignal.set(suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
aiEnabled: boolean = false
|
get aiEnabled(): boolean {
|
||||||
|
return this.aiEnabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set aiEnabled(aiEnabled: boolean) {
|
||||||
|
this.aiEnabledSignal.set(aiEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
loading: boolean = false
|
get loading(): boolean {
|
||||||
|
return this.loadingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set loading(loading: boolean) {
|
||||||
|
this.loadingSignal.set(loading)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
disabled: boolean = false
|
get disabled(): boolean {
|
||||||
|
return this.disabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set disabled(disabled: boolean) {
|
||||||
|
this.disabledSignal.set(disabled)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
getSuggestions: EventEmitter<SuggestionsDropdownComponent> =
|
getSuggestions: EventEmitter<SuggestionsDropdownComponent> =
|
||||||
|
|||||||
+73
-21
@@ -1,5 +1,12 @@
|
|||||||
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
|
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
|
||||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
NgbActiveModal,
|
NgbActiveModal,
|
||||||
NgbModalModule,
|
NgbModalModule,
|
||||||
@@ -50,15 +57,40 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
public SystemStatusItemStatus = SystemStatusItemStatus
|
public SystemStatusItemStatus = SystemStatusItemStatus
|
||||||
public PaperlessTaskType = PaperlessTaskType
|
public PaperlessTaskType = PaperlessTaskType
|
||||||
public status: SystemStatus
|
private statusSignal = signal<SystemStatus>(undefined)
|
||||||
public frontendVersion: string = environment.version
|
public frontendVersion: string = environment.version
|
||||||
public versionMismatch: boolean = false
|
private versionMismatchSignal = signal(false)
|
||||||
|
|
||||||
public copied: boolean = false
|
private copiedSignal = signal(false)
|
||||||
|
|
||||||
private runningTasks: Set<PaperlessTaskType> = new Set()
|
private runningTasksSignal = signal<Set<PaperlessTaskType>>(new Set())
|
||||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
get status(): SystemStatus {
|
||||||
|
return this.statusSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set status(status: SystemStatus) {
|
||||||
|
this.statusSignal.set(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
get versionMismatch(): boolean {
|
||||||
|
return this.versionMismatchSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set versionMismatch(versionMismatch: boolean) {
|
||||||
|
this.versionMismatchSignal.set(versionMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
get copied(): boolean {
|
||||||
|
return this.copiedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set copied(copied: boolean) {
|
||||||
|
this.copiedSignal.set(copied)
|
||||||
|
}
|
||||||
|
|
||||||
get currentUserIsSuperUser(): boolean {
|
get currentUserIsSuperUser(): boolean {
|
||||||
return this.permissionsService.isSuperUser()
|
return this.permissionsService.isSuperUser()
|
||||||
}
|
}
|
||||||
@@ -68,25 +100,23 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
|
const status = this.status
|
||||||
this.versionMismatch =
|
this.versionMismatch =
|
||||||
environment.production &&
|
environment.production &&
|
||||||
this.status.pngx_version &&
|
status.pngx_version &&
|
||||||
this.frontendVersion &&
|
this.frontendVersion &&
|
||||||
this.status.pngx_version !== this.frontendVersion
|
status.pngx_version !== this.frontendVersion
|
||||||
if (this.versionMismatch) {
|
if (this.versionMismatch) {
|
||||||
this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})`
|
this.status = {
|
||||||
|
...status,
|
||||||
|
pngx_version: `${status.pngx_version} (frontend: ${this.frontendVersion})`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.status.websocket_connected = this.websocketStatusService.isConnected()
|
this.updateWebsocketStatus(this.websocketStatusService.isConnected())
|
||||||
? SystemStatusItemStatus.OK
|
|
||||||
: SystemStatusItemStatus.ERROR
|
|
||||||
this.websocketStatusService
|
this.websocketStatusService
|
||||||
.onConnectionStatus()
|
.onConnectionStatus()
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((connected) => {
|
.subscribe((connected) => this.updateWebsocketStatus(connected))
|
||||||
this.status.websocket_connected = connected
|
|
||||||
? SystemStatusItemStatus.OK
|
|
||||||
: SystemStatusItemStatus.ERROR
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public close() {
|
public close() {
|
||||||
@@ -108,23 +138,26 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isRunning(taskName: PaperlessTaskType): boolean {
|
public isRunning(taskName: PaperlessTaskType): boolean {
|
||||||
return this.runningTasks.has(taskName)
|
return this.runningTasksSignal().has(taskName)
|
||||||
}
|
}
|
||||||
|
|
||||||
public runTask(taskName: PaperlessTaskType) {
|
public runTask(taskName: PaperlessTaskType) {
|
||||||
this.runningTasks.add(taskName)
|
this.setTaskRunning(taskName, true)
|
||||||
this.toastService.showInfo(`Task ${taskName} started`)
|
this.toastService.showInfo(`Task ${taskName} started`)
|
||||||
this.tasksService.run(taskName).subscribe({
|
this.tasksService.run(taskName).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.runningTasks.delete(taskName)
|
this.setTaskRunning(taskName, false)
|
||||||
this.systemStatusService.get().subscribe({
|
this.systemStatusService.get().subscribe({
|
||||||
next: (status) => {
|
next: (status) => {
|
||||||
Object.assign(this.status, status)
|
this.status = {
|
||||||
|
...this.status,
|
||||||
|
...status,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.runningTasks.delete(taskName)
|
this.setTaskRunning(taskName, false)
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
`Failed to start task ${taskName}, see the logs for more details`,
|
`Failed to start task ${taskName}, see the logs for more details`,
|
||||||
err
|
err
|
||||||
@@ -133,6 +166,25 @@ export class SystemStatusDialogComponent implements OnInit, OnDestroy {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateWebsocketStatus(connected: boolean): void {
|
||||||
|
this.status = {
|
||||||
|
...this.status,
|
||||||
|
websocket_connected: connected
|
||||||
|
? SystemStatusItemStatus.OK
|
||||||
|
: SystemStatusItemStatus.ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setTaskRunning(taskName: PaperlessTaskType, running: boolean): void {
|
||||||
|
const runningTasks = new Set(this.runningTasksSignal())
|
||||||
|
if (running) {
|
||||||
|
runningTasks.add(taskName)
|
||||||
|
} else {
|
||||||
|
runningTasks.delete(taskName)
|
||||||
|
}
|
||||||
|
this.runningTasksSignal.set(runningTasks)
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.unsubscribeNotifier.next(this)
|
this.unsubscribeNotifier.next(this)
|
||||||
this.unsubscribeNotifier.complete()
|
this.unsubscribeNotifier.complete()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, inject, Input } from '@angular/core'
|
import { Component, inject, Input, signal } from '@angular/core'
|
||||||
import { Tag } from 'src/app/data/tag'
|
import { Tag } from 'src/app/data/tag'
|
||||||
import {
|
import {
|
||||||
PermissionAction,
|
PermissionAction,
|
||||||
@@ -16,16 +16,19 @@ export class TagComponent {
|
|||||||
private permissionsService = inject(PermissionsService)
|
private permissionsService = inject(PermissionsService)
|
||||||
private tagService = inject(TagService)
|
private tagService = inject(TagService)
|
||||||
|
|
||||||
private _tag: Tag
|
private tagSignal = signal<Tag>(null)
|
||||||
|
private linkTitleSignal = signal('')
|
||||||
|
private clickableSignal = signal(false)
|
||||||
|
private showParentsSignal = signal(false)
|
||||||
private _tagID: number
|
private _tagID: number
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
public set tag(tag: Tag) {
|
public set tag(tag: Tag) {
|
||||||
this._tag = tag
|
this.tagSignal.set(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
public get tag(): Tag {
|
public get tag(): Tag {
|
||||||
return this._tag
|
return this.tagSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
@@ -46,13 +49,31 @@ export class TagComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
linkTitle: string = ''
|
get linkTitle(): string {
|
||||||
|
return this.linkTitleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set linkTitle(linkTitle: string) {
|
||||||
|
this.linkTitleSignal.set(linkTitle)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
clickable: boolean = false
|
get clickable(): boolean {
|
||||||
|
return this.clickableSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set clickable(clickable: boolean) {
|
||||||
|
this.clickableSignal.set(clickable)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
showParents: boolean = false
|
get showParents(): boolean {
|
||||||
|
return this.showParentsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set showParents(showParents: boolean) {
|
||||||
|
this.showParentsSignal.set(showParents)
|
||||||
|
}
|
||||||
|
|
||||||
public get loading(): boolean {
|
public get loading(): boolean {
|
||||||
return this.tagService.loading
|
return this.tagService.loading
|
||||||
|
|||||||
@@ -34,10 +34,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col offset-sm-3">
|
<div class="col offset-sm-3">
|
||||||
<button class="btn btn-sm btn-outline-secondary" (click)="copyError(toast.error)">
|
<button class="btn btn-sm btn-outline-secondary" (click)="copyError(toast.error)">
|
||||||
@if (!copied) {
|
@if (!copied()) {
|
||||||
<i-bs name="clipboard" class="me-1"></i-bs>
|
<i-bs name="clipboard" class="me-1"></i-bs>
|
||||||
}
|
}
|
||||||
@if (copied) {
|
@if (copied()) {
|
||||||
<i-bs name="clipboard-check" class="me-1"></i-bs>
|
<i-bs name="clipboard-check" class="me-1"></i-bs>
|
||||||
}
|
}
|
||||||
<ng-container i18n>Copy Raw Error</ng-container>
|
<ng-container i18n>Copy Raw Error</ng-container>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { Clipboard } from '@angular/cdk/clipboard'
|
import { Clipboard } from '@angular/cdk/clipboard'
|
||||||
import { DecimalPipe } from '@angular/common'
|
import { DecimalPipe } from '@angular/common'
|
||||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
NgbProgressbarModule,
|
NgbProgressbarModule,
|
||||||
NgbToastModule,
|
NgbToastModule,
|
||||||
@@ -22,6 +29,7 @@ import { Toast } from 'src/app/services/toast.service'
|
|||||||
})
|
})
|
||||||
export class ToastComponent {
|
export class ToastComponent {
|
||||||
private clipboard = inject(Clipboard)
|
private clipboard = inject(Clipboard)
|
||||||
|
readonly copied = signal(false)
|
||||||
|
|
||||||
@Input() toast: Toast
|
@Input() toast: Toast
|
||||||
|
|
||||||
@@ -31,8 +39,6 @@ export class ToastComponent {
|
|||||||
|
|
||||||
@Output() closed: EventEmitter<Toast> = new EventEmitter<Toast>()
|
@Output() closed: EventEmitter<Toast> = new EventEmitter<Toast>()
|
||||||
|
|
||||||
public copied: boolean = false
|
|
||||||
|
|
||||||
onShown(toast: Toast) {
|
onShown(toast: Toast) {
|
||||||
if (!this.autohide) return
|
if (!this.autohide) return
|
||||||
|
|
||||||
@@ -62,9 +68,9 @@ export class ToastComponent {
|
|||||||
|
|
||||||
public copyError(error: any) {
|
public copyError(error: any) {
|
||||||
this.clipboard.copy(JSON.stringify(error))
|
this.clipboard.copy(JSON.stringify(error))
|
||||||
this.copied = true
|
this.copied.set(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.copied = false
|
this.copied.set(false)
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
@for (toast of toasts; track toast.id) {
|
@for (toast of toasts(); track toast.id) {
|
||||||
<pngx-toast [toast]="toast" [autohide]="true" (closed)="closeToast()"></pngx-toast>
|
<pngx-toast [toast]="toast" [autohide]="true" (closed)="closeToast()"></pngx-toast>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('ToastsComponent', () => {
|
|||||||
let component: ToastsComponent
|
let component: ToastsComponent
|
||||||
let fixture: ComponentFixture<ToastsComponent>
|
let fixture: ComponentFixture<ToastsComponent>
|
||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
let toastSubject: Subject<Toast> = new Subject()
|
let toastSubject: Subject<Toast>
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -33,10 +33,11 @@ describe('ToastsComponent', () => {
|
|||||||
],
|
],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ToastsComponent)
|
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
|
toastSubject = new Subject()
|
||||||
jest.replaceProperty(toastService, 'showToast', toastSubject)
|
jest.replaceProperty(toastService, 'showToast', toastSubject)
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ToastsComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
@@ -47,25 +48,15 @@ describe('ToastsComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should close toast', () => {
|
it('should close toast', () => {
|
||||||
component.toasts = [toast]
|
toastSubject.next(toast)
|
||||||
const closeToastSpy = jest.spyOn(toastService, 'closeToast')
|
const closeToastSpy = jest.spyOn(toastService, 'closeToast')
|
||||||
component.closeToast()
|
component.closeToast()
|
||||||
expect(component.toasts).toEqual([])
|
expect(component.toasts()).toEqual([])
|
||||||
expect(closeToastSpy).toHaveBeenCalledWith(toast)
|
expect(closeToastSpy).toHaveBeenCalledWith(toast)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should unsubscribe', () => {
|
it('should update from toastService', () => {
|
||||||
const unsubscribeSpy = jest.spyOn(
|
|
||||||
(component as any).subscription,
|
|
||||||
'unsubscribe'
|
|
||||||
)
|
|
||||||
component.ngOnDestroy()
|
|
||||||
expect(unsubscribeSpy).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should subscribe to toastService', () => {
|
|
||||||
component.ngOnInit()
|
|
||||||
toastSubject.next(toast)
|
toastSubject.next(toast)
|
||||||
expect(component.toasts).toEqual([toast])
|
expect(component.toasts()).toEqual([toast])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
import { Component, inject } from '@angular/core'
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import {
|
import {
|
||||||
NgbAccordionModule,
|
NgbAccordionModule,
|
||||||
NgbProgressbarModule,
|
NgbProgressbarModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { Subscription } from 'rxjs'
|
import { map, merge, Subject } from 'rxjs'
|
||||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||||
import { ToastComponent } from '../toast/toast.component'
|
import { ToastComponent } from '../toast/toast.component'
|
||||||
|
|
||||||
@@ -19,25 +20,21 @@ import { ToastComponent } from '../toast/toast.component'
|
|||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ToastsComponent implements OnInit, OnDestroy {
|
export class ToastsComponent {
|
||||||
toastService = inject(ToastService)
|
toastService = inject(ToastService)
|
||||||
|
|
||||||
private subscription: Subscription
|
private readonly closedToast = new Subject<void>()
|
||||||
|
|
||||||
public toasts: Toast[] = [] // array to force change detection
|
readonly toasts = toSignal(
|
||||||
|
merge(
|
||||||
ngOnDestroy(): void {
|
this.toastService.showToast.pipe(map((toast) => (toast ? [toast] : []))),
|
||||||
this.subscription?.unsubscribe()
|
this.closedToast.pipe(map(() => [] as Toast[]))
|
||||||
}
|
),
|
||||||
|
{ initialValue: [] as Toast[] }
|
||||||
ngOnInit(): void {
|
)
|
||||||
this.subscription = this.toastService.showToast.subscribe((toast) => {
|
|
||||||
this.toasts = toast ? [toast] : []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
closeToast() {
|
closeToast() {
|
||||||
this.toastService.closeToast(this.toasts[0])
|
this.toastService.closeToast(this.toasts()[0])
|
||||||
this.toasts = []
|
this.closedToast.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@for (v of dashboardViews; track v.id) {
|
@for (v of dashboardViews(); track v.id) {
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<pngx-saved-view-widget
|
<pngx-saved-view-widget
|
||||||
[savedView]="v"
|
[savedView]="v"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
DragDropModule,
|
DragDropModule,
|
||||||
moveItemInArray,
|
moveItemInArray,
|
||||||
} from '@angular/cdk/drag-drop'
|
} from '@angular/cdk/drag-drop'
|
||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject, signal } from '@angular/core'
|
||||||
import { RouterModule } from '@angular/router'
|
import { RouterModule } from '@angular/router'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { TourNgBootstrap, TourService } from 'ngx-ui-tour-ng-bootstrap'
|
import { TourNgBootstrap, TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
@@ -47,12 +47,12 @@ export class DashboardComponent extends ComponentWithPermissions {
|
|||||||
private tourService = inject(TourService)
|
private tourService = inject(TourService)
|
||||||
private toastService = inject(ToastService)
|
private toastService = inject(ToastService)
|
||||||
|
|
||||||
public dashboardViews: SavedView[] = []
|
readonly dashboardViews = signal<SavedView[]>([])
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.savedViewService.listAll().subscribe(() => {
|
this.savedViewService.listAll().subscribe(() => {
|
||||||
this.dashboardViews = this.savedViewService.dashboardViews
|
this.dashboardViews.set(this.savedViewService.dashboardViews)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,14 +81,12 @@ export class DashboardComponent extends ComponentWithPermissions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDrop(event: CdkDragDrop<SavedView[]>) {
|
onDrop(event: CdkDragDrop<SavedView[]>) {
|
||||||
moveItemInArray(
|
const dashboardViews = [...this.dashboardViews()]
|
||||||
this.dashboardViews,
|
moveItemInArray(dashboardViews, event.previousIndex, event.currentIndex)
|
||||||
event.previousIndex,
|
this.dashboardViews.set(dashboardViews)
|
||||||
event.currentIndex
|
|
||||||
)
|
|
||||||
|
|
||||||
this.settingsService
|
this.settingsService
|
||||||
.updateDashboardViewsSort(this.dashboardViews)
|
.updateDashboardViewsSort(this.dashboardViews())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.toastService.showInfo($localize`Dashboard updated`)
|
this.toastService.showInfo($localize`Dashboard updated`)
|
||||||
|
|||||||
+42
-7
@@ -1,11 +1,14 @@
|
|||||||
<pngx-widget-frame
|
<pngx-widget-frame
|
||||||
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
|
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
|
||||||
[title]="savedView.name"
|
[title]="savedView.name"
|
||||||
[badge]="count"
|
[loading]="false"
|
||||||
[loading]="loading"
|
|
||||||
[draggable]="savedView"
|
[draggable]="savedView"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
@if (count !== null) {
|
||||||
|
<span title-badge class="badge bg-info text-dark ms-2">{{count}}</span>
|
||||||
|
}
|
||||||
|
|
||||||
@if (documents.length) {
|
@if (documents.length) {
|
||||||
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
||||||
}
|
}
|
||||||
@@ -30,11 +33,24 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@if (loading && !documents.length) {
|
||||||
|
@for (row of placeholderRows; track row) {
|
||||||
|
<tr>
|
||||||
|
@for (field of displayFields; track field; let j = $index) {
|
||||||
|
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
|
||||||
|
<div class="placeholder-glow text-start">
|
||||||
|
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (row * 1/placeholderRows.length) }"></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
}
|
||||||
@for (doc of documents; track doc.id; let i = $index) {
|
@for (doc of documents; track doc.id; let i = $index) {
|
||||||
<tr>
|
<tr>
|
||||||
@for (field of displayFields; track field; let j = $index) {
|
@for (field of displayFields; track field; let j = $index) {
|
||||||
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
|
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
|
||||||
@if (loading && show) {
|
@if (loading && !documents.length) {
|
||||||
<div class="placeholder-glow text-start">
|
<div class="placeholder-glow text-start">
|
||||||
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (i * 1/documents.length) }"></span>
|
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (i * 1/documents.length) }"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,12 +131,22 @@
|
|||||||
</table>
|
</table>
|
||||||
} @else if (displayMode === DisplayMode.SMALL_CARDS) {
|
} @else if (displayMode === DisplayMode.SMALL_CARDS) {
|
||||||
<div class="row row-cols-paperless-cards my-n2">
|
<div class="row row-cols-paperless-cards my-n2">
|
||||||
|
@if (loading && !documents.length) {
|
||||||
|
@for (row of placeholderRows; track row) {
|
||||||
|
<pngx-document-card-small
|
||||||
|
class="p-0"
|
||||||
|
[ngStyle]="{ opacity: 1 - (row * 1/placeholderRows.length) }"
|
||||||
|
[document]="null"
|
||||||
|
[displayFields]="displayFields">
|
||||||
|
</pngx-document-card-small>
|
||||||
|
}
|
||||||
|
}
|
||||||
@for (d of documents; track d.id; let i = $index) {
|
@for (d of documents; track d.id; let i = $index) {
|
||||||
<pngx-document-card-small
|
<pngx-document-card-small
|
||||||
class="p-0"
|
class="p-0"
|
||||||
[ngStyle]="{ opacity: !loading && show ? 1 : 1 - (i * 1/documents.length) }"
|
[ngStyle]="{ opacity: show ? 1 : 1 - (i * 1/documents.length) }"
|
||||||
(dblClickDocument)="openDocumentDetail(d)"
|
(dblClickDocument)="openDocumentDetail(d)"
|
||||||
[document]="!loading && show ? d : null"
|
[document]="d"
|
||||||
[displayFields]="displayFields"
|
[displayFields]="displayFields"
|
||||||
(clickTag)="clickTag($event)"
|
(clickTag)="clickTag($event)"
|
||||||
(clickCorrespondent)="clickCorrespondent($event)"
|
(clickCorrespondent)="clickCorrespondent($event)"
|
||||||
@@ -131,11 +157,20 @@
|
|||||||
</div>
|
</div>
|
||||||
} @else if (displayMode === DisplayMode.LARGE_CARDS) {
|
} @else if (displayMode === DisplayMode.LARGE_CARDS) {
|
||||||
<div class="row my-n2">
|
<div class="row my-n2">
|
||||||
|
@if (loading && !documents.length) {
|
||||||
|
@for (row of placeholderRows; track row) {
|
||||||
|
<pngx-document-card-large
|
||||||
|
[document]="null"
|
||||||
|
[ngStyle]="{ opacity: 1 - (row * 1/placeholderRows.length) }"
|
||||||
|
[displayFields]="displayFields">
|
||||||
|
</pngx-document-card-large>
|
||||||
|
}
|
||||||
|
}
|
||||||
@for (d of documents; track d.id; let i = $index) {
|
@for (d of documents; track d.id; let i = $index) {
|
||||||
<pngx-document-card-large
|
<pngx-document-card-large
|
||||||
(dblClickDocument)="openDocumentDetail(d)"
|
(dblClickDocument)="openDocumentDetail(d)"
|
||||||
[document]="!loading && show ? d : null"
|
[document]="d"
|
||||||
[ngStyle]="{ opacity: !loading && show ? 1 : 1 - (i * 1/documents.length) }"
|
[ngStyle]="{ opacity: show ? 1 : 1 - (i * 1/documents.length) }"
|
||||||
[displayFields]="displayFields"
|
[displayFields]="displayFields"
|
||||||
(clickTag)="clickTag($event)"
|
(clickTag)="clickTag($event)"
|
||||||
(clickCorrespondent)="clickCorrespondent($event)"
|
(clickCorrespondent)="clickCorrespondent($event)"
|
||||||
|
|||||||
+62
-22
@@ -6,12 +6,13 @@ import {
|
|||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
QueryList,
|
QueryList,
|
||||||
|
signal,
|
||||||
ViewChildren,
|
ViewChildren,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { Router, RouterModule } from '@angular/router'
|
import { Router, RouterModule } from '@angular/router'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { delay, Subject, takeUntil, tap } from 'rxjs'
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
import { CustomFieldDisplayComponent } from 'src/app/components/common/custom-field-display/custom-field-display.component'
|
import { CustomFieldDisplayComponent } from 'src/app/components/common/custom-field-display/custom-field-display.component'
|
||||||
import { PreviewPopupComponent } from 'src/app/components/common/preview-popup/preview-popup.component'
|
import { PreviewPopupComponent } from 'src/app/components/common/preview-popup/preview-popup.component'
|
||||||
import { TagComponent } from 'src/app/components/common/tag/tag.component'
|
import { TagComponent } from 'src/app/components/common/tag/tag.component'
|
||||||
@@ -106,7 +107,7 @@ export class SavedViewWidgetComponent
|
|||||||
@Input()
|
@Input()
|
||||||
savedView: SavedView
|
savedView: SavedView
|
||||||
|
|
||||||
documents: Document[] = []
|
private documentsSignal = signal<Document[]>([])
|
||||||
|
|
||||||
unsubscribeNotifier: Subject<any> = new Subject()
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
@@ -116,21 +117,56 @@ export class SavedViewWidgetComponent
|
|||||||
mouseOnPreview = false
|
mouseOnPreview = false
|
||||||
popoverHidden = true
|
popoverHidden = true
|
||||||
|
|
||||||
displayMode: DisplayMode
|
private displayModeSignal = signal<DisplayMode>(null)
|
||||||
|
|
||||||
displayFields: DisplayField[] = DEFAULT_DASHBOARD_DISPLAY_FIELDS
|
private displayFieldsSignal = signal<DisplayField[]>(
|
||||||
|
DEFAULT_DASHBOARD_DISPLAY_FIELDS
|
||||||
|
)
|
||||||
|
|
||||||
count: number
|
private countSignal = signal<number>(null)
|
||||||
|
|
||||||
|
placeholderRows: number[] = []
|
||||||
|
|
||||||
|
get documents(): Document[] {
|
||||||
|
return this.documentsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documents(value: Document[]) {
|
||||||
|
this.documentsSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayMode(): DisplayMode {
|
||||||
|
return this.displayModeSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set displayMode(value: DisplayMode) {
|
||||||
|
this.displayModeSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayFields(): DisplayField[] {
|
||||||
|
return this.displayFieldsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set displayFields(value: DisplayField[]) {
|
||||||
|
this.displayFieldsSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get count(): number {
|
||||||
|
return this.countSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set count(value: number) {
|
||||||
|
this.countSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.reload()
|
this.placeholderRows = Array.from(
|
||||||
|
{
|
||||||
|
length: this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
|
||||||
|
},
|
||||||
|
(_, index) => index
|
||||||
|
)
|
||||||
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
|
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
|
||||||
this.websocketStatusService
|
|
||||||
.onDocumentConsumptionFinished()
|
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.reload()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.permissionsService.currentUserCan(
|
this.permissionsService.currentUserCan(
|
||||||
@@ -159,6 +195,14 @@ export class SavedViewWidgetComponent
|
|||||||
this.settingsService.allDisplayFields.find((f) => f.id === field) !==
|
this.settingsService.allDisplayFields.find((f) => f.id === field) !==
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.reload()
|
||||||
|
this.websocketStatusService
|
||||||
|
.onDocumentConsumptionFinished()
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.reload()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@@ -168,6 +212,7 @@ export class SavedViewWidgetComponent
|
|||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
this.loading = this.documents.length == 0
|
this.loading = this.documents.length == 0
|
||||||
|
this.show = true
|
||||||
this.documentService
|
this.documentService
|
||||||
.listFiltered(
|
.listFiltered(
|
||||||
1,
|
1,
|
||||||
@@ -177,18 +222,13 @@ export class SavedViewWidgetComponent
|
|||||||
this.savedView.filter_rules,
|
this.savedView.filter_rules,
|
||||||
{ truncate_content: true }
|
{ truncate_content: true }
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
takeUntil(this.unsubscribeNotifier),
|
|
||||||
tap((result) => {
|
|
||||||
this.show = true
|
|
||||||
this.documents = result.results
|
|
||||||
this.count = result.count
|
|
||||||
this.savedViewService.setDocumentCount(this.savedView, result.count)
|
|
||||||
}),
|
|
||||||
delay(500)
|
|
||||||
)
|
|
||||||
.subscribe((result) => {
|
.subscribe((result) => {
|
||||||
|
this.documents = result.results
|
||||||
|
this.count = result.count
|
||||||
|
this.savedViewService.setDocumentCount(this.savedView, result.count)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
this.show = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-23
@@ -1,7 +1,7 @@
|
|||||||
<pngx-widget-frame title="Statistics" [loading]="loading" i18n-title>
|
<pngx-widget-frame title="Statistics" [loading]="loading()" i18n-title>
|
||||||
<ng-container content>
|
<ng-container content>
|
||||||
<div class="list-group border-light placeholder-glow">
|
<div class="list-group border-light placeholder-glow">
|
||||||
@if (loading) {
|
@if (loading()) {
|
||||||
<div class="list-group-item d-flex">
|
<div class="list-group-item d-flex">
|
||||||
<div class="placeholder w-50"></div>
|
<div class="placeholder w-50"></div>
|
||||||
<span class="placeholder badge rounded-pill ms-auto" style="width: 25px;"> </span>
|
<span class="placeholder badge rounded-pill ms-auto" style="width: 25px;"> </span>
|
||||||
@@ -24,32 +24,32 @@
|
|||||||
<div class="placeholder w-100 d-block"></div>
|
<div class="placeholder w-100 d-block"></div>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
@if (statistics?.documents_inbox !== null) {
|
@if (statistics()?.documents_inbox !== null) {
|
||||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to inbox" i18n-title href="javascript:void(0)" (click)="goToInbox()">
|
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to inbox" i18n-title href="javascript:void(0)" (click)="goToInbox()">
|
||||||
<ng-container i18n>Documents in inbox</ng-container>:
|
<ng-container i18n>Documents in inbox</ng-container>:
|
||||||
<span class="badge rounded-pill" [class.bg-primary]="statistics?.documents_inbox > 0" [class.bg-muted]="statistics?.documents_inbox === 0">{{statistics?.documents_inbox}}</span>
|
<span class="badge rounded-pill" [class.bg-primary]="statistics()?.documents_inbox > 0" [class.bg-muted]="statistics()?.documents_inbox === 0">{{statistics()?.documents_inbox}}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to documents" i18n-title routerLink="/documents/">
|
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to documents" i18n-title routerLink="/documents/">
|
||||||
<ng-container i18n>Total documents</ng-container>:
|
<ng-container i18n>Total documents</ng-container>:
|
||||||
<span class="badge bg-primary rounded-pill">{{statistics?.documents_total}}</span>
|
<span class="badge bg-primary rounded-pill">{{statistics()?.documents_total}}</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documents/">
|
<div class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documents/">
|
||||||
<ng-container i18n>Total characters</ng-container>:
|
<ng-container i18n>Total characters</ng-container>:
|
||||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.character_count | number}}</span>
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.character_count | number}}</span>
|
||||||
</div>
|
</div>
|
||||||
@if (statistics?.current_asn) {
|
@if (statistics()?.current_asn) {
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documents/">
|
<div class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documents/">
|
||||||
<ng-container i18n>Current ASN</ng-container>:
|
<ng-container i18n>Current ASN</ng-container>:
|
||||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.current_asn}}</span>
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.current_asn}}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if (statistics?.document_file_type_counts?.length > 1) {
|
@if (statistics()?.document_file_type_counts?.length > 1) {
|
||||||
<div class="list-group-item filetypes">
|
<div class="list-group-item filetypes">
|
||||||
<div class="d-flex justify-content-between align-items-center my-2">
|
<div class="d-flex justify-content-between align-items-center my-2">
|
||||||
<div class="progress flex-grow-1">
|
<div class="progress flex-grow-1">
|
||||||
@for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index; let last = $last) {
|
@for (filetype of statistics()?.document_file_type_counts; track filetype; let i = $index; let last = $last) {
|
||||||
<div
|
<div
|
||||||
class="progress-bar bg-primary"
|
class="progress-bar bg-primary"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap align-items-start">
|
<div class="d-flex flex-wrap align-items-start">
|
||||||
@for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index) {
|
@for (filetype of statistics()?.document_file_type_counts; track filetype; let i = $index) {
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="text-nowrap me-2" [class.cursor-pointer]="!filetype.is_other" (click)="filterByFileType(filetype)">
|
<div class="text-nowrap me-2" [class.cursor-pointer]="!filetype.is_other" (click)="filterByFileType(filetype)">
|
||||||
<span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span>
|
<span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span>
|
||||||
@@ -84,54 +84,54 @@
|
|||||||
|
|
||||||
<div class="list-group border-light mt-3">
|
<div class="list-group border-light mt-3">
|
||||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
|
||||||
@if (loading) {
|
@if (loading()) {
|
||||||
<div class="placeholder-glow list-group-item">
|
<div class="placeholder-glow list-group-item">
|
||||||
<span class="placeholder w-100"></span>
|
<span class="placeholder w-100"></span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (statistics?.tag_count > 0) {
|
@if (statistics()?.tag_count > 0) {
|
||||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/tags/">
|
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/tags/">
|
||||||
<ng-container i18n>Tags</ng-container>:
|
<ng-container i18n>Tags</ng-container>:
|
||||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.tag_count | number}}</span>
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.tag_count | number}}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||||
@if (loading) {
|
@if (loading()) {
|
||||||
<div class="placeholder-glow list-group-item">
|
<div class="placeholder-glow list-group-item">
|
||||||
<span class="placeholder w-100"></span>
|
<span class="placeholder w-100"></span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (statistics?.correspondent_count > 0) {
|
@if (statistics()?.correspondent_count > 0) {
|
||||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/correspondents/">
|
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/correspondents/">
|
||||||
<ng-container i18n>Correspondents</ng-container>:
|
<ng-container i18n>Correspondents</ng-container>:
|
||||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.correspondent_count | number}}</span>
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.correspondent_count | number}}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||||
@if (loading) {
|
@if (loading()) {
|
||||||
<div class="placeholder-glow list-group-item">
|
<div class="placeholder-glow list-group-item">
|
||||||
<span class="placeholder w-100"></span>
|
<span class="placeholder w-100"></span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (statistics?.document_type_count > 0) {
|
@if (statistics()?.document_type_count > 0) {
|
||||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
|
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
|
||||||
<ng-container i18n>Document Types</ng-container>:
|
<ng-container i18n>Document Types</ng-container>:
|
||||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.document_type_count | number}}</span>
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.document_type_count | number}}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||||
@if (loading) {
|
@if (loading()) {
|
||||||
<div class="placeholder-glow list-group-item">
|
<div class="placeholder-glow list-group-item">
|
||||||
<span class="placeholder w-100"></span>
|
<span class="placeholder w-100"></span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (statistics?.storage_path_count > 0) {
|
@if (statistics()?.storage_path_count > 0) {
|
||||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/storagepaths/">
|
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/storagepaths/">
|
||||||
<ng-container i18n>Storage Paths</ng-container>:
|
<ng-container i18n>Storage Paths</ng-container>:
|
||||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.storage_path_count | number}}</span>
|
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.storage_path_count | number}}</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
+1
-1
@@ -74,7 +74,7 @@ describe('StatisticsWidgetComponent', () => {
|
|||||||
|
|
||||||
it('should not call statistics endpoint on reload if already loading', () => {
|
it('should not call statistics endpoint on reload if already loading', () => {
|
||||||
httpTestingController.expectOne(`${environment.apiBaseUrl}statistics/`)
|
httpTestingController.expectOne(`${environment.apiBaseUrl}statistics/`)
|
||||||
component.loading = true
|
component.loading.set(true)
|
||||||
component.reload()
|
component.reload()
|
||||||
httpTestingController.expectNone(`${environment.apiBaseUrl}statistics/`)
|
httpTestingController.expectNone(`${environment.apiBaseUrl}statistics/`)
|
||||||
})
|
})
|
||||||
|
|||||||
+11
-12
@@ -1,6 +1,6 @@
|
|||||||
import { DecimalPipe } from '@angular/common'
|
import { DecimalPipe } from '@angular/common'
|
||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core'
|
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core'
|
||||||
import { RouterModule } from '@angular/router'
|
import { RouterModule } from '@angular/router'
|
||||||
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import * as mimeTypeNames from 'mime-names'
|
import * as mimeTypeNames from 'mime-names'
|
||||||
@@ -55,21 +55,20 @@ export class StatisticsWidgetComponent
|
|||||||
private websocketConnectionService = inject(WebsocketStatusService)
|
private websocketConnectionService = inject(WebsocketStatusService)
|
||||||
private documentListViewService = inject(DocumentListViewService)
|
private documentListViewService = inject(DocumentListViewService)
|
||||||
|
|
||||||
loading: boolean = false
|
readonly loading = signal(false)
|
||||||
|
readonly statistics = signal<Statistics>({})
|
||||||
statistics: Statistics = {}
|
|
||||||
|
|
||||||
subscription: Subscription
|
subscription: Subscription
|
||||||
private unsubscribeNotifer: Subject<any> = new Subject()
|
private unsubscribeNotifer: Subject<any> = new Subject()
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
if (this.loading) return
|
if (this.loading()) return
|
||||||
this.loading = true
|
this.loading.set(true)
|
||||||
this.http
|
this.http
|
||||||
.get<Statistics>(`${environment.apiBaseUrl}statistics/`)
|
.get<Statistics>(`${environment.apiBaseUrl}statistics/`)
|
||||||
.pipe(takeUntil(this.unsubscribeNotifer), first())
|
.pipe(takeUntil(this.unsubscribeNotifer), first())
|
||||||
.subscribe((statistics) => {
|
.subscribe((statistics) => {
|
||||||
this.loading = false
|
this.loading.set(false)
|
||||||
const fileTypeMax = 5
|
const fileTypeMax = 5
|
||||||
if (statistics.document_file_type_counts?.length > fileTypeMax) {
|
if (statistics.document_file_type_counts?.length > fileTypeMax) {
|
||||||
const others = statistics.document_file_type_counts.slice(fileTypeMax)
|
const others = statistics.document_file_type_counts.slice(fileTypeMax)
|
||||||
@@ -85,7 +84,7 @@ export class StatisticsWidgetComponent
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.statistics = statistics
|
this.statistics.set(statistics)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,11 +100,11 @@ export class StatisticsWidgetComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFileTypePercent(filetype: DocumentFileType): number {
|
getFileTypePercent(filetype: DocumentFileType): number {
|
||||||
return (filetype.mime_type_count / this.statistics?.documents_total) * 100
|
return (filetype.mime_type_count / this.statistics()?.documents_total) * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemOpacity(i: number): number {
|
getItemOpacity(i: number): number {
|
||||||
return 1 - i / this.statistics?.document_file_type_counts.length
|
return 1 - i / this.statistics()?.document_file_type_counts.length
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -127,8 +126,8 @@ export class StatisticsWidgetComponent
|
|||||||
this.documentListViewService.quickFilter([
|
this.documentListViewService.quickFilter([
|
||||||
{
|
{
|
||||||
rule_type: FILTER_HAS_TAGS_ANY,
|
rule_type: FILTER_HAS_TAGS_ANY,
|
||||||
value: this.statistics.inbox_tags
|
value: this.statistics()
|
||||||
.map((tagID) => tagID.toString())
|
.inbox_tags.map((tagID) => tagID.toString())
|
||||||
.join(','),
|
.join(','),
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|||||||
+9
-8
@@ -1,19 +1,20 @@
|
|||||||
@if (!cardless) {
|
@if (!cardless()) {
|
||||||
<div class="card shadow-sm bg-light fade" [class.show]="show" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
|
<div class="card shadow-sm bg-light fade" [class.show]="show()" cdkDrag [cdkDragDisabled]="!draggable()" cdkDragPreviewContainer="parent">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
@if (draggable) {
|
@if (draggable()) {
|
||||||
<div class="ms-n2 me-1" cdkDragHandle>
|
<div class="ms-n2 me-1" cdkDragHandle>
|
||||||
<i-bs name="grip-vertical"></i-bs>
|
<i-bs name="grip-vertical"></i-bs>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<h6 class="card-title mb-0">{{title}}</h6>
|
<h6 class="card-title mb-0">{{title()}}</h6>
|
||||||
@if (badge) {
|
<ng-content select="[title-badge]"></ng-content>
|
||||||
<span class="badge bg-info text-dark ms-2">{{badge}}</span>
|
@if (badge() !== null && badge() !== undefined) {
|
||||||
|
<span class="badge bg-info text-dark ms-2">{{badge()}}</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (loading) {
|
@if (loading()) {
|
||||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||||
<div class="visually-hidden" i18n>Loading...</div>
|
<div class="visually-hidden" i18n>Loading...</div>
|
||||||
}
|
}
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="fade" [class.show]="show">
|
<div class="fade" [class.show]="show()">
|
||||||
<ng-container [ngTemplateOutlet]="content"></ng-container>
|
<ng-container [ngTemplateOutlet]="content"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,23 @@
|
|||||||
i-bs {
|
i-bs {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade.show {
|
||||||
|
animation: pngx-entry-fade 160ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pngx-entry-fade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.fade.show {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-5
@@ -45,21 +45,19 @@ describe('WidgetFrameComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should show title', () => {
|
it('should show title', () => {
|
||||||
component.title = 'Foo'
|
fixture.componentRef.setInput('title', 'Foo')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.nativeElement.textContent).toContain('Foo')
|
expect(fixture.debugElement.nativeElement.textContent).toContain('Foo')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show loading indicator', () => {
|
it('should show loading indicator', () => {
|
||||||
expect(fixture.debugElement.query(By.css('.spinner-border'))).toBeNull()
|
expect(fixture.debugElement.query(By.css('.spinner-border'))).toBeNull()
|
||||||
component.loading = true
|
fixture.componentRef.setInput('loading', true)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.query(By.css('.spinner-border'))).not.toBeNull()
|
expect(fixture.debugElement.query(By.css('.spinner-border'))).not.toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show', () => {
|
it('should show', () => {
|
||||||
expect(component.show).toBeFalsy()
|
expect(component.show()).toBeTruthy()
|
||||||
jest.advanceTimersByTime(100)
|
|
||||||
expect(component.show).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||||
import { NgTemplateOutlet } from '@angular/common'
|
import { NgTemplateOutlet } from '@angular/common'
|
||||||
import { AfterViewInit, Component, Input } from '@angular/core'
|
import { AfterViewInit, Component, input, signal } from '@angular/core'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { LoadingComponentWithPermissions } from 'src/app/components/loading-component/loading.component'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-widget-frame',
|
selector: 'pngx-widget-frame',
|
||||||
@@ -10,32 +9,20 @@ import { LoadingComponentWithPermissions } from 'src/app/components/loading-comp
|
|||||||
styleUrls: ['./widget-frame.component.scss'],
|
styleUrls: ['./widget-frame.component.scss'],
|
||||||
imports: [DragDropModule, NgxBootstrapIconsModule, NgTemplateOutlet],
|
imports: [DragDropModule, NgxBootstrapIconsModule, NgTemplateOutlet],
|
||||||
})
|
})
|
||||||
export class WidgetFrameComponent
|
export class WidgetFrameComponent implements AfterViewInit {
|
||||||
extends LoadingComponentWithPermissions
|
readonly show = signal(false)
|
||||||
implements AfterViewInit
|
|
||||||
{
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
loading = input(false)
|
||||||
title: string
|
|
||||||
|
|
||||||
@Input()
|
title = input<string>()
|
||||||
loading: boolean = false
|
|
||||||
|
|
||||||
@Input()
|
draggable = input<any>()
|
||||||
draggable: any
|
|
||||||
|
|
||||||
@Input()
|
cardless = input(false)
|
||||||
cardless: boolean = false
|
|
||||||
|
|
||||||
@Input()
|
badge = input<string | number>(null)
|
||||||
badge: string
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
setTimeout(() => {
|
this.show.set(true)
|
||||||
this.show = true
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -445,8 +445,7 @@
|
|||||||
<button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
<button type="submit" class="order-3 btn btn-sm btn-primary" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save</button>
|
||||||
@if (hasNext()) {
|
@if (hasNext()) {
|
||||||
<button type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
<button type="button" class="order-1 btn btn-sm btn-outline-primary" (click)="saveEditNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & next</button>
|
||||||
}
|
} @else {
|
||||||
@if (!hasNext()) {
|
|
||||||
<button type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & close</button>
|
<button type="button" class="order-2 btn btn-sm btn-outline-primary" (click)="save(true)" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Save & close</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
<button type="button" class="order-0 btn btn-sm btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||||
@@ -473,6 +472,7 @@
|
|||||||
<div class="preview-sticky pdf-viewer-container">
|
<div class="preview-sticky pdf-viewer-container">
|
||||||
<pngx-pdf-viewer
|
<pngx-pdf-viewer
|
||||||
[src]="pdfSource"
|
[src]="pdfSource"
|
||||||
|
[password]="pdfPassword"
|
||||||
[renderMode]="PdfRenderMode.All"
|
[renderMode]="PdfRenderMode.All"
|
||||||
[(page)]="previewCurrentPage"
|
[(page)]="previewCurrentPage"
|
||||||
[zoomScale]="previewZoomScale"
|
[zoomScale]="previewZoomScale"
|
||||||
|
|||||||
@@ -853,6 +853,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
.find((b) => b.nativeElement.textContent === 'Save & next')
|
.find((b) => b.nativeElement.textContent === 'Save & next')
|
||||||
).toBeUndefined()
|
).toBeUndefined()
|
||||||
nextSpy.mockReturnValue(true)
|
nextSpy.mockReturnValue(true)
|
||||||
|
component.networkActive = true
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement
|
fixture.debugElement
|
||||||
@@ -1530,11 +1531,13 @@ describe('DocumentDetailComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should clear pdf source if preview URL is empty', () => {
|
it('should clear pdf source if preview URL is empty', () => {
|
||||||
component.pdfSource = { url: '/preview', password: 'secret' } as any
|
component.pdfSource = '/preview'
|
||||||
|
component.pdfPassword = 'secret'
|
||||||
component.previewUrl = null
|
component.previewUrl = null
|
||||||
;(component as any).updatePdfSource()
|
;(component as any).updatePdfSource()
|
||||||
|
|
||||||
expect(component.pdfSource).toEqual({ url: null, password: undefined })
|
expect(component.pdfSource).toEqual(null)
|
||||||
|
expect(component.pdfPassword).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should close incoming update modal if one is open', () => {
|
it('should close incoming update modal if one is open', () => {
|
||||||
@@ -1871,9 +1874,8 @@ describe('DocumentDetailComponent', () => {
|
|||||||
expect(component.thumbUrl).toBe('thumb-version')
|
expect(component.thumbUrl).toBe('thumb-version')
|
||||||
expect(component.previewText).toBe('version text')
|
expect(component.previewText).toBe('version text')
|
||||||
expect(component.documentForm.get('content').value).toBe('version-content')
|
expect(component.documentForm.get('content').value).toBe('version-content')
|
||||||
const pdfSource = component.pdfSource as { url: string; password?: string }
|
expect(component.pdfSource).toBe('preview-version')
|
||||||
expect(pdfSource.url).toBe('preview-version')
|
expect(component.pdfPassword).toBeUndefined()
|
||||||
expect(pdfSource.password).toBeUndefined()
|
|
||||||
|
|
||||||
previewSpy.mockReturnValueOnce('preview-error')
|
previewSpy.mockReturnValueOnce('preview-error')
|
||||||
component.selectVersion(11)
|
component.selectVersion(11)
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { AsyncPipe, NgTemplateOutlet } from '@angular/common'
|
import { AsyncPipe, NgTemplateOutlet } from '@angular/common'
|
||||||
import { HttpClient, HttpResponse } from '@angular/common/http'
|
import { HttpClient, HttpResponse } from '@angular/common/http'
|
||||||
import { Component, inject, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
inject,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
signal,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormArray,
|
FormArray,
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -115,7 +122,6 @@ import { PDFEditorComponent } from '../common/pdf-editor/pdf-editor.component'
|
|||||||
import { PngxPdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component'
|
import { PngxPdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component'
|
||||||
import {
|
import {
|
||||||
PdfRenderMode,
|
PdfRenderMode,
|
||||||
PdfSource,
|
|
||||||
PdfZoomLevel,
|
PdfZoomLevel,
|
||||||
PdfZoomScale,
|
PdfZoomScale,
|
||||||
PngxPdfDocumentProxy,
|
PngxPdfDocumentProxy,
|
||||||
@@ -228,33 +234,34 @@ export class DocumentDetailComponent
|
|||||||
expandOriginalMetadata = false
|
expandOriginalMetadata = false
|
||||||
expandArchivedMetadata = false
|
expandArchivedMetadata = false
|
||||||
|
|
||||||
error: any
|
private errorSignal = signal<any>(undefined)
|
||||||
|
|
||||||
networkActive = false
|
private networkActiveSignal = signal(false)
|
||||||
|
|
||||||
documentId: number
|
private documentIdSignal = signal<number>(undefined)
|
||||||
document: Document
|
private documentSignal = signal<Document>(undefined)
|
||||||
metadata: DocumentMetadata
|
private metadataSignal = signal<DocumentMetadata>(undefined)
|
||||||
suggestions: DocumentSuggestions
|
private suggestionsSignal = signal<DocumentSuggestions>(undefined)
|
||||||
suggestionsLoading: boolean = false
|
private suggestionsLoadingSignal = signal(false)
|
||||||
users: User[]
|
private usersSignal = signal<User[]>(undefined)
|
||||||
|
|
||||||
title: string
|
private titleSignal = signal<string>(undefined)
|
||||||
titleSubject: Subject<string> = new Subject()
|
titleSubject: Subject<string> = new Subject()
|
||||||
previewUrl: string
|
private previewUrlSignal = signal<string>(undefined)
|
||||||
pdfSource?: PdfSource
|
private pdfSourceSignal = signal<string>(undefined)
|
||||||
thumbUrl: string
|
private pdfPasswordSignal = signal<string>(undefined)
|
||||||
previewText: string
|
private thumbUrlSignal = signal<string>(undefined)
|
||||||
previewLoaded: boolean = false
|
private previewTextSignal = signal<string>(undefined)
|
||||||
tiffURL: string
|
private previewLoadedSignal = signal(false)
|
||||||
tiffError: string
|
private tiffURLSignal = signal<string>(undefined)
|
||||||
|
private tiffErrorSignal = signal<string>(undefined)
|
||||||
|
|
||||||
// Versioning
|
// Versioning
|
||||||
selectedVersionId: number
|
private selectedVersionIdSignal = signal<number>(undefined)
|
||||||
|
|
||||||
correspondents: Correspondent[]
|
private correspondentsSignal = signal<Correspondent[]>(undefined)
|
||||||
documentTypes: DocumentType[]
|
private documentTypesSignal = signal<DocumentType[]>(undefined)
|
||||||
storagePaths: StoragePath[]
|
private storagePathsSignal = signal<StoragePath[]>(undefined)
|
||||||
|
|
||||||
documentForm: FormGroup = new FormGroup({
|
documentForm: FormGroup = new FormGroup({
|
||||||
title: new FormControl(''),
|
title: new FormControl(''),
|
||||||
@@ -269,10 +276,10 @@ export class DocumentDetailComponent
|
|||||||
custom_fields: new FormArray([]),
|
custom_fields: new FormArray([]),
|
||||||
})
|
})
|
||||||
|
|
||||||
previewCurrentPage: number = 1
|
private previewCurrentPageSignal = signal(1)
|
||||||
previewNumPages: number
|
private previewNumPagesSignal = signal<number>(undefined)
|
||||||
previewZoomSetting: PdfZoomLevel = PdfZoomLevel.One
|
private previewZoomSettingSignal = signal<PdfZoomLevel>(PdfZoomLevel.One)
|
||||||
previewZoomScale: PdfZoomScale = PdfZoomScale.PageWidth
|
private previewZoomScaleSignal = signal<PdfZoomScale>(PdfZoomScale.PageWidth)
|
||||||
|
|
||||||
store: BehaviorSubject<any>
|
store: BehaviorSubject<any>
|
||||||
isDirty$: Observable<boolean>
|
isDirty$: Observable<boolean>
|
||||||
@@ -287,9 +294,9 @@ export class DocumentDetailComponent
|
|||||||
|
|
||||||
ogDate: Date
|
ogDate: Date
|
||||||
|
|
||||||
customFields: CustomField[]
|
private customFieldsSignal = signal<CustomField[]>(undefined)
|
||||||
|
|
||||||
public downloading: boolean = false
|
private downloadingSignal = signal(false)
|
||||||
public useFormattedFilename: boolean = false
|
public useFormattedFilename: boolean = false
|
||||||
|
|
||||||
public readonly CustomFieldDataType = CustomFieldDataType
|
public readonly CustomFieldDataType = CustomFieldDataType
|
||||||
@@ -314,13 +321,238 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
DocumentDetailNavIDs = DocumentDetailNavIDs
|
DocumentDetailNavIDs = DocumentDetailNavIDs
|
||||||
activeNavID: number
|
private activeNavIDSignal = signal<number>(undefined)
|
||||||
|
|
||||||
|
get error(): any {
|
||||||
|
return this.errorSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set error(value: any) {
|
||||||
|
this.errorSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get networkActive(): boolean {
|
||||||
|
return this.networkActiveSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set networkActive(value: boolean) {
|
||||||
|
this.networkActiveSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get documentId(): number {
|
||||||
|
return this.documentIdSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentId(value: number) {
|
||||||
|
this.documentIdSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get document(): Document {
|
||||||
|
return this.documentSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set document(value: Document) {
|
||||||
|
this.documentSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get metadata(): DocumentMetadata {
|
||||||
|
return this.metadataSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set metadata(value: DocumentMetadata) {
|
||||||
|
this.metadataSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get suggestions(): DocumentSuggestions {
|
||||||
|
return this.suggestionsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set suggestions(value: DocumentSuggestions) {
|
||||||
|
this.suggestionsSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get suggestionsLoading(): boolean {
|
||||||
|
return this.suggestionsLoadingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set suggestionsLoading(value: boolean) {
|
||||||
|
this.suggestionsLoadingSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get users(): User[] {
|
||||||
|
return this.usersSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set users(value: User[]) {
|
||||||
|
this.usersSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get title(): string {
|
||||||
|
return this.titleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set title(value: string) {
|
||||||
|
this.titleSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get previewUrl(): string {
|
||||||
|
return this.previewUrlSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set previewUrl(value: string) {
|
||||||
|
this.previewUrlSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get pdfSource(): string {
|
||||||
|
return this.pdfSourceSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set pdfSource(value: string) {
|
||||||
|
this.pdfSourceSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get pdfPassword(): string {
|
||||||
|
return this.pdfPasswordSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set pdfPassword(value: string) {
|
||||||
|
this.pdfPasswordSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get thumbUrl(): string {
|
||||||
|
return this.thumbUrlSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set thumbUrl(value: string) {
|
||||||
|
this.thumbUrlSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get previewText(): string {
|
||||||
|
return this.previewTextSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set previewText(value: string) {
|
||||||
|
this.previewTextSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get previewLoaded(): boolean {
|
||||||
|
return this.previewLoadedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set previewLoaded(value: boolean) {
|
||||||
|
this.previewLoadedSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get tiffURL(): string {
|
||||||
|
return this.tiffURLSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set tiffURL(value: string) {
|
||||||
|
this.tiffURLSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get tiffError(): string {
|
||||||
|
return this.tiffErrorSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set tiffError(value: string) {
|
||||||
|
this.tiffErrorSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedVersionId(): number {
|
||||||
|
return this.selectedVersionIdSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set selectedVersionId(value: number) {
|
||||||
|
this.selectedVersionIdSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get correspondents(): Correspondent[] {
|
||||||
|
return this.correspondentsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set correspondents(value: Correspondent[]) {
|
||||||
|
this.correspondentsSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get documentTypes(): DocumentType[] {
|
||||||
|
return this.documentTypesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentTypes(value: DocumentType[]) {
|
||||||
|
this.documentTypesSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get storagePaths(): StoragePath[] {
|
||||||
|
return this.storagePathsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set storagePaths(value: StoragePath[]) {
|
||||||
|
this.storagePathsSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get previewCurrentPage(): number {
|
||||||
|
return this.previewCurrentPageSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set previewCurrentPage(value: number) {
|
||||||
|
this.previewCurrentPageSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get previewNumPages(): number {
|
||||||
|
return this.previewNumPagesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set previewNumPages(value: number) {
|
||||||
|
this.previewNumPagesSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get previewZoomSetting(): PdfZoomLevel {
|
||||||
|
return this.previewZoomSettingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set previewZoomSetting(value: PdfZoomLevel) {
|
||||||
|
this.previewZoomSettingSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get previewZoomScale(): PdfZoomScale {
|
||||||
|
return this.previewZoomScaleSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set previewZoomScale(value: PdfZoomScale) {
|
||||||
|
this.previewZoomScaleSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get downloading(): boolean {
|
||||||
|
return this.downloadingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set downloading(value: boolean) {
|
||||||
|
this.downloadingSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeNavID(): number {
|
||||||
|
return this.activeNavIDSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set activeNavID(value: number) {
|
||||||
|
this.activeNavIDSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get customFields(): CustomField[] {
|
||||||
|
return this.customFieldsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set customFields(value: CustomField[]) {
|
||||||
|
this.customFieldsSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
titleKeyUp(event) {
|
titleKeyUp(event) {
|
||||||
this.titleSubject.next(event.target?.value)
|
this.titleSubject.next(event.target?.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
get useNativePdfViewer(): boolean {
|
get useNativePdfViewer(): boolean {
|
||||||
|
this.settings.trackChanges()
|
||||||
return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)
|
return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,10 +561,12 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get aiEnabled(): boolean {
|
get aiEnabled(): boolean {
|
||||||
|
this.settings.trackChanges()
|
||||||
return this.settings.get(SETTINGS_KEYS.AI_ENABLED)
|
return this.settings.get(SETTINGS_KEYS.AI_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
get archiveContentRenderType(): ContentRenderType {
|
get archiveContentRenderType(): ContentRenderType {
|
||||||
|
this.settings.trackChanges()
|
||||||
const hasArchiveVersion =
|
const hasArchiveVersion =
|
||||||
this.metadata?.has_archive_version ?? !!this.document?.archived_file_name
|
this.metadata?.has_archive_version ?? !!this.document?.archived_file_name
|
||||||
return hasArchiveVersion
|
return hasArchiveVersion
|
||||||
@@ -343,16 +577,19 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get originalContentRenderType(): ContentRenderType {
|
get originalContentRenderType(): ContentRenderType {
|
||||||
|
this.settings.trackChanges()
|
||||||
return this.getRenderType(
|
return this.getRenderType(
|
||||||
this.metadata?.original_mime_type || this.document?.mime_type
|
this.metadata?.original_mime_type || this.document?.mime_type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get showThumbnailOverlay(): boolean {
|
get showThumbnailOverlay(): boolean {
|
||||||
|
this.settings.trackChanges()
|
||||||
return this.settings.get(SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL)
|
return this.settings.get(SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL)
|
||||||
}
|
}
|
||||||
|
|
||||||
isFieldHidden(fieldId: DocumentDetailFieldID): boolean {
|
isFieldHidden(fieldId: DocumentDetailFieldID): boolean {
|
||||||
|
this.settings.trackChanges()
|
||||||
return this.settings
|
return this.settings
|
||||||
.get(SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS)
|
.get(SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS)
|
||||||
.includes(fieldId)
|
.includes(fieldId)
|
||||||
@@ -375,10 +612,8 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updatePdfSource() {
|
private updatePdfSource() {
|
||||||
this.pdfSource = {
|
this.pdfSource = this.previewUrl
|
||||||
url: this.previewUrl,
|
this.pdfPassword = this.password
|
||||||
password: this.password,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadMetadataForSelectedVersion() {
|
private loadMetadataForSelectedVersion() {
|
||||||
@@ -1494,9 +1729,7 @@ export class DocumentDetailComponent
|
|||||||
pdfPreviewLoaded(pdf: PngxPdfDocumentProxy) {
|
pdfPreviewLoaded(pdf: PngxPdfDocumentProxy) {
|
||||||
this.previewNumPages = pdf.numPages
|
this.previewNumPages = pdf.numPages
|
||||||
if (this.password) this.requiresPassword = false
|
if (this.password) this.requiresPassword = false
|
||||||
setTimeout(() => {
|
this.previewLoaded = true
|
||||||
this.previewLoaded = true
|
|
||||||
}, 300)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(event) {
|
onError(event) {
|
||||||
|
|||||||
+20
@@ -2,6 +2,26 @@
|
|||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade.show {
|
||||||
|
animation: pngx-entry-fade 160ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pngx-entry-fade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.fade.show {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.doc-img-container {
|
.doc-img-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-18
@@ -67,15 +67,14 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
|
|
||||||
fixture = TestBed.createComponent(DocumentCardLargeComponent)
|
fixture = TestBed.createComponent(DocumentCardLargeComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
component.document = doc
|
component.document = { ...doc, tags: [...doc.tags], notes: [...doc.notes] }
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
jest.useFakeTimers()
|
jest.useFakeTimers()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show the card', () => {
|
it('should show the card', () => {
|
||||||
expect(component.show).toBeFalsy()
|
expect(component.show).toBeTruthy()
|
||||||
component.ngAfterViewInit()
|
component.ngAfterViewInit()
|
||||||
jest.advanceTimersByTime(100)
|
|
||||||
expect(component.show).toBeTruthy()
|
expect(component.show).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,10 +90,13 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
|
|
||||||
it('should display search hits with colored score', () => {
|
it('should display search hits with colored score', () => {
|
||||||
// high
|
// high
|
||||||
component.document.__search_hit__ = {
|
component.document = {
|
||||||
score: 0.9,
|
...component.document,
|
||||||
rank: 1,
|
__search_hit__: {
|
||||||
highlights: 'cheesecake',
|
score: 0.9,
|
||||||
|
rank: 1,
|
||||||
|
highlights: 'cheesecake',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
let search_hit = fixture.debugElement.query(By.css('.search-score'))
|
let search_hit = fixture.debugElement.query(By.css('.search-score'))
|
||||||
@@ -102,14 +104,20 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
expect(component.searchScoreClass).toEqual('success')
|
expect(component.searchScoreClass).toEqual('success')
|
||||||
|
|
||||||
// medium
|
// medium
|
||||||
component.document.__search_hit__.score = 0.6
|
component.document = {
|
||||||
|
...component.document,
|
||||||
|
__search_hit__: { ...component.document.__search_hit__, score: 0.6 },
|
||||||
|
}
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
search_hit = fixture.debugElement.query(By.css('.search-score'))
|
search_hit = fixture.debugElement.query(By.css('.search-score'))
|
||||||
expect(search_hit).not.toBeUndefined()
|
expect(search_hit).not.toBeUndefined()
|
||||||
expect(component.searchScoreClass).toEqual('warning')
|
expect(component.searchScoreClass).toEqual('warning')
|
||||||
|
|
||||||
// low
|
// low
|
||||||
component.document.__search_hit__.score = 0.1
|
component.document = {
|
||||||
|
...component.document,
|
||||||
|
__search_hit__: { ...component.document.__search_hit__, score: 0.1 },
|
||||||
|
}
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
search_hit = fixture.debugElement.query(By.css('.search-score'))
|
search_hit = fixture.debugElement.query(By.css('.search-score'))
|
||||||
expect(search_hit).not.toBeUndefined()
|
expect(search_hit).not.toBeUndefined()
|
||||||
@@ -117,10 +125,13 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should display note highlights', () => {
|
it('should display note highlights', () => {
|
||||||
component.document.__search_hit__ = {
|
component.document = {
|
||||||
score: 0.9,
|
...component.document,
|
||||||
rank: 1,
|
__search_hit__: {
|
||||||
note_highlights: '<span>bananas</span>',
|
score: 0.9,
|
||||||
|
rank: 1,
|
||||||
|
note_highlights: '<span>bananas</span>',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.nativeElement.textContent).toContain('bananas')
|
expect(fixture.nativeElement.textContent).toContain('bananas')
|
||||||
@@ -128,11 +139,14 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should fall back to document content when a search hit has no highlights', () => {
|
it('should fall back to document content when a search hit has no highlights', () => {
|
||||||
component.document.__search_hit__ = {
|
component.document = {
|
||||||
score: 0.9,
|
...component.document,
|
||||||
rank: 1,
|
__search_hit__: {
|
||||||
highlights: '',
|
score: 0.9,
|
||||||
note_highlights: null,
|
rank: 1,
|
||||||
|
highlights: '',
|
||||||
|
note_highlights: null,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
|
|
||||||
|
|||||||
+28
-9
@@ -7,6 +7,7 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { RouterModule } from '@angular/router'
|
import { RouterModule } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { delay, of } from 'rxjs'
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_DISPLAY_FIELDS,
|
DEFAULT_DISPLAY_FIELDS,
|
||||||
DisplayField,
|
DisplayField,
|
||||||
@@ -65,14 +65,31 @@ export class DocumentCardLargeComponent
|
|||||||
{
|
{
|
||||||
private documentService = inject(DocumentService)
|
private documentService = inject(DocumentService)
|
||||||
settingsService = inject(SettingsService)
|
settingsService = inject(SettingsService)
|
||||||
|
private selectedSignal = signal(false)
|
||||||
|
private displayFieldsSignal = signal<string[]>(
|
||||||
|
DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
||||||
|
)
|
||||||
|
private documentSignal = signal<Document>(undefined)
|
||||||
|
|
||||||
DisplayField = DisplayField
|
DisplayField = DisplayField
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
selected = false
|
get selected(): boolean {
|
||||||
|
return this.selectedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set selected(selected: boolean) {
|
||||||
|
this.selectedSignal.set(selected)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
displayFields: string[] = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
get displayFields(): string[] {
|
||||||
|
return this.displayFieldsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set displayFields(displayFields: string[]) {
|
||||||
|
this.displayFieldsSignal.set(displayFields)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
toggleSelected = new EventEmitter()
|
toggleSelected = new EventEmitter()
|
||||||
@@ -82,7 +99,13 @@ export class DocumentCardLargeComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
document: Document
|
get document(): Document {
|
||||||
|
return this.documentSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set document(document: Document) {
|
||||||
|
this.documentSignal.set(document)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
dblClickDocument = new EventEmitter()
|
dblClickDocument = new EventEmitter()
|
||||||
@@ -108,11 +131,7 @@ export class DocumentCardLargeComponent
|
|||||||
popoverHidden = true
|
popoverHidden = true
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
of(true)
|
this.show = true
|
||||||
.pipe(delay(50))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.show = true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get searchScoreClass() {
|
get searchScoreClass() {
|
||||||
|
|||||||
+20
@@ -2,6 +2,26 @@
|
|||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade.show {
|
||||||
|
animation: pngx-entry-fade 160ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pngx-entry-fade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.fade.show {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.doc-img {
|
.doc-img {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: top left;
|
object-position: top left;
|
||||||
|
|||||||
+4
-5
@@ -52,9 +52,8 @@ describe('DocumentCardSmallComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should show the card', () => {
|
it('should show the card', () => {
|
||||||
expect(component.show).toBeFalsy()
|
expect(component.show).toBeTruthy()
|
||||||
component.ngAfterViewInit()
|
component.ngAfterViewInit()
|
||||||
jest.advanceTimersByTime(100)
|
|
||||||
expect(component.show).toBeTruthy()
|
expect(component.show).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ describe('DocumentCardSmallComponent', () => {
|
|||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||||
).toHaveLength(5)
|
).toHaveLength(5)
|
||||||
component.document.tags = [1, 2]
|
component.document = { ...component.document, tags: [1, 2] }
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||||
@@ -75,7 +74,7 @@ describe('DocumentCardSmallComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should increase limit tags to 6 if no notes', () => {
|
it('should increase limit tags to 6 if no notes', () => {
|
||||||
component.document.notes = []
|
component.document = { ...component.document, notes: [] }
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||||
@@ -85,7 +84,7 @@ describe('DocumentCardSmallComponent', () => {
|
|||||||
it('should clear hidden tag counter when tag count falls below the limit', () => {
|
it('should clear hidden tag counter when tag count falls below the limit', () => {
|
||||||
expect(component.moreTags).toEqual(3)
|
expect(component.moreTags).toEqual(3)
|
||||||
|
|
||||||
component.document.tags = [1, 2, 3, 4, 5, 6]
|
component.document = { ...component.document, tags: [1, 2, 3, 4, 5, 6] }
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
|
|
||||||
expect(component.moreTags).toBeNull()
|
expect(component.moreTags).toBeNull()
|
||||||
|
|||||||
+34
-13
@@ -7,6 +7,7 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { RouterModule } from '@angular/router'
|
import { RouterModule } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
@@ -14,8 +15,6 @@ import {
|
|||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { of } from 'rxjs'
|
|
||||||
import { delay } from 'rxjs/operators'
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_DISPLAY_FIELDS,
|
DEFAULT_DISPLAY_FIELDS,
|
||||||
DisplayField,
|
DisplayField,
|
||||||
@@ -66,20 +65,43 @@ export class DocumentCardSmallComponent
|
|||||||
{
|
{
|
||||||
private documentService = inject(DocumentService)
|
private documentService = inject(DocumentService)
|
||||||
settingsService = inject(SettingsService)
|
settingsService = inject(SettingsService)
|
||||||
|
private selectedSignal = signal(false)
|
||||||
|
private documentSignal = signal<Document>(undefined)
|
||||||
|
private displayFieldsSignal = signal<string[]>(
|
||||||
|
DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
||||||
|
)
|
||||||
|
|
||||||
DisplayField = DisplayField
|
DisplayField = DisplayField
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
selected = false
|
get selected(): boolean {
|
||||||
|
return this.selectedSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set selected(selected: boolean) {
|
||||||
|
this.selectedSignal.set(selected)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
toggleSelected = new EventEmitter()
|
toggleSelected = new EventEmitter()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
document: Document
|
get document(): Document {
|
||||||
|
return this.documentSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set document(document: Document) {
|
||||||
|
this.documentSignal.set(document)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
displayFields: string[] = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
|
get displayFields(): string[] {
|
||||||
|
return this.displayFieldsSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set displayFields(displayFields: string[]) {
|
||||||
|
this.displayFieldsSignal.set(displayFields)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
dblClickDocument = new EventEmitter()
|
dblClickDocument = new EventEmitter()
|
||||||
@@ -96,16 +118,17 @@ export class DocumentCardSmallComponent
|
|||||||
@Output()
|
@Output()
|
||||||
clickStoragePath = new EventEmitter<number>()
|
clickStoragePath = new EventEmitter<number>()
|
||||||
|
|
||||||
moreTags: number = null
|
get moreTags(): number {
|
||||||
|
const limit = this.document?.notes.length > 0 ? 6 : 7
|
||||||
|
return this.document?.tags.length > limit
|
||||||
|
? this.document.tags.length - (limit - 1)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
@ViewChild('popupPreview') popupPreview: PreviewPopupComponent
|
@ViewChild('popupPreview') popupPreview: PreviewPopupComponent
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
of(true)
|
this.show = true
|
||||||
.pipe(delay(50))
|
|
||||||
.subscribe(() => {
|
|
||||||
this.show = true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getIsThumbInverted() {
|
getIsThumbInverted() {
|
||||||
@@ -123,10 +146,8 @@ export class DocumentCardSmallComponent
|
|||||||
get tagIDs() {
|
get tagIDs() {
|
||||||
const limit = this.document.notes.length > 0 ? 6 : 7
|
const limit = this.document.notes.length > 0 ? 6 : 7
|
||||||
if (this.document.tags.length > limit) {
|
if (this.document.tags.length > limit) {
|
||||||
this.moreTags = this.document.tags.length - (limit - 1)
|
|
||||||
return this.document.tags.slice(0, limit - 1)
|
return this.document.tags.slice(0, limit - 1)
|
||||||
} else {
|
} else {
|
||||||
this.moreTags = null
|
|
||||||
return this.document.tags
|
return this.document.tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -777,7 +777,7 @@ describe('DocumentListComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should hide columns if no perms or notes disabled', () => {
|
it('should hide columns if no perms or notes disabled', () => {
|
||||||
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
|
permissionService.initialize([], { is_superuser: true } as any)
|
||||||
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||||
expect(documentListService.sortField).toEqual('created')
|
expect(documentListService.sortField).toEqual('created')
|
||||||
|
|
||||||
@@ -798,7 +798,7 @@ describe('DocumentListComponent', () => {
|
|||||||
).toHaveLength(9)
|
).toHaveLength(9)
|
||||||
|
|
||||||
// insufficient perms
|
// insufficient perms
|
||||||
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
|
permissionService.initialize([], { is_superuser: false } as any)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||||
|
|||||||
@@ -570,6 +570,7 @@ export class DocumentListComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get notesEnabled(): boolean {
|
get notesEnabled(): boolean {
|
||||||
|
this.settingsService.trackChanges()
|
||||||
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
|
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-7
@@ -5,6 +5,7 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@@ -32,29 +33,49 @@ import { TextComponent } from '../../common/input/text/text.component'
|
|||||||
})
|
})
|
||||||
export class SaveViewConfigDialogComponent implements OnInit {
|
export class SaveViewConfigDialogComponent implements OnInit {
|
||||||
private modal = inject(NgbActiveModal)
|
private modal = inject(NgbActiveModal)
|
||||||
|
private errorSignal = signal(undefined)
|
||||||
|
private buttonsEnabledSignal = signal(true)
|
||||||
|
private defaultNameSignal = signal('')
|
||||||
|
private closeEnabledSignal = signal(false)
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
public saveClicked = new EventEmitter()
|
public saveClicked = new EventEmitter()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
error
|
get error() {
|
||||||
|
return this.errorSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set error(error) {
|
||||||
|
this.errorSignal.set(error)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
buttonsEnabled = true
|
get buttonsEnabled(): boolean {
|
||||||
|
return this.buttonsEnabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
closeEnabled = false
|
set buttonsEnabled(buttonsEnabled: boolean) {
|
||||||
|
this.buttonsEnabledSignal.set(buttonsEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
get closeEnabled(): boolean {
|
||||||
|
return this.closeEnabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set closeEnabled(closeEnabled: boolean) {
|
||||||
|
this.closeEnabledSignal.set(closeEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
users: User[]
|
users: User[]
|
||||||
|
|
||||||
_defaultName = ''
|
|
||||||
|
|
||||||
get defaultName() {
|
get defaultName() {
|
||||||
return this._defaultName
|
return this.defaultNameSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set defaultName(value: string) {
|
set defaultName(value: string) {
|
||||||
this._defaultName = value
|
this.defaultNameSignal.set(value)
|
||||||
this.saveViewConfigForm.patchValue({ name: value })
|
this.saveViewConfigForm.patchValue({ name: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -31,22 +38,51 @@ export class DocumentNotesComponent extends ComponentWithPermissions {
|
|||||||
private notesService = inject(DocumentNotesService)
|
private notesService = inject(DocumentNotesService)
|
||||||
private toastService = inject(ToastService)
|
private toastService = inject(ToastService)
|
||||||
private usersService = inject(UserService)
|
private usersService = inject(UserService)
|
||||||
|
private documentIdSignal = signal<number>(undefined)
|
||||||
|
private notesSignal = signal<DocumentNote[]>([])
|
||||||
|
private addDisabledSignal = signal(false)
|
||||||
|
private networkActiveSignal = signal(false)
|
||||||
|
|
||||||
noteForm: FormGroup = new FormGroup({
|
noteForm: FormGroup = new FormGroup({
|
||||||
newNote: new FormControl(''),
|
newNote: new FormControl(''),
|
||||||
})
|
})
|
||||||
|
|
||||||
networkActive = false
|
get networkActive(): boolean {
|
||||||
|
return this.networkActiveSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set networkActive(networkActive: boolean) {
|
||||||
|
this.networkActiveSignal.set(networkActive)
|
||||||
|
}
|
||||||
|
|
||||||
newNoteError: boolean = false
|
newNoteError: boolean = false
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
documentId: number
|
get documentId(): number {
|
||||||
|
return this.documentIdSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set documentId(documentId: number) {
|
||||||
|
this.documentIdSignal.set(documentId)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
notes: DocumentNote[] = []
|
get notes(): DocumentNote[] {
|
||||||
|
return this.notesSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set notes(notes: DocumentNote[]) {
|
||||||
|
this.notesSignal.set(notes)
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
addDisabled: boolean = false
|
get addDisabled(): boolean {
|
||||||
|
return this.addDisabledSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
set addDisabled(addDisabled: boolean) {
|
||||||
|
this.addDisabledSignal.set(addDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
updated: EventEmitter<DocumentNote[]> = new EventEmitter()
|
updated: EventEmitter<DocumentNote[]> = new EventEmitter()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div [class.pe-none]="fileIsOver">
|
<div [class.pe-none]="fileIsOver()">
|
||||||
<ng-content select="[content]"></ng-content>
|
<ng-content select="[content]"></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="global-dropzone-overlay position-fixed top-0 start-0 bottom-0 end-0 text-center pe-none" [class.active]="fileIsOver && !hidden">
|
<div class="global-dropzone-overlay position-fixed top-0 start-0 bottom-0 end-0 text-center pe-none" [class.active]="fileIsOver() && !hidden()">
|
||||||
<h2 class="pe-none position-absolute top-50 start-50 translate-middle" i18n>Drop files to begin upload</h2>
|
<h2 class="pe-none position-absolute top-50 start-50 translate-middle" i18n>Drop files to begin upload</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ describe('FileDropComponent', () => {
|
|||||||
component.onDragOver(new Event('dragover') as DragEvent)
|
component.onDragOver(new Event('dragover') as DragEvent)
|
||||||
tick(1)
|
tick(1)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.fileIsOver).toBeFalsy()
|
expect(component.fileIsOver()).toBeFalsy()
|
||||||
const dropzone = fixture.debugElement.query(
|
const dropzone = fixture.debugElement.query(
|
||||||
By.css('.global-dropzone-overlay')
|
By.css('.global-dropzone-overlay')
|
||||||
)
|
)
|
||||||
@@ -85,13 +85,13 @@ describe('FileDropComponent', () => {
|
|||||||
|
|
||||||
it('should support drag drop, initiate upload', fakeAsync(() => {
|
it('should support drag drop, initiate upload', fakeAsync(() => {
|
||||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||||
expect(component.fileIsOver).toBeFalsy()
|
expect(component.fileIsOver()).toBeFalsy()
|
||||||
const overEvent = new Event('dragover') as DragEvent
|
const overEvent = new Event('dragover') as DragEvent
|
||||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||||
component.onDragOver(overEvent)
|
component.onDragOver(overEvent)
|
||||||
tick(1)
|
tick(1)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.fileIsOver).toBeTruthy()
|
expect(component.fileIsOver()).toBeTruthy()
|
||||||
component.onDragLeave(new Event('dragleave') as DragEvent)
|
component.onDragLeave(new Event('dragleave') as DragEvent)
|
||||||
tick(700)
|
tick(700)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
@@ -121,13 +121,13 @@ describe('FileDropComponent', () => {
|
|||||||
|
|
||||||
it('should support drag drop, initiate upload with webkitGetAsEntry', fakeAsync(() => {
|
it('should support drag drop, initiate upload with webkitGetAsEntry', fakeAsync(() => {
|
||||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||||
expect(component.fileIsOver).toBeFalsy()
|
expect(component.fileIsOver()).toBeFalsy()
|
||||||
const overEvent = new Event('dragover') as DragEvent
|
const overEvent = new Event('dragover') as DragEvent
|
||||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||||
component.onDragOver(overEvent)
|
component.onDragOver(overEvent)
|
||||||
tick(1)
|
tick(1)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.fileIsOver).toBeTruthy()
|
expect(component.fileIsOver()).toBeTruthy()
|
||||||
component.onDragLeave(new Event('dragleave') as DragEvent)
|
component.onDragLeave(new Event('dragleave') as DragEvent)
|
||||||
tick(700)
|
tick(700)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
@@ -202,13 +202,13 @@ describe('FileDropComponent', () => {
|
|||||||
|
|
||||||
it('should support drag drop, initiate upload without DataTransfer API support', fakeAsync(() => {
|
it('should support drag drop, initiate upload without DataTransfer API support', fakeAsync(() => {
|
||||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||||
expect(component.fileIsOver).toBeFalsy()
|
expect(component.fileIsOver()).toBeFalsy()
|
||||||
const overEvent = new Event('dragover') as DragEvent
|
const overEvent = new Event('dragover') as DragEvent
|
||||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||||
component.onDragOver(overEvent)
|
component.onDragOver(overEvent)
|
||||||
tick(1)
|
tick(1)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.fileIsOver).toBeTruthy()
|
expect(component.fileIsOver()).toBeTruthy()
|
||||||
component.onDragLeave(new Event('dragleave') as DragEvent)
|
component.onDragLeave(new Event('dragleave') as DragEvent)
|
||||||
tick(700)
|
tick(700)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
@@ -315,8 +315,8 @@ describe('FileDropComponent', () => {
|
|||||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||||
component.onDragOver(overEvent)
|
component.onDragOver(overEvent)
|
||||||
tick(1)
|
tick(1)
|
||||||
expect(component.hidden).toBeFalsy()
|
expect(component.hidden()).toBeFalsy()
|
||||||
expect(component.fileIsOver).toBeTruthy()
|
expect(component.fileIsOver()).toBeTruthy()
|
||||||
jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
|
jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
|
||||||
component.onVisibilityChange()
|
component.onVisibilityChange()
|
||||||
expect(leaveSpy).toHaveBeenCalled()
|
expect(leaveSpy).toHaveBeenCalled()
|
||||||
@@ -331,8 +331,8 @@ describe('FileDropComponent', () => {
|
|||||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||||
component.onDragOver(overEvent)
|
component.onDragOver(overEvent)
|
||||||
tick(1)
|
tick(1)
|
||||||
expect(component.hidden).toBeFalsy()
|
expect(component.hidden()).toBeFalsy()
|
||||||
expect(component.fileIsOver).toBeTruthy()
|
expect(component.fileIsOver()).toBeTruthy()
|
||||||
jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
|
jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
|
||||||
component.onWindowBlur()
|
component.onWindowBlur()
|
||||||
expect(leaveSpy).toHaveBeenCalled()
|
expect(leaveSpy).toHaveBeenCalled()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, HostListener, inject } from '@angular/core'
|
import { Component, HostListener, inject, signal } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
PermissionAction,
|
PermissionAction,
|
||||||
PermissionsService,
|
PermissionsService,
|
||||||
@@ -21,8 +21,8 @@ export class FileDropComponent {
|
|||||||
private permissionsService = inject(PermissionsService)
|
private permissionsService = inject(PermissionsService)
|
||||||
|
|
||||||
private fileLeaveTimeoutID: any
|
private fileLeaveTimeoutID: any
|
||||||
fileIsOver: boolean = false
|
fileIsOver = signal<boolean>(false)
|
||||||
hidden: boolean = true
|
hidden = signal<boolean>(true)
|
||||||
|
|
||||||
public get dragDropEnabled(): boolean {
|
public get dragDropEnabled(): boolean {
|
||||||
return (
|
return (
|
||||||
@@ -42,9 +42,9 @@ export class FileDropComponent {
|
|||||||
this.settings.globalDropzoneActive = true
|
this.settings.globalDropzoneActive = true
|
||||||
// allows transition
|
// allows transition
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fileIsOver = true
|
this.fileIsOver.set(true)
|
||||||
}, 1)
|
}, 1)
|
||||||
this.hidden = false
|
this.hidden.set(false)
|
||||||
// stop fileLeave timeout
|
// stop fileLeave timeout
|
||||||
clearTimeout(this.fileLeaveTimeoutID)
|
clearTimeout(this.fileLeaveTimeoutID)
|
||||||
}
|
}
|
||||||
@@ -61,10 +61,10 @@ export class FileDropComponent {
|
|||||||
const ms = immediate ? 0 : 500
|
const ms = immediate ? 0 : 500
|
||||||
|
|
||||||
this.fileLeaveTimeoutID = setTimeout(() => {
|
this.fileLeaveTimeoutID = setTimeout(() => {
|
||||||
this.fileIsOver = false
|
this.fileIsOver.set(false)
|
||||||
// await transition completed
|
// await transition completed
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.hidden = true
|
this.hidden.set(true)
|
||||||
}, 150)
|
}, 150)
|
||||||
}, ms)
|
}, ms)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Directive, OnDestroy } from '@angular/core'
|
import { Directive, OnDestroy, signal } from '@angular/core'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@@ -7,8 +7,24 @@ export abstract class LoadingComponentWithPermissions
|
|||||||
extends ComponentWithPermissions
|
extends ComponentWithPermissions
|
||||||
implements OnDestroy
|
implements OnDestroy
|
||||||
{
|
{
|
||||||
public loading: boolean = true
|
private loadingSignal = signal(true)
|
||||||
public show: boolean = false
|
private showSignal = signal(false)
|
||||||
|
|
||||||
|
public get loading(): boolean {
|
||||||
|
return this.loadingSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set loading(value: boolean) {
|
||||||
|
this.loadingSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get show(): boolean {
|
||||||
|
return this.showSignal()
|
||||||
|
}
|
||||||
|
|
||||||
|
public set show(value: boolean) {
|
||||||
|
this.showSignal.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
protected unsubscribeNotifier: Subject<any> = new Subject()
|
protected unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user