feat: integrate PDF Tools with toolbox portal
This commit is contained in:
21
src/App.tsx
21
src/App.tsx
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import Layout from './components/Layout';
|
||||
import { AppShell } from '@add-ideas/toolbox-shell-react';
|
||||
import FileLoader from './components/FileLoader';
|
||||
import ReorderPanel from './components/ReorderPanel';
|
||||
import ActionsPanel from './components/ActionsPanel';
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
createSplitResultsZip,
|
||||
createSplitZipFilename,
|
||||
} from './pdf/pdfZipService';
|
||||
import { toolboxApp } from './toolboxApp';
|
||||
import {
|
||||
createSelectionPdfName,
|
||||
createSelectionWorkspaceName,
|
||||
@@ -1139,7 +1140,21 @@ const App: React.FC = () => {
|
||||
previewVisualIndex >= 0 && previewVisualIndex < pages.length - 1;
|
||||
|
||||
return (
|
||||
<Layout onOpenHelp={() => setHelpOpen(true)}>
|
||||
<AppShell
|
||||
app={toolboxApp}
|
||||
className="pdf-toolbox-shell"
|
||||
appActions={
|
||||
<button
|
||||
type="button"
|
||||
className="app-help-button"
|
||||
onClick={() => setHelpOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
title="Open help and keyboard shortcuts (F1 or ?)"
|
||||
>
|
||||
Help <span aria-hidden="true">?</span>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<FileLoader pdf={pdf} onFilesLoaded={handleFilesLoaded} />
|
||||
|
||||
{mergeQueueOpen && mergeQueueItems.length > 0 && (
|
||||
@@ -1252,7 +1267,7 @@ const App: React.FC = () => {
|
||||
</ActionDialog>
|
||||
|
||||
<HelpDialog open={helpOpen} onClose={() => setHelpOpen(false)} />
|
||||
</Layout>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { APP_VERSION } from '../version';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
onOpenHelp?: () => void;
|
||||
}
|
||||
|
||||
const Layout: React.FC<LayoutProps> = ({ children, onOpenHelp }) => {
|
||||
return (
|
||||
<div className="app-root">
|
||||
<header className="app-header">
|
||||
<div className="app-header-content">
|
||||
<div className="app-header-title">
|
||||
<span className="app-logo">📄</span>
|
||||
<div>
|
||||
<h1>PDF Workbench</h1>
|
||||
<small>All in your browser</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-header-actions">
|
||||
{onOpenHelp && (
|
||||
<button
|
||||
type="button"
|
||||
className="app-help-button"
|
||||
onClick={onOpenHelp}
|
||||
aria-haspopup="dialog"
|
||||
title="Open help and keyboard shortcuts (F1 or ?)"
|
||||
>
|
||||
Help <span aria-hidden="true">?</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="app-version" title={`Version ${APP_VERSION}`}>
|
||||
v{APP_VERSION}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="app-main">{children}</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import '@add-ideas/toolbox-shell-react/styles.css';
|
||||
import './styles.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
|
||||
@@ -124,45 +124,20 @@ button.secondary {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.app-root {
|
||||
min-height: 100vh;
|
||||
background-color: #f3f4f6;
|
||||
.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;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: #111827;
|
||||
color: #e5e7eb;
|
||||
padding: 0.6rem 1rem;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.4);
|
||||
}
|
||||
|
||||
.app-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.app-header h1 {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app-header small {
|
||||
color: #9ca3af;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.app-main {
|
||||
.pdf-toolbox-shell .toolbox-shell__main {
|
||||
padding: 0.75rem;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
@@ -184,36 +159,11 @@ button.secondary {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.app-header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.app-version {
|
||||
flex: 0 0 auto;
|
||||
border-radius: 999px;
|
||||
background: #374151;
|
||||
color: #d1d5db;
|
||||
padding: 0.4rem 0.7rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.app-help-button {
|
||||
border: 1px solid #4b5563;
|
||||
border: 1px solid var(--toolbox-border);
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
color: #e5e7eb;
|
||||
color: var(--toolbox-accent);
|
||||
padding: 0.35rem 0.65rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
@@ -223,8 +173,9 @@ button.secondary {
|
||||
|
||||
.app-help-button:hover,
|
||||
.app-help-button:focus-visible {
|
||||
background: #374151;
|
||||
outline: none;
|
||||
background: color-mix(in srgb, var(--toolbox-accent) 10%, transparent);
|
||||
outline: 2px solid var(--toolbox-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.help-dialog-backdrop {
|
||||
@@ -393,10 +344,6 @@ button.secondary {
|
||||
.help-concepts dl {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.app-header-content {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.merge-queue-list {
|
||||
|
||||
36
src/toolboxApp.definition.json
Normal file
36
src/toolboxApp.definition.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"id": "de.add-ideas.pdf-tools",
|
||||
"name": "PDF Workbench",
|
||||
"description": "Page-level PDF operations performed locally in the browser.",
|
||||
"entry": "./",
|
||||
"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"],
|
||||
"embedding": "unsupported"
|
||||
},
|
||||
"requirements": {
|
||||
"secureContext": true,
|
||||
"workers": true,
|
||||
"indexedDb": true,
|
||||
"crossOriginIsolated": false
|
||||
},
|
||||
"privacy": {
|
||||
"processing": "local",
|
||||
"fileUploads": false,
|
||||
"telemetry": false
|
||||
},
|
||||
"source": {
|
||||
"repository": "https://git.add-ideas.de/zemion/pdf-tools",
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
||||
}
|
||||
10
src/toolboxApp.test.ts
Normal file
10
src/toolboxApp.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseToolboxApp } from '@add-ideas/toolbox-contract';
|
||||
import generatedManifest from '../public/toolbox-app.json';
|
||||
import { toolboxApp } from './toolboxApp';
|
||||
|
||||
describe('toolbox app definition', () => {
|
||||
it('matches the generated public manifest', () => {
|
||||
expect(toolboxApp).toEqual(parseToolboxApp(generatedManifest));
|
||||
});
|
||||
});
|
||||
9
src/toolboxApp.ts
Normal file
9
src/toolboxApp.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { parseToolboxApp } from '@add-ideas/toolbox-contract';
|
||||
import definition from './toolboxApp.definition.json';
|
||||
import { APP_VERSION } from './version';
|
||||
|
||||
export const toolboxApp = parseToolboxApp({
|
||||
schemaVersion: 1,
|
||||
...definition,
|
||||
version: APP_VERSION,
|
||||
});
|
||||
Reference in New Issue
Block a user