@pallasoft/ps-file-components 2.3.0 → 2.4.1
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-icon-button.js +1 -1
- package/dist/components/ps-file-list.js +1 -1
- package/dist/components/ps-file-picker.js +1 -1
- 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-upload-row.js +1 -1
- 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-icon-button.js +1 -1
- package/dist/esm/components/ps-file-list.js +1 -1
- package/dist/esm/components/ps-file-picker.js +1 -1
- 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-upload-row.js +1 -1
- 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 +6 -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 +21 -26
- 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 +2 -0
- package/dist/models/ps-image-text-box.js +2 -0
- package/dist/models/ps-image-text-layer.d.ts +2 -0
- package/dist/models/ps-image-text-layer.js +2 -0
- package/dist/models/ps-image-text-word.d.ts +2 -0
- package/dist/models/ps-image-text-word.js +2 -0
- package/dist/models/ps-item-match-part.d.ts +2 -0
- package/dist/models/ps-item-match-part.js +2 -0
- package/dist/models/ps-item-match.d.ts +2 -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 +2 -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 +2 -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-text-match.d.ts +2 -4
- package/dist/models/ps-text-segment.d.ts +2 -5
- 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 +10 -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 +21 -26
- package/dist/services/ps-word-service.d.ts +3 -0
- package/dist/services/ps-word-service.js +52 -0
- package/package.json +6 -3
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsWordViewer = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
6
|
+
const use_ps_loaded_file_1 = require("../hooks/use-ps-loaded-file");
|
|
7
|
+
const ps_word_service_1 = require("../services/ps-word-service");
|
|
8
|
+
const ps_html_document_1 = require("./ps-html-document");
|
|
9
|
+
const PsWordViewer = ({ activeMatch, file, labels, onMatchCount, query, zoom, }) => {
|
|
10
|
+
const { hasFailed, value } = (0, use_ps_loaded_file_1.usePsLoadedFile)(file.url, ps_word_service_1.PsWordService.load);
|
|
11
|
+
if (hasFailed)
|
|
12
|
+
return (0, jsx_runtime_1.jsx)("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
13
|
+
if (value === null)
|
|
14
|
+
return (0, jsx_runtime_1.jsx)(ps_core_components_1.PsSpinner, { className: 'm-auto', color: 'current', size: 'large' });
|
|
15
|
+
return ((0, jsx_runtime_1.jsx)(ps_html_document_1.PsHtmlDocument, { activeMatch: activeMatch, html: value, onMatchCount: onMatchCount, query: query, zoom: zoom }));
|
|
16
|
+
};
|
|
17
|
+
exports.PsWordViewer = PsWordViewer;
|
|
@@ -6,9 +6,12 @@ const ps_file_kind_1 = require("./ps-file-kind");
|
|
|
6
6
|
exports.PsFileKindIcons = {
|
|
7
7
|
[ps_file_kind_1.PsFileKind.audio]: ps_icons_1.PsIconKey.Audio,
|
|
8
8
|
[ps_file_kind_1.PsFileKind.csv]: ps_icons_1.PsIconKey.Sheet,
|
|
9
|
+
[ps_file_kind_1.PsFileKind.html]: ps_icons_1.PsIconKey.Text,
|
|
9
10
|
[ps_file_kind_1.PsFileKind.image]: ps_icons_1.PsIconKey.Image,
|
|
10
11
|
[ps_file_kind_1.PsFileKind.other]: ps_icons_1.PsIconKey.File,
|
|
11
12
|
[ps_file_kind_1.PsFileKind.pdf]: ps_icons_1.PsIconKey.Doc,
|
|
13
|
+
[ps_file_kind_1.PsFileKind.sheet]: ps_icons_1.PsIconKey.Sheet,
|
|
12
14
|
[ps_file_kind_1.PsFileKind.text]: ps_icons_1.PsIconKey.Text,
|
|
13
15
|
[ps_file_kind_1.PsFileKind.video]: ps_icons_1.PsIconKey.Video,
|
|
16
|
+
[ps_file_kind_1.PsFileKind.word]: ps_icons_1.PsIconKey.Doc,
|
|
14
17
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export declare const PsFileKind: {
|
|
2
2
|
readonly audio: "audio";
|
|
3
3
|
readonly csv: "csv";
|
|
4
|
+
readonly html: "html";
|
|
4
5
|
readonly image: "image";
|
|
5
6
|
readonly other: "other";
|
|
6
7
|
readonly pdf: "pdf";
|
|
8
|
+
readonly sheet: "sheet";
|
|
7
9
|
readonly text: "text";
|
|
8
10
|
readonly video: "video";
|
|
11
|
+
readonly word: "word";
|
|
9
12
|
};
|
|
10
13
|
export type PsFileKind = (typeof PsFileKind)[keyof typeof PsFileKind];
|
|
@@ -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
|
+
};
|
|
@@ -3,7 +3,7 @@ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
|
3
3
|
import { PsIcon } from '@pallasoft/ps-icons';
|
|
4
4
|
import { PsFileIconButtonClasses } from '../constants/ps-file-icon-button-classes';
|
|
5
5
|
export const PsFileIconButton = ({ disabled = false, download, href, iconKey, label, onClick, tone = 'neutral', }) => {
|
|
6
|
-
const className = PallasoftClassNameService.merge('ps-focus-ring ps-control-square ps-control-compact inline-flex shrink-0 items-center justify-center rounded-ps-control text-
|
|
6
|
+
const className = PallasoftClassNameService.merge('ps-focus-ring ps-control-square ps-control-compact inline-flex shrink-0 items-center justify-center rounded-ps-control text-muted transition disabled:pointer-events-none disabled:opacity-40', PsFileIconButtonClasses[tone]);
|
|
7
7
|
const icon = (_jsx(PsIcon, { "aria-hidden": 'true', className: 'size-ps-control-icon-standalone', iconKey: iconKey }));
|
|
8
8
|
return href === undefined ? (_jsx("button", { "aria-label": label, className: className, disabled: disabled, onClick: onClick, title: label, type: 'button', children: icon })) : (_jsx("a", { "aria-label": label, className: className, download: download, href: href, title: label, children: icon }));
|
|
9
9
|
};
|
|
@@ -18,6 +18,6 @@ export const PsFileList = ({ busyIds = [], files, formatDate, labels, onDelete,
|
|
|
18
18
|
? null
|
|
19
19
|
: formatDate(file.createdAt),
|
|
20
20
|
].filter((value) => value !== null);
|
|
21
|
-
return (_jsxs("li", { className: 'group flex min-w-0 items-center gap-3 rounded-ps-control border border-line bg-surface p-2 transition hover:border-primary/40', children: [_jsxs("button", { "aria-label": `${text.open} ${file.name}`, className: 'ps-focus-ring flex min-w-0 flex-1 items-center gap-3 rounded-ps-control text-left', onClick: () => onOpen(index), type: 'button', children: [isImage ? (_jsx("img", { alt: '', className: 'h-12 w-12 shrink-0 rounded-ps-control border border-line object-cover', loading: 'lazy', src: file.url })) : (_jsx(PsFileKindIcon, { className: 'h-12 w-12', contentType: file.contentType, name: file.name })), _jsxs("span", { className: 'flex min-w-0 flex-col', children: [_jsx("span", { className: 'truncate text-sm font-medium text-ink group-hover:text-primary', children: file.name }), meta.length > 0 ? (_jsx("span", { className: 'truncate text-xs text-
|
|
21
|
+
return (_jsxs("li", { className: 'group flex min-w-0 items-center gap-3 rounded-ps-control border border-line bg-surface p-2 transition hover:border-primary/40', children: [_jsxs("button", { "aria-label": `${text.open} ${file.name}`, className: 'ps-focus-ring flex min-w-0 flex-1 items-center gap-3 rounded-ps-control text-left', onClick: () => onOpen(index), type: 'button', children: [isImage ? (_jsx("img", { alt: '', className: 'h-12 w-12 shrink-0 rounded-ps-control border border-line object-cover', loading: 'lazy', src: file.url })) : (_jsx(PsFileKindIcon, { className: 'h-12 w-12', contentType: file.contentType, name: file.name })), _jsxs("span", { className: 'flex min-w-0 flex-col', children: [_jsx("span", { className: 'truncate text-sm font-medium text-ink group-hover:text-primary', children: file.name }), meta.length > 0 ? (_jsx("span", { className: 'truncate text-xs text-muted', children: meta.join(' · ') })) : null] })] }), _jsxs("div", { className: 'flex shrink-0 items-center gap-0.5', children: [_jsx(PsFileIconButton, { download: file.name, href: file.downloadUrl === undefined ? file.url : file.downloadUrl, iconKey: PsIconKey.Download, label: `${text.download} ${file.name}` }), onRename ? (_jsx(PsFileIconButton, { disabled: busyIds.includes(file.id), iconKey: PsIconKey.Pencil, label: `${text.rename} ${file.name}`, onClick: () => onRename(file) })) : null, onDelete ? (_jsx(PsFileIconButton, { disabled: busyIds.includes(file.id), iconKey: PsIconKey.Trash, label: `${text.delete} ${file.name}`, onClick: () => onDelete(file), tone: 'danger' })) : null] })] }, file.id));
|
|
22
22
|
}) }));
|
|
23
23
|
};
|
|
@@ -64,5 +64,5 @@ export const PsFilePicker = ({ accept, acceptLabel, className, disabled = false,
|
|
|
64
64
|
select(event.dataTransfer.files);
|
|
65
65
|
}, children: [_jsx("span", { "aria-hidden": 'true', className: PallasoftClassNameService.merge('flex size-12 items-center justify-center rounded-full transition-colors duration-200', isDragging
|
|
66
66
|
? 'bg-primary text-[color:var(--ps-color-primary-contrast)]'
|
|
67
|
-
: 'bg-primary-soft text-primary'), children: _jsx(PsIcon, { className: 'size-6', iconKey: PsIconKey.Upload }) }), _jsxs("div", { className: 'flex flex-col gap-1', children: [_jsx("p", { className: 'text-base font-semibold text-ink', children: isDragging ? (text.dropActive) : (_jsxs(_Fragment, { children: [_jsx("span", { className: touchLabel === undefined ? '' : '[@media(pointer:coarse)]:hidden', children: dropLabel }), touchLabel === undefined ? null : (_jsx("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: touchLabel }))] })) }), _jsx("p", { className: 'text-sm text-ink/60 [@media(pointer:coarse)]:hidden', children: multiple ? text.dragHint : text.dragHintOne })] }), _jsx(PsFilePickerActions, { chooseLabel: chooseLabel, className: 'flex [@media(pointer:coarse)]:hidden', disabled: disabled, isTouch: false, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), _jsx(PsFilePickerActions, { chooseLabel: chooseLabel, className: 'hidden [@media(pointer:coarse)]:flex', disabled: disabled, isTouch: true, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), details.length > 0 ? _jsx("p", { className: 'text-xs text-
|
|
67
|
+
: 'bg-primary-soft text-primary'), children: _jsx(PsIcon, { className: 'size-6', iconKey: PsIconKey.Upload }) }), _jsxs("div", { className: 'flex flex-col gap-1', children: [_jsx("p", { className: 'text-base font-semibold text-ink', children: isDragging ? (text.dropActive) : (_jsxs(_Fragment, { children: [_jsx("span", { className: touchLabel === undefined ? '' : '[@media(pointer:coarse)]:hidden', children: dropLabel }), touchLabel === undefined ? null : (_jsx("span", { className: 'hidden [@media(pointer:coarse)]:inline', children: touchLabel }))] })) }), _jsx("p", { className: 'text-sm text-ink/60 [@media(pointer:coarse)]:hidden', children: multiple ? text.dragHint : text.dragHintOne })] }), _jsx(PsFilePickerActions, { chooseLabel: chooseLabel, className: 'flex [@media(pointer:coarse)]:hidden', disabled: disabled, isTouch: false, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), _jsx(PsFilePickerActions, { chooseLabel: chooseLabel, className: 'hidden [@media(pointer:coarse)]:flex', disabled: disabled, isTouch: true, onChoose: browse, onScan: openScan, scanLabel: scanLabel }), details.length > 0 ? _jsx("p", { className: 'text-xs text-muted', children: details.join(' · ') }) : null, _jsx("input", { accept: acceptAttr, "aria-hidden": 'true', className: 'sr-only', disabled: disabled, multiple: multiple, onChange: onChange, onClick: (event) => event.stopPropagation(), ref: input, tabIndex: -1, type: 'file' })] }), queue === undefined || queue.items.length === 0 ? null : (_jsx("ul", { "aria-label": rowLabels.title, className: 'flex flex-col gap-2', children: queue.items.map((item) => (_jsx(PsUploadRow, { item: item, labels: rowLabels, onCancel: () => queue.cancel(item.id), onRemove: () => queue.remove(item.id), onRetry: () => queue.retry(item.id) }, item.id))) })), scan === undefined ? null : (_jsx(PsDocumentScanner, { ...scan, fileName: scanName === null ? PsScanConfig.defaultFileName : scanName, isOpen: scanName !== null, onClose: () => setScanName(null), onComplete: select }))] }));
|
|
68
68
|
};
|
|
@@ -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
|
+
};
|