From 8425d4465817d2876595f34baf47179a2e7a337e Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Thu, 23 Jul 2026 00:29:21 +0200 Subject: [PATCH] feat: add shared Toolbox header and themes --- CHANGELOG.md | 15 + README.md | 18 +- package-lock.json | 32 +- package.json | 8 +- public/toolbox-app.json | 9 +- src/App.tsx | 21 +- src/components/ActionDialog.tsx | 31 +- src/components/ActionsPanel.tsx | 2 +- src/components/FileLoader.tsx | 8 +- src/components/MergeQueuePanel.tsx | 15 +- .../PageWorkspace/CopyPagesDialog.tsx | 31 +- src/components/PageWorkspace/PageCard.tsx | 38 ++- .../PageWorkspace/PageSelectionToolbar.tsx | 18 +- src/components/ReorderPanel.tsx | 6 +- src/components/WorkspacePanel.tsx | 48 +-- src/styles.css | 314 ++++++++++-------- src/toolboxApp.definition.json | 7 - src/toolboxApp.test.ts | 7 + src/version.ts | 2 +- 19 files changed, 356 insertions(+), 274 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25e4992..7b2c7c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to `pdf-tools` are documented here. The project follows a pragmatic versioning scheme while the app is still below `1.0.0`: minor versions mark coherent user-facing milestones; patch versions mark fixes and small improvements. +## 0.4.0 — Shared Toolbox header and themes + +### Added + +- Added the standard Toolbox header controls for in-app help, the app-specific Gitea source link, appearance personalization, and application switching. +- Added explicit light, dark, and system-following themes for the PDF workspace, saved-workspace list, merge queue, dialogs, status messages, and controls. + +### Changed + +- Upgraded the Toolbox contract, React shell, and validation testkit to `0.2.0`. +- The header now uses the manifest's repository metadata for the source link instead of carrying a release-specific source action. +- Replaced application color literals with semantic PDF workspace variables while intentionally keeping rendered PDF pages on a white paper surface. +- Scoped application button and card styles to the shell's main content so they cannot override shared header controls. +- Bumped the app/package version to `0.4.0`. + ## 0.3.4 — PDF.js compatibility and Toolbox integration ### Added diff --git a/README.md b/README.md index afe8b2f..ee672dd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Current hosted version: -Current release branch: **v0.3.4 — PDF.js `toHex` browser compatibility fix**. See [`CHANGELOG.md`](CHANGELOG.md) for milestone history. +Current development release: **v0.4.0 — shared Toolbox header and themes**. See [`CHANGELOG.md`](CHANGELOG.md) for milestone history. Active work is tracked in Gitea issues. Durable project context is mirrored into the Gitea wiki from repository docs, including [`ROADMAP.md`](ROADMAP.md). @@ -43,6 +43,7 @@ This makes the project especially useful for self-hosted environments, public-se - **Stable page references**: Duplicated pages and reordered pages are tracked as workspace page references rather than only by original page number. - **Merge choices**: Loading another PDF can replace the current document, append pages, or insert pages at a chosen position. - **PDF.js compatibility setup**: PDF.js is configured through one shared module and a local worker wrapper so the app can polyfill `Uint8Array.prototype.toHex` before PDF.js runs in older browsers. +- **Toolbox appearance controls**: The shared header provides light, dark, and system-following themes and keeps the app switcher, help, and source controls in the same place as the Toolbox portal. - **In-app help**: The app includes a Help/Tutorial dialog with keyboard shortcuts and workflow explanations. ## Current features @@ -96,7 +97,7 @@ Keyboard shortcuts are ignored while typing in form fields. ## In-app documentation concept -The app includes a Help/Tutorial dialog reachable from the header via **Help ?**, `F1`, or `?`. +The app includes a Help/Tutorial dialog reachable from the header's **?** control, `F1`, or `?`. Recommended structure for in-app documentation: @@ -126,10 +127,11 @@ The production build is relocatable and can also be assembled into an [Toolbox App Contract](https://git.add-ideas.de/zemion/toolbox-sdk). The same artifact remains fully usable on its own. -When launched with a same-origin catalogue URL, for example -`?toolbox=/toolbox.catalog.json`, the shared first-party shell adds a toolbox -home link and application switcher. Missing, invalid, and cross-origin -catalogues are ignored so the app falls back to standalone mode. +The shared first-party shell always provides help, source, and appearance +controls. When launched with a same-origin catalogue URL, for example +`?toolbox=/toolbox.catalog.json`, it also connects the application switcher to +the current Toolbox catalogue. Missing, invalid, and cross-origin catalogues +are ignored so the app falls back to standalone mode. ### Build and test from source @@ -255,10 +257,10 @@ The application version shown in the header is defined in `src/version.ts`. The The current development baseline is: ```text -v0.3.4 — PDF.js toHex browser compatibility fix +v0.4.0 — shared Toolbox header and light/dark/system themes ``` -This branch preserves the browser-only workspace baseline, includes the multi-file merge queue from the `0.3.2` milestone, bumps the displayed app/package version to `0.3.4`, and adds a shared PDF.js setup with a `toHex` compatibility shim for older browsers. Workspace state, thumbnail handling, generated download URLs, page-grid components, tests, type-checking, linting, and formatting are separated enough to support additional feature work without turning `App.tsx` back into a monolith. +This branch preserves the browser-only workspace baseline and PDF.js compatibility setup while moving the app to Toolbox SDK `0.2.0`. The displayed app/package version is `0.4.0`; the shared header and saved appearance preference now work in standalone and portal-launched sessions. Workspace state, thumbnail handling, generated download URLs, page-grid components, tests, type-checking, linting, and formatting remain separated enough to support additional feature work without turning `App.tsx` back into a monolith. ## Project structure diff --git a/package-lock.json b/package-lock.json index 8d21b71..c224314 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "pdf-tools", - "version": "0.3.4", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pdf-tools", - "version": "0.3.4", + "version": "0.4.0", "license": "AGPL-3.0-only", "dependencies": { - "@add-ideas/toolbox-contract": "^0.1.0", - "@add-ideas/toolbox-shell-react": "^0.1.0", + "@add-ideas/toolbox-contract": "^0.2.0", + "@add-ideas/toolbox-shell-react": "^0.2.0", "fflate": "^0.8.3", "pdf-lib": "^1.17.1", "pdfjs-dist": "^5.7.284", @@ -18,7 +18,7 @@ "react-dom": "^19.2.6" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "^0.1.0", + "@add-ideas/toolbox-testkit": "^0.2.0", "@eslint/js": "^10.0.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", @@ -40,18 +40,18 @@ } }, "node_modules/@add-ideas/toolbox-contract": { - "version": "0.1.1", - "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-contract/-/0.1.1/toolbox-contract-0.1.1.tgz", - "integrity": "sha512-0q2whA5Y4SqG4PNNleXYR8nGbT3UYsZ1x+6ZWVmxuc/WjMxmujhD7ilIISmFzmnNdbyEyv/Zpr+U0fnHqGODqw==", + "version": "0.2.0", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-contract/-/0.2.0/toolbox-contract-0.2.0.tgz", + "integrity": "sha512-up+wY+ft1L7TbXrflcTeTOwEdcFwHBWFlGFj8skQvcLy5z2mDBpd6/7PVQKDvDTkiODu2npviHm6AFglFw+17Q==", "license": "Apache-2.0" }, "node_modules/@add-ideas/toolbox-shell-react": { - "version": "0.1.1", - "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.1.1/toolbox-shell-react-0.1.1.tgz", - "integrity": "sha512-Di2mu1BoJrWJg4/v5jDExROXDpvLXorlUgqcsBQV8ea/LklKoBFTAfgTkk/23HZ2BzixyMIjrhl9kD507s+iPQ==", + "version": "0.2.0", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.2.0/toolbox-shell-react-0.2.0.tgz", + "integrity": "sha512-qYR+OBXX8rfB+BYCsUbpiCzo2xK12SIPWVNXHre158Da5uT6usr8rRvp5qa5u2oQJ50PNzlXyXMyTeRp0Vz5wg==", "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.1.1" + "@add-ideas/toolbox-contract": "0.2.0" }, "peerDependencies": { "react": ">=18 <20", @@ -59,13 +59,13 @@ } }, "node_modules/@add-ideas/toolbox-testkit": { - "version": "0.1.1", - "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-testkit/-/0.1.1/toolbox-testkit-0.1.1.tgz", - "integrity": "sha512-o+H7LfM7ipdgmYz18S0lLuN6JsXUR+8ubAbcjTWTk8rSqNfp1JyeoRHOPbJVEHXrQwIFe/OB86UzRpex2gnKUQ==", + "version": "0.2.0", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-testkit/-/0.2.0/toolbox-testkit-0.2.0.tgz", + "integrity": "sha512-+BSiWgS0F3vpjPgty9B4UtfdZTRbo97ZElb2BSb0ry57fS3cE7w7pfJW1Mzk/gV1/JChspw3JBY+4ZJDBhkiFw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.1.1" + "@add-ideas/toolbox-contract": "0.2.0" }, "bin": { "toolbox-check": "dist/cli.js" diff --git a/package.json b/package.json index d4ec7a2..2cf08a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pdf-tools", - "version": "0.3.4", + "version": "0.4.0", "private": true, "license": "AGPL-3.0-only", "type": "module", @@ -23,8 +23,8 @@ "check": "npm run manifest:check && npm run typecheck && npm run lint && npm run test && npm run build && npm run test:toolbox" }, "dependencies": { - "@add-ideas/toolbox-contract": "^0.1.0", - "@add-ideas/toolbox-shell-react": "^0.1.0", + "@add-ideas/toolbox-contract": "^0.2.0", + "@add-ideas/toolbox-shell-react": "^0.2.0", "fflate": "^0.8.3", "pdf-lib": "^1.17.1", "pdfjs-dist": "^5.7.284", @@ -32,7 +32,7 @@ "react-dom": "^19.2.6" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "^0.1.0", + "@add-ideas/toolbox-testkit": "^0.2.0", "@eslint/js": "^10.0.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", diff --git a/public/toolbox-app.json b/public/toolbox-app.json index 3c24e93..ef3ace9 100644 --- a/public/toolbox-app.json +++ b/public/toolbox-app.json @@ -8,13 +8,6 @@ "icon": "./favicon.svg", "categories": ["documents", "pdf"], "tags": ["merge", "split", "rotate", "reorder"], - "actions": [ - { - "id": "source", - "label": "Source", - "url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4" - } - ], "integration": { "contextVersion": 1, "launchModes": ["navigate", "new-tab"], @@ -35,5 +28,5 @@ "repository": "https://git.add-ideas.de/zemion/pdf-tools", "license": "AGPL-3.0-only" }, - "version": "0.3.4" + "version": "0.4.0" } diff --git a/src/App.tsx b/src/App.tsx index 18fcb6b..2b1cda3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1143,17 +1143,10 @@ const App: React.FC = () => { setHelpOpen(true)} - aria-haspopup="dialog" - title="Open help and keyboard shortcuts (F1 or ?)" - > - Help - - } + helpAction={{ + onClick: () => setHelpOpen(true), + title: 'Open help and keyboard shortcuts (F1 or ?)', + }} > @@ -1237,7 +1230,11 @@ const App: React.FC = () => { {error && (
Error: {error}
diff --git a/src/components/ActionDialog.tsx b/src/components/ActionDialog.tsx index 167258f..b4755af 100644 --- a/src/components/ActionDialog.tsx +++ b/src/components/ActionDialog.tsx @@ -21,18 +21,18 @@ const backgroundByVariant: Record< NonNullable, string > = { - primary: '#2563eb', - secondary: '#e5e7eb', - danger: '#dc2626', + primary: 'var(--pdf-primary)', + secondary: 'var(--pdf-control)', + danger: 'var(--pdf-danger-text)', }; const colorByVariant: Record< NonNullable, string > = { - primary: 'white', - secondary: '#111827', - danger: 'white', + primary: 'var(--pdf-on-primary)', + secondary: 'var(--pdf-control-text)', + danger: 'var(--pdf-on-primary)', }; const ActionDialog: React.FC = ({ @@ -75,7 +75,7 @@ const ActionDialog: React.FC = ({ position: 'fixed', inset: 0, zIndex: 70, - background: 'rgba(15, 23, 42, 0.55)', + background: 'var(--pdf-overlay)', display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -86,9 +86,10 @@ const ActionDialog: React.FC = ({ style={{ width: '100%', maxWidth: '440px', - background: 'white', + background: 'var(--pdf-surface-raised)', + color: 'var(--pdf-text)', borderRadius: '0.75rem', - boxShadow: '0 20px 40px rgba(15, 23, 42, 0.35)', + boxShadow: 'var(--pdf-dialog-shadow)', padding: '1rem', display: 'flex', flexDirection: 'column', @@ -121,8 +122,8 @@ const ActionDialog: React.FC = ({ borderRadius: '999px', width: '1.8rem', height: '1.8rem', - background: '#e5e7eb', - color: '#111827', + background: 'var(--pdf-control)', + color: 'var(--pdf-control-text)', cursor: 'pointer', fontSize: '1.1rem', lineHeight: 1, @@ -139,7 +140,7 @@ const ActionDialog: React.FC = ({
@@ -173,9 +174,11 @@ const ActionDialog: React.FC = ({ borderRadius: '0.5rem', padding: '0.45rem 0.8rem', background: action.disabled - ? '#e5e7eb' + ? 'var(--pdf-control)' : backgroundByVariant[variant], - color: action.disabled ? '#6b7280' : colorByVariant[variant], + color: action.disabled + ? 'var(--pdf-muted-soft)' + : colorByVariant[variant], cursor: action.disabled ? 'default' : 'pointer', fontSize: '0.9rem', }} diff --git a/src/components/ActionsPanel.tsx b/src/components/ActionsPanel.tsx index bd672e1..fb4d8cf 100644 --- a/src/components/ActionsPanel.tsx +++ b/src/components/ActionsPanel.tsx @@ -49,7 +49,7 @@ const ActionsPanel: React.FC = ({ return (

Tools

-

+

Use these tools on the current in-memory document (reordered, rotated, with deletions). Nothing is uploaded to a server.

diff --git a/src/components/FileLoader.tsx b/src/components/FileLoader.tsx index cab02bd..855537a 100644 --- a/src/components/FileLoader.tsx +++ b/src/components/FileLoader.tsx @@ -30,7 +30,13 @@ const FileLoader: React.FC = ({ pdf, onFilesLoaded }) => { /> {pdf && ( -
+
Loaded: {pdf.name}
diff --git a/src/components/MergeQueuePanel.tsx b/src/components/MergeQueuePanel.tsx index bc4b01b..d5aa38b 100644 --- a/src/components/MergeQueuePanel.tsx +++ b/src/components/MergeQueuePanel.tsx @@ -54,22 +54,25 @@ const MergeQueuePanel: React.FC = ({ return (

Merge PDF queue

-

+

Add several PDFs, reorder the queue, then merge them into a new unsaved workspace. Processing still happens entirely in your browser.

{hasCurrentPdf ? ( -

+

Current workspace:{' '} {currentPdfName ?? 'Untitled document'} with{' '} {currentPageCount} {currentPageCount === 1 ? 'page' : 'pages'}.

) : ( -

+

No current workspace is open. The queue will become a new workspace.

)} @@ -122,7 +125,7 @@ const MergeQueuePanel: React.FC = ({ ))}
-

+

Queue total:{' '} {items.length} {items.length === 1 ? 'PDF' : 'PDFs'} @@ -186,7 +189,7 @@ const MergeQueuePanel: React.FC = ({ fontSize: '0.85rem', }} />{' '} - + (1 = before first page, {currentPageCount + 1} = after last page) diff --git a/src/components/PageWorkspace/CopyPagesDialog.tsx b/src/components/PageWorkspace/CopyPagesDialog.tsx index 19b89e0..06b55d6 100644 --- a/src/components/PageWorkspace/CopyPagesDialog.tsx +++ b/src/components/PageWorkspace/CopyPagesDialog.tsx @@ -48,7 +48,7 @@ const CopyPagesDialog: React.FC = ({ position: 'fixed', inset: 0, zIndex: 60, - background: 'rgba(15, 23, 42, 0.55)', + background: 'var(--pdf-overlay)', display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -60,9 +60,10 @@ const CopyPagesDialog: React.FC = ({ style={{ width: '100%', maxWidth: '420px', - background: 'white', + background: 'var(--pdf-surface-raised)', + color: 'var(--pdf-text)', borderRadius: '0.75rem', - boxShadow: '0 20px 40px rgba(15, 23, 42, 0.35)', + boxShadow: 'var(--pdf-dialog-shadow)', padding: '1rem', display: 'flex', flexDirection: 'column', @@ -95,8 +96,8 @@ const CopyPagesDialog: React.FC = ({ borderRadius: '999px', width: '1.8rem', height: '1.8rem', - background: '#e5e7eb', - color: '#111827', + background: 'var(--pdf-control)', + color: 'var(--pdf-control-text)', cursor: 'pointer', fontSize: '1.1rem', lineHeight: 1, @@ -114,7 +115,7 @@ const CopyPagesDialog: React.FC = ({ style={{ margin: 0, fontSize: '0.9rem', - color: '#4b5563', + color: 'var(--pdf-muted)', }} > Copy{' '} @@ -145,7 +146,7 @@ const CopyPagesDialog: React.FC = ({ style={{ padding: '0.45rem 0.55rem', borderRadius: '0.5rem', - border: '1px solid #d1d5db', + border: '1px solid var(--pdf-border-strong)', fontSize: '0.95rem', }} /> @@ -154,7 +155,7 @@ const CopyPagesDialog: React.FC = ({

@@ -166,9 +167,9 @@ const CopyPagesDialog: React.FC = ({
= ({ border: 'none', borderRadius: '0.5rem', padding: '0.45rem 0.8rem', - background: '#e5e7eb', - color: '#111827', + background: 'var(--pdf-control)', + color: 'var(--pdf-control-text)', cursor: 'pointer', fontSize: '0.9rem', }} @@ -207,8 +208,8 @@ const CopyPagesDialog: React.FC = ({ border: 'none', borderRadius: '0.5rem', padding: '0.45rem 0.8rem', - background: '#16a34a', - color: 'white', + background: 'var(--pdf-success-text)', + color: 'var(--pdf-on-primary)', cursor: 'pointer', fontSize: '0.9rem', }} diff --git a/src/components/PageWorkspace/PageCard.tsx b/src/components/PageWorkspace/PageCard.tsx index f1df7f9..712bca8 100644 --- a/src/components/PageWorkspace/PageCard.tsx +++ b/src/components/PageWorkspace/PageCard.tsx @@ -53,11 +53,11 @@ const PageCard: React.FC = ({ }) => { const background = isDraggingCard ? isCopyDragging - ? '#dcfce7' - : '#dbeafe' + ? 'var(--pdf-success-soft)' + : 'var(--pdf-info-surface)' : selected - ? '#eff6ff' - : '#f9fafb'; + ? 'var(--pdf-info-surface)' + : 'var(--pdf-surface-soft)'; return (
= ({ width: '162px', padding: '0.4rem', borderRadius: '0.5rem', - border: '1px solid #e5e7eb', + border: '1px solid var(--pdf-border)', background, display: 'flex', flexDirection: 'column', @@ -91,9 +91,11 @@ const PageCard: React.FC = ({ width: '20px', height: '20px', borderRadius: '0.4rem', - border: '1px solid #9ca3af', - background: selected ? '#2563eb' : 'rgba(255,255,255,0.9)', - color: selected ? 'white' : 'transparent', + border: '1px solid var(--pdf-border-strong)', + background: selected + ? 'var(--pdf-primary)' + : 'color-mix(in srgb, var(--pdf-surface) 92%, transparent)', + color: selected ? 'var(--pdf-on-primary)' : 'transparent', fontSize: '0.8rem', display: 'flex', alignItems: 'center', @@ -131,8 +133,8 @@ const PageCard: React.FC = ({ height: 'auto', objectFit: 'contain', borderRadius: '0.25rem', - border: '1px solid #e5e7eb', - background: 'white', + border: '1px solid var(--pdf-paper-border)', + background: 'var(--pdf-paper)', }} /> ) : ( @@ -141,8 +143,8 @@ const PageCard: React.FC = ({ width: '60px', height: '80px', borderRadius: '0.25rem', - border: '1px dashed #d1d5db', - background: '#f3f4f6', + border: '1px dashed var(--pdf-paper-border)', + background: 'var(--pdf-paper-placeholder)', }} /> )} @@ -151,7 +153,7 @@ const PageCard: React.FC = ({ Page {page.sourcePageIndex + 1} - + Pos {visualIndex + 1} · Rot {page.rotation}° @@ -170,7 +172,8 @@ const PageCard: React.FC = ({ }} style={{ ...pageActionButtonStyle, - background: '#e5e7eb', + background: 'var(--pdf-control)', + color: 'var(--pdf-control-text)', }} > ↻ 90° @@ -184,7 +187,8 @@ const PageCard: React.FC = ({ }} style={{ ...pageActionButtonStyle, - background: '#e5e7eb', + background: 'var(--pdf-control)', + color: 'var(--pdf-control-text)', }} > ↺ 90° @@ -198,8 +202,8 @@ const PageCard: React.FC = ({ }} style={{ ...pageActionButtonStyle, - background: '#fecaca', - color: '#b91c1c', + background: 'var(--pdf-danger-soft)', + color: 'var(--pdf-danger-text)', }} title="Remove this page from the exported PDF" > diff --git a/src/components/PageWorkspace/PageSelectionToolbar.tsx b/src/components/PageWorkspace/PageSelectionToolbar.tsx index 176dba0..f3857f9 100644 --- a/src/components/PageWorkspace/PageSelectionToolbar.tsx +++ b/src/components/PageWorkspace/PageSelectionToolbar.tsx @@ -53,8 +53,8 @@ const PageSelectionToolbar: React.FC = ({ disabled={!hasSelection} style={{ ...pillButtonStyle, - background: '#dcfce7', - color: '#166534', + background: 'var(--pdf-success-soft)', + color: 'var(--pdf-success-text)', cursor: 'pointer', }} title="Copy selected pages to another position" @@ -69,8 +69,8 @@ const PageSelectionToolbar: React.FC = ({ onClick={onDeleteSelected} style={{ ...pillButtonStyle, - background: '#fee2e2', - color: '#b91c1c', + background: 'var(--pdf-danger-soft)', + color: 'var(--pdf-danger-text)', cursor: 'pointer', }} > @@ -83,8 +83,8 @@ const PageSelectionToolbar: React.FC = ({ onClick={onSelectAll} style={{ ...pillButtonStyle, - background: '#8dcd8d', - color: '#111827', + background: 'var(--pdf-success-border)', + color: 'var(--pdf-control-text)', cursor: 'pointer', }} > @@ -97,8 +97,10 @@ const PageSelectionToolbar: React.FC = ({ disabled={!hasSelection} style={{ ...pillButtonStyle, - background: '#e5e7eb', - color: hasSelection ? '#111827' : '#6b7280', + background: 'var(--pdf-control)', + color: hasSelection + ? 'var(--pdf-control-text)' + : 'var(--pdf-muted-soft)', cursor: hasSelection ? 'pointer' : 'default', }} > diff --git a/src/components/ReorderPanel.tsx b/src/components/ReorderPanel.tsx index ac95c7a..89dfdce 100644 --- a/src/components/ReorderPanel.tsx +++ b/src/components/ReorderPanel.tsx @@ -279,13 +279,15 @@ const ReorderPanel: React.FC = ({ draggingPage != null && selectedPageIds.length > 0 && selectedPageIds.includes(draggingPage.id); - const dropIndicatorColor = isCopyDragging ? '#16a34a' : '#2563eb'; + const dropIndicatorColor = isCopyDragging + ? 'var(--pdf-success-text)' + : 'var(--pdf-primary)'; return ( <>

Pages

-

+

Tap/click a page to preview it. Use the checkbox to select pages (Shift for ranges). Drag to reorder; dragging a selected page moves the whole selection. Hold Ctrl/⌘ while dropping to copy instead of diff --git a/src/components/WorkspacePanel.tsx b/src/components/WorkspacePanel.tsx index e711e94..6d82aa9 100644 --- a/src/components/WorkspacePanel.tsx +++ b/src/components/WorkspacePanel.tsx @@ -54,7 +54,7 @@ const WorkspacePanel: React.FC = ({

Workspace

-

+

Save named workspaces in this browser. PDF binaries are stored in IndexedDB; nothing is uploaded.

@@ -78,7 +78,7 @@ const WorkspacePanel: React.FC = ({ minWidth: 0, padding: '0.45rem 0.55rem', borderRadius: '0.5rem', - border: '1px solid #d1d5db', + border: '1px solid var(--pdf-border-strong)', fontSize: '0.9rem', }} /> @@ -140,7 +140,7 @@ const WorkspacePanel: React.FC = ({ style={{ marginTop: '0.5rem', fontSize: '0.8rem', - color: '#92400e', + color: 'var(--pdf-warning-text)', }} > Unsaved workspace changes. @@ -152,7 +152,7 @@ const WorkspacePanel: React.FC = ({ style={{ marginTop: '0.5rem', fontSize: '0.85rem', - color: '#166534', + color: 'var(--pdf-success-text)', }} > {workspaceMessage} @@ -178,10 +178,12 @@ const WorkspacePanel: React.FC = ({
= ({
{workspace.name} {active && ( - · active + + {' '} + · active + )}
-
+
{workspace.pdfName} · source pages:{' '} {workspace.sourcePageCount} · workspace pages:{' '} {workspace.workspacePageCount} · undo:{' '} @@ -228,8 +238,8 @@ const WorkspacePanel: React.FC = ({ disabled={isBusy} onClick={() => onDeleteWorkspace(workspace.id)} style={{ - background: '#fee2e2', - color: '#991b1b', + background: 'var(--pdf-danger-soft)', + color: 'var(--pdf-danger-text)', }} > Delete @@ -261,8 +271,8 @@ const WorkspacePanel: React.FC = ({ key={entry.id} style={{ fontSize: '0.8rem', - color: '#374151', - borderLeft: '3px solid #2563eb', + color: 'var(--pdf-muted)', + borderLeft: '3px solid var(--pdf-primary)', paddingLeft: '0.45rem', paddingTop: '0.2rem', paddingBottom: '0.2rem', @@ -272,7 +282,7 @@ const WorkspacePanel: React.FC = ({ Undo {history.length - index}. {entry.label}
- + {new Date(entry.timestamp).toLocaleString()}
@@ -282,12 +292,12 @@ const WorkspacePanel: React.FC = ({ style={{ margin: '0.25rem 0', borderRadius: '999px', - background: '#ecfdf5', - color: '#166534', + background: 'var(--pdf-success-surface)', + color: 'var(--pdf-success-text)', fontSize: '0.8rem', fontWeight: 600, alignSelf: 'flex-start', - border: '2px solid #166534', + border: '2px solid var(--pdf-success-text)', width: '100%', }} >
@@ -300,8 +310,8 @@ const WorkspacePanel: React.FC = ({ key={entry.id} style={{ fontSize: '0.8rem', - color: '#9ca3af', - borderLeft: '3px solid #d1d5db', + color: 'var(--pdf-muted-faint)', + borderLeft: '3px solid var(--pdf-border-strong)', paddingLeft: '0.45rem', paddingTop: '0.2rem', paddingBottom: '0.2rem', @@ -312,7 +322,7 @@ const WorkspacePanel: React.FC = ({ Redo {index + 1}. {entry.label}
- + {new Date(entry.timestamp).toLocaleString()}
diff --git a/src/styles.css b/src/styles.css index c92b7ae..4716fa3 100644 --- a/src/styles.css +++ b/src/styles.css @@ -12,89 +12,30 @@ body { BlinkMacSystemFont, 'Segoe UI', sans-serif; - background-color: #f3f4f6; - color: #111827; + background-color: var(--toolbox-background); + color: var(--toolbox-text); } #root { min-height: 100vh; } -.app-shell { - display: flex; - min-height: 100vh; -} - -.app-sidebar { - width: 260px; - background: #111827; - color: #e5e7eb; - padding: 1rem; - display: flex; - flex-direction: column; - gap: 1rem; -} - -.app-sidebar h1 { - font-size: 1.1rem; - margin: 0; -} - -.app-sidebar small { - color: #9ca3af; -} - -.app-nav { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.app-nav button { - width: 100%; - text-align: left; - padding: 0.4rem 0.6rem; - border-radius: 0.375rem; - border: none; - background: transparent; - color: #d1d5db; - cursor: pointer; - font-size: 0.9rem; -} - -.app-nav button.active { - background: #374151; - color: #f9fafb; -} - -.app-nav button:disabled { - opacity: 0.5; - cursor: default; -} - -.app-main { - flex: 1; - padding: 1rem 1.5rem; - display: flex; - flex-direction: column; - gap: 1rem; -} - -.card { - background: white; +.pdf-toolbox-shell .toolbox-shell__main .card { + border: 1px solid var(--pdf-border); + background: var(--pdf-surface); border-radius: 0.75rem; padding: 1rem; - box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12); + box-shadow: var(--pdf-card-shadow); } -.card h2 { +.pdf-toolbox-shell .toolbox-shell__main .card h2 { margin-top: 0; font-size: 1rem; } -button.primary { - background: #2563eb; - color: white; +.pdf-toolbox-shell .toolbox-shell__main button.primary { + background: var(--pdf-primary); + color: var(--pdf-on-primary); border-radius: 0.5rem; padding: 0.45rem 0.9rem; border: none; @@ -102,14 +43,14 @@ button.primary { font-size: 0.9rem; } -button.primary:disabled { +.pdf-toolbox-shell .toolbox-shell__main button.primary:disabled { opacity: 0.6; cursor: default; } -button.secondary { - background: #e5e7eb; - color: #111827; +.pdf-toolbox-shell .toolbox-shell__main button.secondary { + background: var(--pdf-control); + color: var(--pdf-control-text); border-radius: 0.5rem; padding: 0.45rem 0.9rem; border: none; @@ -117,7 +58,7 @@ button.secondary { font-size: 0.9rem; } -.button-row { +.pdf-toolbox-shell .toolbox-shell__main .button-row { display: flex; flex-wrap: wrap; gap: 0.5rem; @@ -125,14 +66,104 @@ button.secondary { } .pdf-toolbox-shell { - color-scheme: light; - --toolbox-background: #f3f4f6; - --toolbox-surface: #ffffff; - --toolbox-text: #111827; - --toolbox-muted: #4b5563; - --toolbox-border: #d1d5db; - --toolbox-accent: #2563eb; - --toolbox-accent-contrast: #ffffff; + --pdf-surface: var(--toolbox-surface); + --pdf-surface-soft: #f9fafb; + --pdf-surface-raised: #ffffff; + --pdf-text: var(--toolbox-text); + --pdf-muted: #4b5563; + --pdf-muted-soft: #6b7280; + --pdf-muted-faint: #9ca3af; + --pdf-border: #e5e7eb; + --pdf-border-strong: #d1d5db; + --pdf-primary: #2563eb; + --pdf-on-primary: #ffffff; + --pdf-control: #e5e7eb; + --pdf-control-text: #111827; + --pdf-info-surface: #eff6ff; + --pdf-info-border: #bfdbfe; + --pdf-info-text: #1e3a8a; + --pdf-success-surface: #ecfdf5; + --pdf-success-soft: #dcfce7; + --pdf-success-border: #86efac; + --pdf-success-text: #166534; + --pdf-warning-surface: #fff7ed; + --pdf-warning-border: #fed7aa; + --pdf-warning-text: #9a3412; + --pdf-danger-surface: #fef2f2; + --pdf-danger-soft: #fee2e2; + --pdf-danger-border: #fecaca; + --pdf-danger-text: #b91c1c; + --pdf-overlay: rgba(15, 23, 42, 0.62); + --pdf-dialog-shadow: 0 20px 40px rgba(15, 23, 42, 0.35); + --pdf-card-shadow: 0 1px 3px rgba(15, 23, 42, 0.12); + --pdf-paper: #ffffff; + --pdf-paper-placeholder: #f3f4f6; + --pdf-paper-border: #d1d5db; + --pdf-paper-text: #111827; +} + +.pdf-toolbox-shell[data-toolbox-theme='dark'] { + --pdf-surface-soft: #20283a; + --pdf-surface-raised: #1b2334; + --pdf-muted: #b6bfd2; + --pdf-muted-soft: #a9b3ca; + --pdf-muted-faint: #8792aa; + --pdf-border: #303a50; + --pdf-border-strong: #465169; + --pdf-primary: #8798f5; + --pdf-on-primary: #101728; + --pdf-control: #293247; + --pdf-control-text: #edf1fb; + --pdf-info-surface: #182946; + --pdf-info-border: #345a8c; + --pdf-info-text: #c6dcff; + --pdf-success-surface: #12382e; + --pdf-success-soft: #174436; + --pdf-success-border: #33765d; + --pdf-success-text: #a7e8cf; + --pdf-warning-surface: #402814; + --pdf-warning-border: #80522d; + --pdf-warning-text: #ffd2a6; + --pdf-danger-surface: #421f29; + --pdf-danger-soft: #4f2530; + --pdf-danger-border: #81394b; + --pdf-danger-text: #ffc0cc; + --pdf-overlay: rgba(2, 6, 16, 0.76); + --pdf-dialog-shadow: 0 24px 52px rgba(0, 0, 0, 0.5); + --pdf-card-shadow: 0 2px 7px rgba(0, 0, 0, 0.26); +} + +@media (prefers-color-scheme: dark) { + .pdf-toolbox-shell[data-toolbox-theme='system'] { + --pdf-surface-soft: #20283a; + --pdf-surface-raised: #1b2334; + --pdf-muted: #b6bfd2; + --pdf-muted-soft: #a9b3ca; + --pdf-muted-faint: #8792aa; + --pdf-border: #303a50; + --pdf-border-strong: #465169; + --pdf-primary: #8798f5; + --pdf-on-primary: #101728; + --pdf-control: #293247; + --pdf-control-text: #edf1fb; + --pdf-info-surface: #182946; + --pdf-info-border: #345a8c; + --pdf-info-text: #c6dcff; + --pdf-success-surface: #12382e; + --pdf-success-soft: #174436; + --pdf-success-border: #33765d; + --pdf-success-text: #a7e8cf; + --pdf-warning-surface: #402814; + --pdf-warning-border: #80522d; + --pdf-warning-text: #ffd2a6; + --pdf-danger-surface: #421f29; + --pdf-danger-soft: #4f2530; + --pdf-danger-border: #81394b; + --pdf-danger-text: #ffc0cc; + --pdf-overlay: rgba(2, 6, 16, 0.76); + --pdf-dialog-shadow: 0 24px 52px rgba(0, 0, 0, 0.5); + --pdf-card-shadow: 0 2px 7px rgba(0, 0, 0, 0.26); + } } .pdf-toolbox-shell .toolbox-shell__main { @@ -143,46 +174,59 @@ button.secondary { gap: 0.75rem; } -/* Make cards full-width on mobile */ -.card { +.pdf-toolbox-shell .toolbox-shell__main input, +.pdf-toolbox-shell .toolbox-shell__main select, +.pdf-toolbox-shell .toolbox-shell__main textarea { + border-color: var(--pdf-border-strong); + background: var(--pdf-surface); + color: var(--pdf-text); +} + +.pdf-toolbox-shell .toolbox-shell__main input::placeholder, +.pdf-toolbox-shell .toolbox-shell__main textarea::placeholder { + color: var(--pdf-muted-soft); +} + +.pdf-toolbox-shell .toolbox-shell__main a { + color: var(--toolbox-accent); +} + +.pdf-toolbox-shell .toolbox-shell__main a:hover { + color: var(--toolbox-accent-hover); +} + +.pdf-toolbox-shell .toolbox-shell__main button:disabled { + opacity: 0.58; +} + +.pdf-toolbox-shell .toolbox-shell__main button:focus-visible, +.pdf-toolbox-shell .toolbox-shell__main a:focus-visible, +.pdf-toolbox-shell .toolbox-shell__main input:focus-visible, +.pdf-toolbox-shell .toolbox-shell__main select:focus-visible { + outline: 3px solid color-mix(in srgb, var(--toolbox-focus) 72%, transparent); + outline-offset: 2px; +} + +.pdf-toolbox-shell .toolbox-shell__main .card { width: 100%; } -.download-link { +.pdf-toolbox-shell .toolbox-shell__main .download-link { display: inline-block; margin: 0.15rem 0; font-size: 0.85rem; } -.card hr { +.pdf-toolbox-shell .toolbox-shell__main .card hr { border: none; - border-top: 1px solid #e5e7eb; -} - -.app-help-button { - border: 1px solid var(--toolbox-border); - border-radius: 999px; - background: transparent; - color: var(--toolbox-accent); - padding: 0.35rem 0.65rem; - font-size: 0.85rem; - font-weight: 500; - cursor: pointer; - line-height: 1; -} - -.app-help-button:hover, -.app-help-button:focus-visible { - background: color-mix(in srgb, var(--toolbox-accent) 10%, transparent); - outline: 2px solid var(--toolbox-accent); - outline-offset: 2px; + border-top: 1px solid var(--pdf-border); } .help-dialog-backdrop { position: fixed; inset: 0; z-index: 80; - background: rgba(15, 23, 42, 0.65); + background: var(--pdf-overlay); display: flex; align-items: center; justify-content: center; @@ -193,9 +237,9 @@ button.secondary { width: min(920px, 100%); max-height: min(88vh, 760px); overflow: auto; - background: #ffffff; + background: var(--pdf-surface-raised); border-radius: 0.9rem; - box-shadow: 0 24px 60px rgba(15, 23, 42, 0.4); + box-shadow: var(--pdf-dialog-shadow); } .help-dialog-header { @@ -207,8 +251,8 @@ button.secondary { align-items: flex-start; gap: 1rem; padding: 1rem; - background: #ffffff; - border-bottom: 1px solid #e5e7eb; + background: var(--pdf-surface-raised); + border-bottom: 1px solid var(--pdf-border); } .help-dialog-header h2 { @@ -218,7 +262,7 @@ button.secondary { .help-dialog-header p { margin: 0.35rem 0 0; - color: #4b5563; + color: var(--pdf-muted); font-size: 0.9rem; line-height: 1.45; } @@ -229,8 +273,8 @@ button.secondary { border-radius: 999px; width: 2rem; height: 2rem; - background: #e5e7eb; - color: #111827; + background: var(--pdf-control); + color: var(--pdf-control-text); cursor: pointer; font-size: 1.25rem; line-height: 1; @@ -244,10 +288,10 @@ button.secondary { } .help-section { - border: 1px solid #e5e7eb; + border: 1px solid var(--pdf-border); border-radius: 0.75rem; padding: 0.9rem; - background: #f9fafb; + background: var(--pdf-surface-soft); } .help-section h3 { @@ -263,8 +307,8 @@ button.secondary { .help-step { border-radius: 0.65rem; - background: #ffffff; - border: 1px solid #e5e7eb; + background: var(--pdf-surface); + border: 1px solid var(--pdf-border); padding: 0.75rem; } @@ -276,7 +320,7 @@ button.secondary { .help-step p, .help-note { margin: 0.35rem 0 0; - color: #4b5563; + color: var(--pdf-muted); font-size: 0.85rem; line-height: 1.45; } @@ -292,18 +336,18 @@ button.secondary { display: inline-flex; justify-content: center; border-radius: 0.4rem; - border: 1px solid #d1d5db; - background: #ffffff; + border: 1px solid var(--pdf-border-strong); + background: var(--pdf-surface); padding: 0.2rem 0.45rem; font-size: 0.78rem; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - color: #111827; - box-shadow: inset 0 -1px 0 #d1d5db; + color: var(--pdf-text); + box-shadow: inset 0 -1px 0 var(--pdf-border-strong); white-space: nowrap; } .shortcut-grid span { - color: #4b5563; + color: var(--pdf-muted); font-size: 0.85rem; line-height: 1.35; } @@ -320,8 +364,8 @@ button.secondary { } .help-concepts dl > div { - background: #ffffff; - border: 1px solid #e5e7eb; + background: var(--pdf-surface); + border: 1px solid var(--pdf-border); border-radius: 0.65rem; padding: 0.75rem; } @@ -334,7 +378,7 @@ button.secondary { .help-concepts dd { margin: 0; - color: #4b5563; + color: var(--pdf-muted); font-size: 0.82rem; line-height: 1.4; } @@ -358,16 +402,16 @@ button.secondary { grid-template-columns: auto 1fr auto; align-items: center; gap: 0.75rem; - border: 1px solid #bfdbfe; + border: 1px solid var(--pdf-info-border); border-radius: 0.75rem; - background: rgba(255, 255, 255, 0.8); + background: color-mix(in srgb, var(--pdf-surface) 86%, transparent); padding: 0.6rem; } .merge-queue-order { border-radius: 999px; - background: #dbeafe; - color: #1e3a8a; + background: var(--pdf-info-surface); + color: var(--pdf-info-text); font-size: 0.8rem; font-weight: 700; padding: 0.2rem 0.5rem; @@ -386,7 +430,7 @@ button.secondary { } .merge-queue-details span { - color: #4b5563; + color: var(--pdf-muted); font-size: 0.8rem; } @@ -418,10 +462,10 @@ button.secondary { .merge-warning { margin: 0.75rem 0 0; - border: 1px solid #fed7aa; + border: 1px solid var(--pdf-warning-border); border-radius: 0.5rem; - background: #fff7ed; - color: #9a3412; + background: var(--pdf-warning-surface); + color: var(--pdf-warning-text); padding: 0.55rem 0.65rem; font-size: 0.85rem; } diff --git a/src/toolboxApp.definition.json b/src/toolboxApp.definition.json index 86e3e27..647e0d2 100644 --- a/src/toolboxApp.definition.json +++ b/src/toolboxApp.definition.json @@ -6,13 +6,6 @@ "icon": "./favicon.svg", "categories": ["documents", "pdf"], "tags": ["merge", "split", "rotate", "reorder"], - "actions": [ - { - "id": "source", - "label": "Source", - "url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4" - } - ], "integration": { "contextVersion": 1, "launchModes": ["navigate", "new-tab"], diff --git a/src/toolboxApp.test.ts b/src/toolboxApp.test.ts index 67be793..12a1a45 100644 --- a/src/toolboxApp.test.ts +++ b/src/toolboxApp.test.ts @@ -7,4 +7,11 @@ describe('toolbox app definition', () => { it('matches the generated public manifest', () => { expect(toolboxApp).toEqual(parseToolboxApp(generatedManifest)); }); + + it('uses repository metadata for the shared source control', () => { + expect(toolboxApp.source?.repository).toBe( + 'https://git.add-ideas.de/zemion/pdf-tools' + ); + expect(toolboxApp.actions).toBeUndefined(); + }); }); diff --git a/src/version.ts b/src/version.ts index 0416fa1..47eec42 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const APP_VERSION = '0.3.4'; +export const APP_VERSION = '0.4.0';