@pallasoft/ps-file-components 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ps-file-search-bar.d.ts +10 -0
- package/dist/components/ps-file-search-bar.js +25 -0
- package/dist/components/ps-file-viewer-content.js +9 -0
- package/dist/components/ps-file-viewer-pane.d.ts +1 -1
- package/dist/components/ps-file-viewer-pane.js +26 -2
- package/dist/components/ps-file-viewer.js +11 -23
- package/dist/components/ps-html-document.d.ts +8 -0
- package/dist/components/ps-html-document.js +23 -0
- package/dist/components/ps-html-viewer.d.ts +2 -0
- package/dist/components/ps-html-viewer.js +17 -0
- package/dist/components/ps-image-text-highlights.d.ts +9 -0
- package/dist/components/ps-image-text-highlights.js +31 -0
- package/dist/components/ps-image-viewer.d.ts +1 -1
- package/dist/components/ps-image-viewer.js +11 -2
- package/dist/components/ps-pdf-page.js +1 -1
- package/dist/components/ps-pdf-viewer.js +6 -2
- package/dist/components/ps-sheet-viewer.d.ts +2 -0
- package/dist/components/ps-sheet-viewer.js +26 -0
- package/dist/components/ps-table-view.d.ts +7 -0
- package/dist/components/ps-table-view.js +17 -0
- package/dist/components/ps-text-viewer.js +7 -14
- package/dist/components/ps-word-viewer.d.ts +2 -0
- package/dist/components/ps-word-viewer.js +17 -0
- package/dist/constants/ps-file-kind-icons.js +3 -0
- package/dist/constants/ps-file-kind.d.ts +3 -0
- package/dist/constants/ps-file-kind.js +3 -0
- package/dist/constants/ps-file-type-config.d.ts +6 -1
- package/dist/constants/ps-file-type-config.js +16 -2
- package/dist/constants/ps-file-viewer-config.d.ts +1 -0
- package/dist/constants/ps-file-viewer-config.js +1 -0
- package/dist/constants/ps-file-viewer-kinds.d.ts +2 -2
- package/dist/constants/ps-file-viewer-kinds.js +17 -2
- package/dist/constants/ps-file-viewer-labels.d.ts +3 -0
- package/dist/constants/ps-file-viewer-labels.js +3 -0
- package/dist/constants/ps-html-sanitize-config.d.ts +8 -0
- package/dist/constants/ps-html-sanitize-config.js +26 -0
- package/dist/esm/components/ps-file-search-bar.js +21 -0
- package/dist/esm/components/ps-file-viewer-content.js +9 -0
- package/dist/esm/components/ps-file-viewer-pane.js +27 -3
- package/dist/esm/components/ps-file-viewer.js +13 -25
- package/dist/esm/components/ps-html-document.js +19 -0
- package/dist/esm/components/ps-html-viewer.js +13 -0
- package/dist/esm/components/ps-image-text-highlights.js +27 -0
- package/dist/esm/components/ps-image-viewer.js +13 -4
- package/dist/esm/components/ps-pdf-page.js +1 -1
- package/dist/esm/components/ps-pdf-viewer.js +6 -2
- package/dist/esm/components/ps-sheet-viewer.js +22 -0
- package/dist/esm/components/ps-table-view.js +13 -0
- package/dist/esm/components/ps-text-viewer.js +7 -14
- package/dist/esm/components/ps-word-viewer.js +13 -0
- package/dist/esm/constants/ps-file-kind-icons.js +3 -0
- package/dist/esm/constants/ps-file-kind.js +3 -0
- package/dist/esm/constants/ps-file-type-config.js +16 -2
- package/dist/esm/constants/ps-file-viewer-config.js +1 -0
- package/dist/esm/constants/ps-file-viewer-kinds.js +17 -2
- package/dist/esm/constants/ps-file-viewer-labels.js +3 -0
- package/dist/esm/constants/ps-html-sanitize-config.js +23 -0
- package/dist/esm/hooks/use-ps-file-search.js +37 -0
- package/dist/esm/hooks/use-ps-file-text.js +4 -19
- package/dist/esm/hooks/use-ps-find-shortcut.js +23 -0
- package/dist/esm/hooks/use-ps-loaded-file.js +19 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/models/ps-file-search-state.js +1 -0
- package/dist/esm/models/ps-html-highlight-result.js +1 -0
- package/dist/esm/models/ps-html-text-node.js +1 -0
- package/dist/esm/models/ps-image-match.js +1 -0
- package/dist/esm/models/ps-image-text-box.js +1 -0
- package/dist/esm/models/ps-image-text-layer.js +1 -0
- package/dist/esm/models/ps-image-text-word.js +1 -0
- package/dist/esm/models/ps-item-match-part.js +1 -0
- package/dist/esm/models/ps-item-match.js +1 -0
- package/dist/esm/models/ps-loaded-file-state.js +1 -0
- package/dist/esm/models/ps-normalized-text.js +1 -0
- package/dist/esm/models/ps-pdf-text-item.js +1 -0
- package/dist/esm/models/ps-pdf-viewport-like.js +1 -0
- package/dist/esm/models/ps-search-item.js +1 -0
- package/dist/esm/models/ps-sheet-data.js +1 -0
- package/dist/esm/services/ps-file-fetch-service.js +14 -0
- package/dist/esm/services/ps-file-search-service.js +15 -0
- package/dist/esm/services/ps-file-type-service.js +12 -1
- package/dist/esm/services/ps-html-file-service.js +7 -0
- package/dist/esm/services/ps-html-service.js +83 -0
- package/dist/esm/services/ps-image-text-service.js +28 -0
- package/dist/esm/services/ps-label-service.js +5 -0
- package/dist/esm/services/ps-pdf-service.js +39 -16
- package/dist/esm/services/ps-sheet-service.js +23 -0
- package/dist/esm/services/ps-text-search-service.js +70 -5
- package/dist/esm/services/ps-word-service.js +15 -0
- package/dist/hooks/use-ps-file-search.d.ts +2 -0
- package/dist/hooks/use-ps-file-search.js +41 -0
- package/dist/hooks/use-ps-file-text.js +4 -19
- package/dist/hooks/use-ps-find-shortcut.d.ts +2 -0
- package/dist/hooks/use-ps-find-shortcut.js +27 -0
- package/dist/hooks/use-ps-loaded-file.d.ts +2 -0
- package/dist/hooks/use-ps-loaded-file.js +23 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +33 -0
- package/dist/models/ps-file-search-state.d.ts +13 -0
- package/dist/models/ps-file-search-state.js +2 -0
- package/dist/models/ps-file-viewer-pane-props.d.ts +3 -1
- package/dist/models/ps-html-highlight-result.d.ts +4 -0
- package/dist/models/ps-html-highlight-result.js +2 -0
- package/dist/models/ps-html-text-node.d.ts +4 -0
- package/dist/models/ps-html-text-node.js +2 -0
- package/dist/models/ps-image-match.d.ts +7 -0
- package/dist/models/ps-image-match.js +2 -0
- package/dist/models/ps-image-text-box.d.ts +6 -0
- package/dist/models/ps-image-text-box.js +2 -0
- package/dist/models/ps-image-text-layer.d.ts +6 -0
- package/dist/models/ps-image-text-layer.js +2 -0
- package/dist/models/ps-image-text-word.d.ts +5 -0
- package/dist/models/ps-image-text-word.js +2 -0
- package/dist/models/ps-item-match-part.d.ts +5 -0
- package/dist/models/ps-item-match-part.js +2 -0
- package/dist/models/ps-item-match.d.ts +4 -0
- package/dist/models/ps-item-match.js +2 -0
- package/dist/models/ps-loaded-file-state.d.ts +4 -0
- package/dist/models/ps-loaded-file-state.js +2 -0
- package/dist/models/ps-normalized-text.d.ts +5 -0
- package/dist/models/ps-normalized-text.js +2 -0
- package/dist/models/ps-pdf-match.d.ts +1 -0
- package/dist/models/ps-pdf-text-item.d.ts +7 -0
- package/dist/models/ps-pdf-text-item.js +2 -0
- package/dist/models/ps-pdf-viewport-like.d.ts +5 -0
- package/dist/models/ps-pdf-viewport-like.js +2 -0
- package/dist/models/ps-search-item.d.ts +4 -0
- package/dist/models/ps-search-item.js +2 -0
- package/dist/models/ps-sheet-data.d.ts +4 -0
- package/dist/models/ps-sheet-data.js +2 -0
- package/dist/models/ps-viewer-file.d.ts +2 -0
- package/dist/services/ps-file-fetch-service.d.ts +5 -0
- package/dist/services/ps-file-fetch-service.js +18 -0
- package/dist/services/ps-file-search-service.d.ts +5 -0
- package/dist/services/ps-file-search-service.js +19 -0
- package/dist/services/ps-file-type-service.js +12 -1
- package/dist/services/ps-html-file-service.d.ts +3 -0
- package/dist/services/ps-html-file-service.js +11 -0
- package/dist/services/ps-html-service.d.ts +7 -0
- package/dist/services/ps-html-service.js +120 -0
- package/dist/services/ps-image-text-service.d.ts +5 -0
- package/dist/services/ps-image-text-service.js +32 -0
- package/dist/services/ps-label-service.d.ts +3 -0
- package/dist/services/ps-label-service.js +9 -0
- package/dist/services/ps-pdf-service.d.ts +3 -0
- package/dist/services/ps-pdf-service.js +39 -16
- package/dist/services/ps-sheet-service.d.ts +5 -0
- package/dist/services/ps-sheet-service.js +60 -0
- package/dist/services/ps-text-search-service.d.ts +7 -0
- package/dist/services/ps-text-search-service.js +70 -5
- package/dist/services/ps-word-service.d.ts +3 -0
- package/dist/services/ps-word-service.js +52 -0
- package/package.json +5 -2
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { PsFileKind } from './ps-file-kind';
|
|
2
2
|
export declare const PsFileTypeConfig: {
|
|
3
|
-
readonly csvTypes: readonly ["text/csv", "application/csv"
|
|
3
|
+
readonly csvTypes: readonly ["text/csv", "application/csv"];
|
|
4
|
+
readonly excelLegacyType: "application/vnd.ms-excel";
|
|
4
5
|
readonly extensionKinds: Record<string, PsFileKind>;
|
|
6
|
+
readonly htmlTypes: readonly ["text/html", "application/xhtml+xml"];
|
|
7
|
+
readonly sheetTypes: readonly ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"];
|
|
5
8
|
readonly textTypes: readonly ["application/json", "application/xml"];
|
|
9
|
+
readonly unsupportedImageTypes: readonly ["image/heic", "image/heif", "image/heic-sequence", "image/heif-sequence"];
|
|
10
|
+
readonly wordTypes: readonly ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"];
|
|
6
11
|
};
|
|
@@ -3,27 +3,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PsFileTypeConfig = void 0;
|
|
4
4
|
const ps_file_kind_1 = require("./ps-file-kind");
|
|
5
5
|
exports.PsFileTypeConfig = {
|
|
6
|
-
csvTypes: ['text/csv', 'application/csv'
|
|
6
|
+
csvTypes: ['text/csv', 'application/csv'],
|
|
7
|
+
excelLegacyType: 'application/vnd.ms-excel',
|
|
7
8
|
extensionKinds: {
|
|
9
|
+
aac: ps_file_kind_1.PsFileKind.audio,
|
|
10
|
+
avif: ps_file_kind_1.PsFileKind.image,
|
|
11
|
+
bmp: ps_file_kind_1.PsFileKind.image,
|
|
8
12
|
csv: ps_file_kind_1.PsFileKind.csv,
|
|
13
|
+
docx: ps_file_kind_1.PsFileKind.word,
|
|
9
14
|
gif: ps_file_kind_1.PsFileKind.image,
|
|
10
|
-
|
|
15
|
+
htm: ps_file_kind_1.PsFileKind.html,
|
|
16
|
+
html: ps_file_kind_1.PsFileKind.html,
|
|
11
17
|
jpeg: ps_file_kind_1.PsFileKind.image,
|
|
12
18
|
jpg: ps_file_kind_1.PsFileKind.image,
|
|
13
19
|
json: ps_file_kind_1.PsFileKind.text,
|
|
14
20
|
log: ps_file_kind_1.PsFileKind.text,
|
|
21
|
+
m4a: ps_file_kind_1.PsFileKind.audio,
|
|
15
22
|
md: ps_file_kind_1.PsFileKind.text,
|
|
16
23
|
mov: ps_file_kind_1.PsFileKind.video,
|
|
17
24
|
mp3: ps_file_kind_1.PsFileKind.audio,
|
|
18
25
|
mp4: ps_file_kind_1.PsFileKind.video,
|
|
26
|
+
ogg: ps_file_kind_1.PsFileKind.audio,
|
|
19
27
|
pdf: ps_file_kind_1.PsFileKind.pdf,
|
|
20
28
|
png: ps_file_kind_1.PsFileKind.image,
|
|
21
29
|
svg: ps_file_kind_1.PsFileKind.image,
|
|
30
|
+
tsv: ps_file_kind_1.PsFileKind.text,
|
|
22
31
|
txt: ps_file_kind_1.PsFileKind.text,
|
|
23
32
|
wav: ps_file_kind_1.PsFileKind.audio,
|
|
24
33
|
webm: ps_file_kind_1.PsFileKind.video,
|
|
25
34
|
webp: ps_file_kind_1.PsFileKind.image,
|
|
35
|
+
xlsx: ps_file_kind_1.PsFileKind.sheet,
|
|
26
36
|
xml: ps_file_kind_1.PsFileKind.text,
|
|
27
37
|
},
|
|
38
|
+
htmlTypes: ['text/html', 'application/xhtml+xml'],
|
|
39
|
+
sheetTypes: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
28
40
|
textTypes: ['application/json', 'application/xml'],
|
|
41
|
+
unsupportedImageTypes: ['image/heic', 'image/heif', 'image/heic-sequence', 'image/heif-sequence'],
|
|
42
|
+
wordTypes: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
|
29
43
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const PsFileViewerKinds: {
|
|
2
2
|
readonly rotatable: readonly ["image", "pdf"];
|
|
3
|
-
readonly searchable: readonly ["csv", "pdf", "text"];
|
|
4
|
-
readonly zoomable: readonly ["csv", "image", "pdf", "text"];
|
|
3
|
+
readonly searchable: readonly ["csv", "html", "pdf", "sheet", "text", "word"];
|
|
4
|
+
readonly zoomable: readonly ["csv", "html", "image", "pdf", "sheet", "text", "word"];
|
|
5
5
|
};
|
|
@@ -4,6 +4,21 @@ exports.PsFileViewerKinds = void 0;
|
|
|
4
4
|
const ps_file_kind_1 = require("./ps-file-kind");
|
|
5
5
|
exports.PsFileViewerKinds = {
|
|
6
6
|
rotatable: [ps_file_kind_1.PsFileKind.image, ps_file_kind_1.PsFileKind.pdf],
|
|
7
|
-
searchable: [
|
|
8
|
-
|
|
7
|
+
searchable: [
|
|
8
|
+
ps_file_kind_1.PsFileKind.csv,
|
|
9
|
+
ps_file_kind_1.PsFileKind.html,
|
|
10
|
+
ps_file_kind_1.PsFileKind.pdf,
|
|
11
|
+
ps_file_kind_1.PsFileKind.sheet,
|
|
12
|
+
ps_file_kind_1.PsFileKind.text,
|
|
13
|
+
ps_file_kind_1.PsFileKind.word,
|
|
14
|
+
],
|
|
15
|
+
zoomable: [
|
|
16
|
+
ps_file_kind_1.PsFileKind.csv,
|
|
17
|
+
ps_file_kind_1.PsFileKind.html,
|
|
18
|
+
ps_file_kind_1.PsFileKind.image,
|
|
19
|
+
ps_file_kind_1.PsFileKind.pdf,
|
|
20
|
+
ps_file_kind_1.PsFileKind.sheet,
|
|
21
|
+
ps_file_kind_1.PsFileKind.text,
|
|
22
|
+
ps_file_kind_1.PsFileKind.word,
|
|
23
|
+
],
|
|
9
24
|
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export declare const PsFileViewerLabels: {
|
|
2
2
|
readonly close: "Close";
|
|
3
|
+
readonly closeSearch: "Close search";
|
|
3
4
|
readonly delete: "Delete";
|
|
4
5
|
readonly deleteConfirm: "Delete file";
|
|
5
6
|
readonly deleteConfirmBody: "will be deleted. You cannot undo this.";
|
|
6
7
|
readonly deleteCancel: "Cancel";
|
|
7
8
|
readonly download: "Download";
|
|
8
9
|
readonly loadFailed: "This file could not be opened.";
|
|
10
|
+
readonly matchCount: "{current} of {total}";
|
|
9
11
|
readonly next: "Next file";
|
|
10
12
|
readonly nextMatch: "Next match";
|
|
11
13
|
readonly noMatches: "No matches";
|
|
@@ -14,6 +16,7 @@ export declare const PsFileViewerLabels: {
|
|
|
14
16
|
readonly previousMatch: "Previous match";
|
|
15
17
|
readonly rotate: "Rotate";
|
|
16
18
|
readonly search: "Search in file";
|
|
19
|
+
readonly sheetTruncated: "Showing the first {count} rows";
|
|
17
20
|
readonly unsupported: "This file type cannot be previewed here. Download it to open it.";
|
|
18
21
|
readonly zoomIn: "Zoom in";
|
|
19
22
|
readonly zoomOut: "Zoom out";
|
|
@@ -3,12 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PsFileViewerLabels = void 0;
|
|
4
4
|
exports.PsFileViewerLabels = {
|
|
5
5
|
close: 'Close',
|
|
6
|
+
closeSearch: 'Close search',
|
|
6
7
|
delete: 'Delete',
|
|
7
8
|
deleteConfirm: 'Delete file',
|
|
8
9
|
deleteConfirmBody: 'will be deleted. You cannot undo this.',
|
|
9
10
|
deleteCancel: 'Cancel',
|
|
10
11
|
download: 'Download',
|
|
11
12
|
loadFailed: 'This file could not be opened.',
|
|
13
|
+
matchCount: '{current} of {total}',
|
|
12
14
|
next: 'Next file',
|
|
13
15
|
nextMatch: 'Next match',
|
|
14
16
|
noMatches: 'No matches',
|
|
@@ -17,6 +19,7 @@ exports.PsFileViewerLabels = {
|
|
|
17
19
|
previousMatch: 'Previous match',
|
|
18
20
|
rotate: 'Rotate',
|
|
19
21
|
search: 'Search in file',
|
|
22
|
+
sheetTruncated: 'Showing the first {count} rows',
|
|
20
23
|
unsupported: 'This file type cannot be previewed here. Download it to open it.',
|
|
21
24
|
zoomIn: 'Zoom in',
|
|
22
25
|
zoomOut: 'Zoom out',
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const PsHtmlSanitizeConfig: {
|
|
2
|
+
readonly anchorRel: "noopener noreferrer";
|
|
3
|
+
readonly anchorTarget: "_blank";
|
|
4
|
+
readonly blockSelector: "p,li,td,th,tr,h1,h2,h3,h4,h5,h6,div,pre,blockquote,section,article,caption";
|
|
5
|
+
readonly forbiddenAttributes: readonly ["style", "srcset"];
|
|
6
|
+
readonly forbiddenTags: readonly ["style", "form", "input", "button", "textarea", "select", "iframe", "frame", "object", "embed", "link", "meta", "base", "script"];
|
|
7
|
+
readonly mediaTags: readonly ["img", "picture", "source", "video", "audio", "track"];
|
|
8
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsHtmlSanitizeConfig = void 0;
|
|
4
|
+
exports.PsHtmlSanitizeConfig = {
|
|
5
|
+
anchorRel: 'noopener noreferrer',
|
|
6
|
+
anchorTarget: '_blank',
|
|
7
|
+
blockSelector: 'p,li,td,th,tr,h1,h2,h3,h4,h5,h6,div,pre,blockquote,section,article,caption',
|
|
8
|
+
forbiddenAttributes: ['style', 'srcset'],
|
|
9
|
+
forbiddenTags: [
|
|
10
|
+
'style',
|
|
11
|
+
'form',
|
|
12
|
+
'input',
|
|
13
|
+
'button',
|
|
14
|
+
'textarea',
|
|
15
|
+
'select',
|
|
16
|
+
'iframe',
|
|
17
|
+
'frame',
|
|
18
|
+
'object',
|
|
19
|
+
'embed',
|
|
20
|
+
'link',
|
|
21
|
+
'meta',
|
|
22
|
+
'base',
|
|
23
|
+
'script',
|
|
24
|
+
],
|
|
25
|
+
mediaTags: ['img', 'picture', 'source', 'video', 'audio', 'track'],
|
|
26
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
import { PsLabelService } from '../services/ps-label-service';
|
|
5
|
+
import { PsFileViewerButton } from './ps-file-viewer-button';
|
|
6
|
+
export const PsFileSearchBar = ({ className, labels, onClose, state }) => (_jsxs("div", { className: PallasoftClassNameService.merge('flex items-center gap-1 rounded-ps-control bg-white/10 px-3', className), role: 'search', children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'h-4 w-4 shrink-0 text-white/60', iconKey: PsIconKey.Search }), _jsx("input", { "aria-label": labels.search, className: 'h-ps-control-compact min-w-0 flex-1 bg-transparent text-sm text-white placeholder:text-white/45 focus:outline-none', onChange: (event) => state.setSearch(event.target.value), onKeyDown: (event) => {
|
|
7
|
+
if (event.key === 'Enter') {
|
|
8
|
+
event.preventDefault();
|
|
9
|
+
state.step(event.shiftKey ? -1 : 1);
|
|
10
|
+
}
|
|
11
|
+
else if (event.key === 'Escape' && onClose !== undefined) {
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
event.stopPropagation();
|
|
14
|
+
onClose();
|
|
15
|
+
}
|
|
16
|
+
}, placeholder: labels.search, ref: state.inputRef, type: 'search', value: state.search }), state.query.trim().length > 0 ? (_jsx("span", { "aria-live": 'polite', className: 'shrink-0 whitespace-nowrap text-xs text-white/60', children: state.matchCount === 0
|
|
17
|
+
? labels.noMatches
|
|
18
|
+
: PsLabelService.fill(labels.matchCount, {
|
|
19
|
+
current: state.activeMatch + 1,
|
|
20
|
+
total: state.matchCount,
|
|
21
|
+
}) })) : null, _jsx(PsFileViewerButton, { disabled: state.matchCount === 0, label: labels.previousMatch, onClick: () => state.step(-1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone -rotate-90', iconKey: PsIconKey.ChevronRight }) }), _jsx(PsFileViewerButton, { disabled: state.matchCount === 0, label: labels.nextMatch, onClick: () => state.step(1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone rotate-90', iconKey: PsIconKey.ChevronRight }) }), onClose === undefined ? null : (_jsx(PsFileViewerButton, { label: labels.closeSearch, onClick: onClose, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Close }) }))] }));
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { PsFileKind } from '../constants/ps-file-kind';
|
|
3
3
|
import { PsFileTypeService } from '../services/ps-file-type-service';
|
|
4
|
+
import { PsHtmlViewer } from './ps-html-viewer';
|
|
4
5
|
import { PsImageViewer } from './ps-image-viewer';
|
|
5
6
|
import { PsMediaViewer } from './ps-media-viewer';
|
|
6
7
|
import { PsPdfViewer } from './ps-pdf-viewer';
|
|
8
|
+
import { PsSheetViewer } from './ps-sheet-viewer';
|
|
7
9
|
import { PsTextViewer } from './ps-text-viewer';
|
|
8
10
|
import { PsUnsupportedViewer } from './ps-unsupported-viewer';
|
|
11
|
+
import { PsWordViewer } from './ps-word-viewer';
|
|
9
12
|
export const PsFileViewerContent = (props) => {
|
|
10
13
|
switch (PsFileTypeService.kindOf(props.file.contentType, props.file.name)) {
|
|
11
14
|
case PsFileKind.image:
|
|
@@ -15,6 +18,12 @@ export const PsFileViewerContent = (props) => {
|
|
|
15
18
|
case PsFileKind.csv:
|
|
16
19
|
case PsFileKind.text:
|
|
17
20
|
return _jsx(PsTextViewer, { ...props });
|
|
21
|
+
case PsFileKind.word:
|
|
22
|
+
return _jsx(PsWordViewer, { ...props });
|
|
23
|
+
case PsFileKind.sheet:
|
|
24
|
+
return _jsx(PsSheetViewer, { ...props });
|
|
25
|
+
case PsFileKind.html:
|
|
26
|
+
return _jsx(PsHtmlViewer, { ...props });
|
|
18
27
|
case PsFileKind.audio:
|
|
19
28
|
case PsFileKind.video:
|
|
20
29
|
return _jsx(PsMediaViewer, { ...props });
|
|
@@ -1,22 +1,46 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useCallback, useRef, useState } from 'react';
|
|
3
3
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
4
|
import { PsFileViewerConfig } from '../constants/ps-file-viewer-config';
|
|
5
5
|
import { PsFileViewerKinds } from '../constants/ps-file-viewer-kinds';
|
|
6
6
|
import { PsFileViewerLabels } from '../constants/ps-file-viewer-labels';
|
|
7
|
+
import { usePsFileSearch } from '../hooks/use-ps-file-search';
|
|
8
|
+
import { usePsFindShortcut } from '../hooks/use-ps-find-shortcut';
|
|
9
|
+
import { PsFileSearchService } from '../services/ps-file-search-service';
|
|
7
10
|
import { PsFileTypeService } from '../services/ps-file-type-service';
|
|
11
|
+
import { PsFileSearchBar } from './ps-file-search-bar';
|
|
8
12
|
import { PsFileViewerButton } from './ps-file-viewer-button';
|
|
9
13
|
import { PsFileViewerContent } from './ps-file-viewer-content';
|
|
10
|
-
export const PsFileViewerPane = ({ contentType, headerActions, id, labels
|
|
14
|
+
export const PsFileViewerPane = ({ contentType, headerActions, id, labels, name, pdfWorkerSrc, textLayer, url, }) => {
|
|
15
|
+
const text = { ...PsFileViewerLabels, ...labels };
|
|
11
16
|
const [zoom, setZoom] = useState(1);
|
|
12
17
|
const [rotation, setRotation] = useState(0);
|
|
18
|
+
const [isFindOpen, setIsFindOpen] = useState(false);
|
|
19
|
+
const root = useRef(null);
|
|
13
20
|
const kind = PsFileTypeService.kindOf(contentType, name);
|
|
14
21
|
const canZoom = PsFileViewerKinds.zoomable.includes(kind);
|
|
15
22
|
const canRotate = PsFileViewerKinds.rotatable.includes(kind);
|
|
23
|
+
const canSearch = PsFileSearchService.canSearch(kind, textLayer !== undefined);
|
|
16
24
|
const changeZoom = (step) => setZoom((current) => Math.min(PsFileViewerConfig.maxZoom, Math.max(PsFileViewerConfig.minZoom, current + step)));
|
|
17
25
|
const fileKey = id === undefined ? url : id;
|
|
26
|
+
const searchState = usePsFileSearch(fileKey);
|
|
27
|
+
const { focus, reset } = searchState;
|
|
28
|
+
const openFind = useCallback(() => {
|
|
29
|
+
setIsFindOpen(true);
|
|
30
|
+
requestAnimationFrame(focus);
|
|
31
|
+
}, [focus]);
|
|
32
|
+
const closeFind = useCallback(() => {
|
|
33
|
+
setIsFindOpen(false);
|
|
34
|
+
reset();
|
|
35
|
+
}, [reset]);
|
|
36
|
+
usePsFindShortcut(root, canSearch, openFind);
|
|
18
37
|
const rotate = () => setRotation((current) => (current + PsFileViewerConfig.rotateStep) % 360);
|
|
19
38
|
const onKeyDown = (event) => {
|
|
39
|
+
if (event.key === 'Escape' && isFindOpen) {
|
|
40
|
+
closeFind();
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
20
44
|
if (event.metaKey || event.ctrlKey || event.altKey)
|
|
21
45
|
return;
|
|
22
46
|
if (canZoom && (event.key === '+' || event.key === '='))
|
|
@@ -31,5 +55,5 @@ export const PsFileViewerPane = ({ contentType, headerActions, id, labels = PsFi
|
|
|
31
55
|
return;
|
|
32
56
|
event.preventDefault();
|
|
33
57
|
};
|
|
34
|
-
return (_jsxs("div", { className: 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden rounded-ps-panel bg-black/85', children: [_jsxs("div", { className: 'flex shrink-0 flex-wrap items-center justify-between gap-x-2 border-b border-white/10 px-2 py-1 text-white', children: [_jsx("p", { className: 'min-w-0 flex-1 basis-32 truncate px-1 text-xs text-white/70', title: name, children: name }), _jsxs("div", { className: 'flex items-center gap-0.5', role: 'toolbar', children: [canZoom ? (_jsxs(_Fragment, { children: [_jsx(PsFileViewerButton, { disabled: zoom <= PsFileViewerConfig.minZoom, label:
|
|
58
|
+
return (_jsxs("div", { className: 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden rounded-ps-panel bg-black/85', ref: root, children: [_jsxs("div", { className: 'flex shrink-0 flex-wrap items-center justify-between gap-x-2 border-b border-white/10 px-2 py-1 text-white', children: [_jsx("p", { className: 'min-w-0 flex-1 basis-32 truncate px-1 text-xs text-white/70', title: name, children: name }), _jsxs("div", { className: 'flex items-center gap-0.5', role: 'toolbar', children: [canSearch ? (_jsx(PsFileViewerButton, { label: text.search, onClick: isFindOpen ? closeFind : openFind, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Search }) })) : null, canZoom ? (_jsxs(_Fragment, { children: [_jsx(PsFileViewerButton, { disabled: zoom <= PsFileViewerConfig.minZoom, label: text.zoomOut, onClick: () => changeZoom(-PsFileViewerConfig.zoomStep), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.ZoomOut }) }), _jsxs(PsFileViewerButton, { className: 'w-14 text-xs', label: text.zoomReset, onClick: () => setZoom(1), children: [Math.round(zoom * 100), "%"] }), _jsx(PsFileViewerButton, { disabled: zoom >= PsFileViewerConfig.maxZoom, label: text.zoomIn, onClick: () => changeZoom(PsFileViewerConfig.zoomStep), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.ZoomIn }) })] })) : null, canRotate ? (_jsx(PsFileViewerButton, { label: text.rotate, onClick: rotate, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-4', iconKey: PsIconKey.Rotate }) })) : null, headerActions] })] }), canSearch && isFindOpen ? (_jsx("div", { className: 'shrink-0 border-b border-white/10 px-2 py-1', children: _jsx(PsFileSearchBar, { labels: text, onClose: closeFind, state: searchState }) })) : null, _jsx("div", { className: 'ps-focus-ring flex min-h-0 flex-1 p-2', onKeyDown: onKeyDown, tabIndex: 0, children: _jsx(PsFileViewerContent, { activeMatch: searchState.activeMatch, file: { contentType, id: fileKey, name, textLayer, url }, labels: text, onMatchCount: searchState.setMatchCount, pdfWorkerSrc: pdfWorkerSrc, query: canSearch && isFindOpen ? searchState.query : '', rotation: rotation, zoom: zoom }, fileKey) })] }));
|
|
35
59
|
};
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect,
|
|
3
|
-
import { PsLayer,
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { PsLayer, usePsExitTransition, usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
4
4
|
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
5
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
import { PsFileViewerConfig } from '../constants/ps-file-viewer-config';
|
|
8
8
|
import { PsFileViewerKinds } from '../constants/ps-file-viewer-kinds';
|
|
9
9
|
import { PsFileViewerLabels } from '../constants/ps-file-viewer-labels';
|
|
10
|
+
import { usePsFileSearch } from '../hooks/use-ps-file-search';
|
|
11
|
+
import { usePsFindShortcut } from '../hooks/use-ps-find-shortcut';
|
|
12
|
+
import { PsFileSearchService } from '../services/ps-file-search-service';
|
|
10
13
|
import { PsFileSizeService } from '../services/ps-file-size-service';
|
|
11
14
|
import { PsFileTypeService } from '../services/ps-file-type-service';
|
|
12
15
|
import { PsFileDeleteConfirm } from './ps-file-delete-confirm';
|
|
16
|
+
import { PsFileSearchBar } from './ps-file-search-bar';
|
|
13
17
|
import { PsFileViewerButton } from './ps-file-viewer-button';
|
|
14
18
|
import { PsFileViewerContent } from './ps-file-viewer-content';
|
|
15
19
|
export const PsFileViewer = ({ files, index, isOpen, labels, onClose, onDelete, onIndexChange, pdfWorkerSrc, }) => {
|
|
@@ -18,34 +22,24 @@ export const PsFileViewer = ({ files, index, isOpen, labels, onClose, onDelete,
|
|
|
18
22
|
const zIndex = usePsLayerZIndex(PallasoftZIndex.Modal);
|
|
19
23
|
const [zoom, setZoom] = useState(1);
|
|
20
24
|
const [rotation, setRotation] = useState(0);
|
|
21
|
-
const [search, setSearch] = useState('');
|
|
22
|
-
const [activeMatch, setActiveMatch] = useState(0);
|
|
23
|
-
const [matchCount, setMatchCount] = useState(0);
|
|
24
25
|
const [isConfirming, setIsConfirming] = useState(false);
|
|
25
26
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
26
|
-
const searchInput = useRef(null);
|
|
27
|
-
const query = usePsDebouncedValue(search, PsFileViewerConfig.searchDebounceMs);
|
|
28
27
|
const file = files.length === 0 ? null : files[Math.min(index, files.length - 1)];
|
|
29
28
|
const kind = file === null ? null : PsFileTypeService.kindOf(file.contentType, file.name);
|
|
30
|
-
const
|
|
29
|
+
const searchState = usePsFileSearch(file === null ? null : file.id);
|
|
30
|
+
const canSearch = kind !== null &&
|
|
31
|
+
file !== null &&
|
|
32
|
+
PsFileSearchService.canSearch(kind, file.textLayer !== undefined);
|
|
31
33
|
const canZoom = kind !== null && PsFileViewerKinds.zoomable.includes(kind);
|
|
32
34
|
const canRotate = kind !== null && PsFileViewerKinds.rotatable.includes(kind);
|
|
33
35
|
useEffect(() => {
|
|
34
36
|
setZoom(1);
|
|
35
37
|
setRotation(0);
|
|
36
|
-
setSearch('');
|
|
37
|
-
setActiveMatch(0);
|
|
38
|
-
setMatchCount(0);
|
|
39
38
|
}, [file === null ? null : file.id]);
|
|
40
|
-
useEffect(() => setActiveMatch(0), [query]);
|
|
41
39
|
const go = (step) => {
|
|
42
40
|
if (files.length > 1)
|
|
43
41
|
onIndexChange((index + step + files.length) % files.length);
|
|
44
42
|
};
|
|
45
|
-
const stepMatch = (step) => {
|
|
46
|
-
if (matchCount > 0)
|
|
47
|
-
setActiveMatch((current) => (current + step + matchCount) % matchCount);
|
|
48
|
-
};
|
|
49
43
|
const changeZoom = (step) => setZoom((current) => Math.min(PsFileViewerConfig.maxZoom, Math.max(PsFileViewerConfig.minZoom, current + step)));
|
|
50
44
|
useEffect(() => {
|
|
51
45
|
if (!isOpen)
|
|
@@ -54,10 +48,6 @@ export const PsFileViewer = ({ files, index, isOpen, labels, onClose, onDelete,
|
|
|
54
48
|
const isTyping = event.target instanceof HTMLInputElement;
|
|
55
49
|
if (event.key === 'Escape' && !isConfirming)
|
|
56
50
|
onClose();
|
|
57
|
-
else if ((event.metaKey || event.ctrlKey) && event.key === 'f' && canSearch) {
|
|
58
|
-
event.preventDefault();
|
|
59
|
-
searchInput.current?.focus();
|
|
60
|
-
}
|
|
61
51
|
else if (isTyping)
|
|
62
52
|
return;
|
|
63
53
|
else if (event.key === 'ArrowRight')
|
|
@@ -71,7 +61,8 @@ export const PsFileViewer = ({ files, index, isOpen, labels, onClose, onDelete,
|
|
|
71
61
|
};
|
|
72
62
|
window.addEventListener('keydown', onKey);
|
|
73
63
|
return () => window.removeEventListener('keydown', onKey);
|
|
74
|
-
}, [isOpen, isConfirming,
|
|
64
|
+
}, [isOpen, isConfirming, index, files.length]);
|
|
65
|
+
usePsFindShortcut(null, isOpen && canSearch, searchState.focus);
|
|
75
66
|
if (!shouldRender || file === null || typeof document === 'undefined')
|
|
76
67
|
return null;
|
|
77
68
|
const confirmDelete = async () => {
|
|
@@ -91,8 +82,5 @@ export const PsFileViewer = ({ files, index, isOpen, labels, onClose, onDelete,
|
|
|
91
82
|
file.sizeBytes === undefined ? null : PsFileSizeService.format(file.sizeBytes),
|
|
92
83
|
]
|
|
93
84
|
.filter((value) => value !== null)
|
|
94
|
-
.join(' · ') })] }) }), canSearch ? (
|
|
95
|
-
if (event.key === 'Enter')
|
|
96
|
-
stepMatch(event.shiftKey ? -1 : 1);
|
|
97
|
-
}, placeholder: text.search, ref: searchInput, type: 'search', value: search }), query.trim().length > 0 ? (_jsx("span", { className: 'shrink-0 text-xs text-white/60', children: matchCount === 0 ? text.noMatches : `${activeMatch + 1} / ${matchCount}` })) : null, _jsx(PsFileViewerButton, { disabled: matchCount === 0, label: text.previousMatch, onClick: () => stepMatch(-1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone -rotate-90', iconKey: PsIconKey.ChevronRight }) }), _jsx(PsFileViewerButton, { disabled: matchCount === 0, label: text.nextMatch, onClick: () => stepMatch(1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone rotate-90', iconKey: PsIconKey.ChevronRight }) })] })) : null, _jsxs("div", { className: 'flex items-center gap-0.5', children: [canZoom ? (_jsxs(_Fragment, { children: [_jsx(PsFileViewerButton, { label: text.zoomOut, onClick: () => changeZoom(-PsFileViewerConfig.zoomStep), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.ZoomOut }) }), _jsxs(PsFileViewerButton, { className: 'w-14 text-xs', label: text.zoomReset, onClick: () => setZoom(1), children: [Math.round(zoom * 100), "%"] }), _jsx(PsFileViewerButton, { label: text.zoomIn, onClick: () => changeZoom(PsFileViewerConfig.zoomStep), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.ZoomIn }) })] })) : null, canRotate ? (_jsx(PsFileViewerButton, { label: text.rotate, onClick: () => setRotation((current) => (current + PsFileViewerConfig.rotateStep) % 360), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Rotate }) })) : null, _jsx("a", { "aria-label": text.download, className: 'ps-focus-ring ps-control-square ps-control-compact inline-flex items-center justify-center rounded-ps-control text-white/80 transition hover:bg-white/15 hover:text-white', download: file.name, href: file.downloadUrl === undefined ? file.url : file.downloadUrl, title: text.download, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Download }) }), onDelete ? (_jsx(PsFileViewerButton, { label: text.delete, onClick: () => setIsConfirming(true), tone: 'danger', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Trash }) })) : null, _jsx(PsFileViewerButton, { label: text.close, onClick: onClose, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Close }) })] })] }), _jsxs("div", { className: 'relative flex min-h-0 flex-1', children: [files.length > 1 ? (_jsx(PsFileViewerButton, { className: 'absolute left-2 top-1/2 z-docked size-ps-control -translate-y-1/2 bg-black/40', label: text.previous, onClick: () => go(-1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone rotate-180', iconKey: PsIconKey.ChevronRight }) })) : null, _jsx("div", { className: 'flex min-h-0 min-w-0 flex-1 p-3 sm:p-6', children: _jsx(PsFileViewerContent, { activeMatch: activeMatch, file: file, labels: text, onMatchCount: setMatchCount, pdfWorkerSrc: pdfWorkerSrc, query: canSearch ? query : '', rotation: rotation, zoom: zoom }, file.id) }), files.length > 1 ? (_jsx(PsFileViewerButton, { className: 'absolute right-2 top-1/2 z-docked size-ps-control -translate-y-1/2 bg-black/40', label: text.next, onClick: () => go(1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.ChevronRight }) })) : null] }), _jsx(PsFileDeleteConfirm, { fileName: file.name, isDeleting: isDeleting, isOpen: isConfirming, labels: text, onCancel: () => setIsConfirming(false), onConfirm: () => void confirmDelete() })] }) }), document.body);
|
|
85
|
+
.join(' · ') })] }) }), canSearch ? (_jsx(PsFileSearchBar, { className: 'order-last w-full sm:order-none sm:w-72', labels: text, state: searchState })) : null, _jsxs("div", { className: 'flex items-center gap-0.5', children: [canZoom ? (_jsxs(_Fragment, { children: [_jsx(PsFileViewerButton, { label: text.zoomOut, onClick: () => changeZoom(-PsFileViewerConfig.zoomStep), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.ZoomOut }) }), _jsxs(PsFileViewerButton, { className: 'w-14 text-xs', label: text.zoomReset, onClick: () => setZoom(1), children: [Math.round(zoom * 100), "%"] }), _jsx(PsFileViewerButton, { label: text.zoomIn, onClick: () => changeZoom(PsFileViewerConfig.zoomStep), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.ZoomIn }) })] })) : null, canRotate ? (_jsx(PsFileViewerButton, { label: text.rotate, onClick: () => setRotation((current) => (current + PsFileViewerConfig.rotateStep) % 360), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Rotate }) })) : null, _jsx("a", { "aria-label": text.download, className: 'ps-focus-ring ps-control-square ps-control-compact inline-flex items-center justify-center rounded-ps-control text-white/80 transition hover:bg-white/15 hover:text-white', download: file.name, href: file.downloadUrl === undefined ? file.url : file.downloadUrl, title: text.download, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Download }) }), onDelete ? (_jsx(PsFileViewerButton, { label: text.delete, onClick: () => setIsConfirming(true), tone: 'danger', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Trash }) })) : null, _jsx(PsFileViewerButton, { label: text.close, onClick: onClose, children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.Close }) })] })] }), _jsxs("div", { className: 'relative flex min-h-0 flex-1', children: [files.length > 1 ? (_jsx(PsFileViewerButton, { className: 'absolute left-2 top-1/2 z-docked size-ps-control -translate-y-1/2 bg-black/40', label: text.previous, onClick: () => go(-1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone rotate-180', iconKey: PsIconKey.ChevronRight }) })) : null, _jsx("div", { className: 'flex min-h-0 min-w-0 flex-1 p-3 sm:p-6', children: _jsx(PsFileViewerContent, { activeMatch: searchState.activeMatch, file: file, labels: text, onMatchCount: searchState.setMatchCount, pdfWorkerSrc: pdfWorkerSrc, query: canSearch ? searchState.query : '', rotation: rotation, zoom: zoom }, file.id) }), files.length > 1 ? (_jsx(PsFileViewerButton, { className: 'absolute right-2 top-1/2 z-docked size-ps-control -translate-y-1/2 bg-black/40', label: text.next, onClick: () => go(1), children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: PsIconKey.ChevronRight }) })) : null] }), _jsx(PsFileDeleteConfirm, { fileName: file.name, isDeleting: isDeleting, isOpen: isConfirming, labels: text, onCancel: () => setIsConfirming(false), onConfirm: () => void confirmDelete() })] }) }), document.body);
|
|
98
86
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { usePsActiveMatchScroll } from '../hooks/use-ps-active-match-scroll';
|
|
4
|
+
import { PsHtmlService } from '../services/ps-html-service';
|
|
5
|
+
export const PsHtmlDocument = ({ activeMatch, html, onMatchCount, query, zoom, }) => {
|
|
6
|
+
const container = useRef(null);
|
|
7
|
+
const highlighted = useMemo(() => PsHtmlService.highlight(html, query), [html, query]);
|
|
8
|
+
useEffect(() => onMatchCount(highlighted.count), [highlighted.count]);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
container.current?.querySelectorAll('mark[data-ps-match]').forEach((mark) => {
|
|
11
|
+
if (mark.getAttribute('data-ps-match') === String(activeMatch))
|
|
12
|
+
mark.setAttribute('data-ps-active', '');
|
|
13
|
+
else
|
|
14
|
+
mark.removeAttribute('data-ps-active');
|
|
15
|
+
});
|
|
16
|
+
}, [highlighted.html, activeMatch]);
|
|
17
|
+
usePsActiveMatchScroll(container, activeMatch, highlighted.html);
|
|
18
|
+
return (_jsx("div", { className: 'h-full w-full overflow-auto', children: _jsx("div", { className: 'ps-html-document mx-auto w-full max-w-4xl break-words rounded-lg bg-surface p-8 leading-relaxed text-ink shadow-2xl [&_a]:text-primary [&_a]:underline [&_blockquote]:border-l-4 [&_blockquote]:border-line [&_blockquote]:pl-4 [&_h1]:mb-3 [&_h1]:text-2xl [&_h1]:font-bold [&_h2]:mb-2 [&_h2]:text-xl [&_h2]:font-bold [&_h3]:mb-2 [&_h3]:text-lg [&_h3]:font-semibold [&_img]:h-auto [&_img]:max-w-full [&_li]:my-1 [&_mark]:rounded-sm [&_mark]:bg-warning/40 [&_mark]:text-inherit [&_mark[data-ps-active]]:bg-warning [&_mark[data-ps-active]]:text-ink [&_mark[data-ps-active]]:ring-2 [&_mark[data-ps-active]]:ring-warning [&_ol]:list-decimal [&_ol]:pl-6 [&_p]:mb-3 [&_table]:my-3 [&_table]:border-collapse [&_td]:border [&_td]:border-line [&_td]:px-2 [&_td]:py-1 [&_th]:border [&_th]:border-line [&_th]:px-2 [&_th]:py-1 [&_ul]:list-disc [&_ul]:pl-6', dangerouslySetInnerHTML: { __html: highlighted.html }, ref: container, style: { fontSize: `${zoom}rem` } }) }));
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsSpinner } from '@pallasoft/ps-core-components';
|
|
3
|
+
import { usePsLoadedFile } from '../hooks/use-ps-loaded-file';
|
|
4
|
+
import { PsHtmlFileService } from '../services/ps-html-file-service';
|
|
5
|
+
import { PsHtmlDocument } from './ps-html-document';
|
|
6
|
+
export const PsHtmlViewer = ({ activeMatch, file, labels, onMatchCount, query, zoom, }) => {
|
|
7
|
+
const { hasFailed, value } = usePsLoadedFile(file.url, PsHtmlFileService.load);
|
|
8
|
+
if (hasFailed)
|
|
9
|
+
return _jsx("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
10
|
+
if (value === null)
|
|
11
|
+
return _jsx(PsSpinner, { className: 'm-auto', color: 'current', size: 'large' });
|
|
12
|
+
return (_jsx(PsHtmlDocument, { activeMatch: activeMatch, html: value, onMatchCount: onMatchCount, query: query, zoom: zoom }));
|
|
13
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
export const PsImageTextHighlights = ({ activeIndex, image, matches, transform, }) => {
|
|
4
|
+
const [size, setSize] = useState({ height: 0, width: 0 });
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const node = image.current;
|
|
7
|
+
if (node === null)
|
|
8
|
+
return;
|
|
9
|
+
const measure = () => setSize({ height: node.clientHeight, width: node.clientWidth });
|
|
10
|
+
measure();
|
|
11
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
12
|
+
window.addEventListener('resize', measure);
|
|
13
|
+
return () => window.removeEventListener('resize', measure);
|
|
14
|
+
}
|
|
15
|
+
const observer = new ResizeObserver(measure);
|
|
16
|
+
observer.observe(node);
|
|
17
|
+
return () => observer.disconnect();
|
|
18
|
+
}, [image]);
|
|
19
|
+
return (_jsx("div", { className: 'pointer-events-none absolute inset-0 flex items-center justify-center', children: _jsx("div", { className: 'relative shrink-0 transition-transform duration-150', style: { height: size.height, transform, width: size.width }, children: matches.map((match, index) => (_jsx("span", { className: match.matchIndex === activeIndex
|
|
20
|
+
? 'absolute rounded-sm bg-warning/60 ring-2 ring-warning'
|
|
21
|
+
: 'absolute rounded-sm bg-warning/35', style: {
|
|
22
|
+
height: `${match.height * 100}%`,
|
|
23
|
+
left: `${match.left * 100}%`,
|
|
24
|
+
top: `${match.top * 100}%`,
|
|
25
|
+
width: `${match.width * 100}%`,
|
|
26
|
+
} }, index))) }) }));
|
|
27
|
+
};
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { usePsDragPan } from '../hooks/use-ps-drag-pan';
|
|
4
|
+
import { PsImageTextService } from '../services/ps-image-text-service';
|
|
4
5
|
import { PsPanService } from '../services/ps-pan-service';
|
|
5
|
-
|
|
6
|
+
import { PsImageTextHighlights } from './ps-image-text-highlights';
|
|
7
|
+
export const PsImageViewer = ({ activeMatch, file, labels, onMatchCount, query, rotation, zoom, }) => {
|
|
6
8
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
7
9
|
const [hasFailed, setHasFailed] = useState(false);
|
|
8
10
|
const pan = useRef(null);
|
|
9
11
|
const base = useRef({ x: 0, y: 0 });
|
|
10
12
|
const frame = useRef(0);
|
|
11
13
|
const pending = useRef({ x: 0, y: 0 });
|
|
14
|
+
const image = useRef(null);
|
|
15
|
+
const layer = file.textLayer;
|
|
16
|
+
const matches = useMemo(() => (layer === undefined ? [] : PsImageTextService.matches(layer, query)), [layer, query]);
|
|
17
|
+
const count = matches.length === 0 ? 0 : matches[matches.length - 1].matchIndex + 1;
|
|
18
|
+
const activeIndex = count === 0 ? -1 : activeMatch % count;
|
|
19
|
+
const imageTransform = `scale(${zoom}) rotate(${rotation}deg)`;
|
|
20
|
+
useEffect(() => onMatchCount(count), [count]);
|
|
12
21
|
useEffect(() => {
|
|
13
22
|
setOffset({ x: 0, y: 0 });
|
|
14
23
|
setHasFailed(false);
|
|
@@ -44,5 +53,5 @@ export const PsImageViewer = ({ file, labels, rotation, zoom }) => {
|
|
|
44
53
|
});
|
|
45
54
|
if (hasFailed)
|
|
46
55
|
return _jsx("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
47
|
-
return (_jsx("div", { className: `h-full w-full touch-none overflow-hidden ${zoom > 1 ? 'cursor-grab active:cursor-grabbing' : ''}`, ...handlers, children:
|
|
56
|
+
return (_jsx("div", { className: `h-full w-full touch-none overflow-hidden ${zoom > 1 ? 'cursor-grab active:cursor-grabbing' : ''}`, ...handlers, children: _jsxs("div", { className: 'relative flex h-full w-full items-center justify-center', ref: pan, style: { transform: PsPanService.translate(offset) }, children: [_jsx("img", { alt: file.name, className: 'max-h-full max-w-full select-none object-contain shadow-2xl transition-transform duration-150', draggable: false, onError: () => setHasFailed(true), ref: image, src: file.url, style: { transform: imageTransform } }), matches.length > 0 ? (_jsx(PsImageTextHighlights, { activeIndex: activeIndex, image: image, matches: matches, transform: imageTransform })) : null] }) }));
|
|
48
57
|
};
|
|
@@ -22,7 +22,7 @@ export const PsPdfPage = ({ activeMatch, document, label, matches, pageNumber, r
|
|
|
22
22
|
task?.cancel();
|
|
23
23
|
};
|
|
24
24
|
}, [document, pageNumber, rotation, scale, isInView]);
|
|
25
|
-
return (_jsxs("div", { "aria-label": `${label} ${pageNumber}`, className: 'relative mx-auto bg-white shadow-2xl', "data-ps-pdf-page": pageNumber, ref: container, style: size === null ? { height: 800, width: 600 } : { height: size.height, width: size.width }, children: [_jsx("canvas", { className: 'block', ref: canvas }), matches.map((match, index) => (_jsx("span", { className: match === activeMatch
|
|
25
|
+
return (_jsxs("div", { "aria-label": `${label} ${pageNumber}`, className: 'relative mx-auto bg-white shadow-2xl', "data-ps-pdf-page": pageNumber, ref: container, style: size === null ? { height: 800, width: 600 } : { height: size.height, width: size.width }, children: [_jsx("canvas", { className: 'block', ref: canvas }), matches.map((match, index) => (_jsx("span", { className: activeMatch !== null && match.matchIndex === activeMatch.matchIndex
|
|
26
26
|
? 'pointer-events-none absolute rounded-sm bg-warning/60 ring-2 ring-warning'
|
|
27
27
|
: 'pointer-events-none absolute rounded-sm bg-warning/35', style: {
|
|
28
28
|
height: `${match.height * 100}%`,
|
|
@@ -76,13 +76,17 @@ export const PsPdfViewer = ({ activeMatch, file, labels, onMatchCount, pdfWorker
|
|
|
76
76
|
if (!isCurrent)
|
|
77
77
|
return;
|
|
78
78
|
setMatches(found);
|
|
79
|
-
onMatchCount(found.length);
|
|
79
|
+
onMatchCount(found.length === 0 ? 0 : found[found.length - 1].matchIndex + 1);
|
|
80
80
|
});
|
|
81
81
|
return () => {
|
|
82
82
|
isCurrent = false;
|
|
83
83
|
};
|
|
84
84
|
}, [document, query, rotation]);
|
|
85
|
-
const
|
|
85
|
+
const matchTotal = matches.length === 0 ? 0 : matches[matches.length - 1].matchIndex + 1;
|
|
86
|
+
const activeRect = matchTotal === 0
|
|
87
|
+
? undefined
|
|
88
|
+
: matches.find((match) => match.matchIndex === activeMatch % matchTotal);
|
|
89
|
+
const active = activeRect === undefined ? null : activeRect;
|
|
86
90
|
const scrollToPage = (pageNumber) => {
|
|
87
91
|
const node = scroller.current?.querySelector(`[data-ps-pdf-page='${pageNumber}']`);
|
|
88
92
|
if (node instanceof HTMLElement)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { PsSpinner } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PsFileViewerConfig } from '../constants/ps-file-viewer-config';
|
|
5
|
+
import { usePsLoadedFile } from '../hooks/use-ps-loaded-file';
|
|
6
|
+
import { PsLabelService } from '../services/ps-label-service';
|
|
7
|
+
import { PsSheetService } from '../services/ps-sheet-service';
|
|
8
|
+
import { PsTableView } from './ps-table-view';
|
|
9
|
+
export const PsSheetViewer = ({ activeMatch, file, labels, onMatchCount, query, zoom, }) => {
|
|
10
|
+
const { hasFailed, value } = usePsLoadedFile(file.url, PsSheetService.load);
|
|
11
|
+
const [sheetIndex, setSheetIndex] = useState(0);
|
|
12
|
+
const sheet = value === null || value.length === 0 ? null : value[Math.min(sheetIndex, value.length - 1)];
|
|
13
|
+
const rows = useMemo(() => (sheet === null ? [] : sheet.rows.slice(0, PsFileViewerConfig.maxSheetRows)), [sheet]);
|
|
14
|
+
useEffect(() => setSheetIndex(0), [file.id]);
|
|
15
|
+
if (hasFailed || (value !== null && value.length === 0))
|
|
16
|
+
return _jsx("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
17
|
+
if (value === null || sheet === null)
|
|
18
|
+
return _jsx(PsSpinner, { className: 'm-auto', color: 'current', size: 'large' });
|
|
19
|
+
return (_jsxs("div", { className: 'flex h-full w-full min-w-0 flex-col gap-2', children: [_jsx("div", { className: 'min-h-0 flex-1 overflow-auto', children: _jsx("div", { className: 'mx-auto w-full max-w-6xl rounded-lg bg-surface p-5 text-ink shadow-2xl', style: { fontSize: `${zoom * 0.875}rem` }, children: _jsx(PsTableView, { activeMatch: activeMatch, onMatchCount: onMatchCount, query: query, rows: rows }, sheet.name) }) }), _jsxs("div", { className: 'flex shrink-0 flex-wrap items-center gap-2 text-xs text-white/70', children: [value.length > 1 ? (_jsx("div", { className: 'flex flex-wrap gap-1', role: 'tablist', children: value.map((entry, index) => (_jsx("button", { "aria-selected": entry === sheet, className: entry === sheet
|
|
20
|
+
? 'ps-focus-ring ps-control-compact rounded-ps-control bg-white/20 px-3 text-white'
|
|
21
|
+
: 'ps-focus-ring ps-control-compact rounded-ps-control px-3 text-white/70 hover:bg-white/10', onClick: () => setSheetIndex(index), role: 'tab', type: 'button', children: entry.name }, `${entry.name}-${index}`))) })) : null, sheet.rows.length > rows.length ? (_jsx("span", { children: PsLabelService.fill(labels.sheetTruncated, { count: PsFileViewerConfig.maxSheetRows }) })) : null] })] }));
|
|
22
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { usePsActiveMatchScroll } from '../hooks/use-ps-active-match-scroll';
|
|
4
|
+
import { PsTextSearchService } from '../services/ps-text-search-service';
|
|
5
|
+
import { PsHighlightedText } from './ps-highlighted-text';
|
|
6
|
+
export const PsTableView = ({ activeMatch, onMatchCount, query, rows }) => {
|
|
7
|
+
const container = useRef(null);
|
|
8
|
+
const cells = useMemo(() => PsTextSearchService.tableSegments(rows, query), [rows, query]);
|
|
9
|
+
const count = useMemo(() => PsTextSearchService.countSegments(cells), [cells]);
|
|
10
|
+
useEffect(() => onMatchCount(count), [count]);
|
|
11
|
+
usePsActiveMatchScroll(container, activeMatch, query);
|
|
12
|
+
return (_jsx("table", { className: 'w-full border-collapse text-left', ref: container, children: _jsx("tbody", { children: cells.map((row, rowIndex) => (_jsx("tr", { className: rowIndex === 0 ? 'bg-primary-soft/50 font-semibold' : 'border-t border-line', children: row.map((cell, cellIndex) => (_jsx("td", { className: 'whitespace-nowrap px-3 py-1.5 align-top', children: _jsx(PsHighlightedText, { activeMatch: activeMatch, segments: cell }) }, cellIndex))) }, rowIndex))) }) }));
|
|
13
|
+
};
|