@warkypublic/svelix 0.1.46 → 0.1.47
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/README.md +93 -33
- package/dist/components/BetterMenu/BetterMenu.svelte +21 -37
- package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte +21 -31
- package/dist/components/BetterMenu/BetterMenuPreview.svelte +27 -29
- package/dist/components/BetterMenu/MenuRenderer.svelte +40 -51
- package/dist/components/Boxer/Boxer.svelte +488 -602
- package/dist/components/Boxer/BoxerTarget.svelte +6 -46
- package/dist/components/Boxer/BoxerTarget.svelte.d.ts +1 -0
- package/dist/components/Boxer/types.d.ts +0 -1
- package/dist/components/Button.svelte +19 -43
- package/dist/components/ButtonPreview.svelte +2 -11
- package/dist/components/CardGrid/CardGrid.svelte +443 -614
- package/dist/components/CardGrid/CardGridFilterPanel.svelte +111 -90
- package/dist/components/CardGrid/DefaultCard.svelte +80 -96
- package/dist/components/CardGrid/ImageCardStory.svelte +87 -36
- package/dist/components/ContentEditor/CollaboraInsertTextDemo.svelte +5 -10
- package/dist/components/ContentEditor/ContentEditor.svelte +24 -50
- package/dist/components/ContentEditor/subcomponents/AudioPlayer.svelte +17 -17
- package/dist/components/ContentEditor/subcomponents/CollaboraEditor.svelte +244 -309
- package/dist/components/ContentEditor/subcomponents/EmailViewer.svelte +2 -3
- package/dist/components/ContentEditor/subcomponents/ImageViewer.svelte +14 -15
- package/dist/components/ContentEditor/subcomponents/MarkdownViewer.svelte +74 -90
- package/dist/components/ContentEditor/subcomponents/MonacoEditor.svelte +75 -103
- package/dist/components/ContentEditor/subcomponents/Office365Editor.svelte +234 -297
- package/dist/components/ContentEditor/subcomponents/PdfViewer.svelte +14 -20
- package/dist/components/ContentEditor/subcomponents/TextEditor.svelte +122 -150
- package/dist/components/ContentEditor/subcomponents/UnknownFile.svelte +105 -126
- package/dist/components/ContentEditor/subcomponents/VideoPlayer.svelte +17 -17
- package/dist/components/ContentEditor/subcomponents/ZipViewer.svelte +2 -3
- package/dist/components/ErrorBoundary/ErrorBoundary.svelte +41 -58
- package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte +8 -16
- package/dist/components/Former/Former.svelte +306 -424
- package/dist/components/Former/FormerButtonArea.svelte +17 -68
- package/dist/components/Former/FormerDrawer.svelte +26 -78
- package/dist/components/Former/FormerDrawerPreview.svelte +99 -89
- package/dist/components/Former/FormerModal.svelte +24 -70
- package/dist/components/Former/FormerModalPreview.svelte +99 -89
- package/dist/components/Former/FormerPreview.svelte +76 -78
- package/dist/components/Former/FormerRestApiPreview.svelte +55 -48
- package/dist/components/FormerControllers/ButtonCtrl.svelte +13 -44
- package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte +280 -445
- package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte +95 -136
- package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte +3 -62
- package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte +143 -217
- package/dist/components/FormerControllers/IconButtonCtrl.svelte +13 -42
- package/dist/components/FormerControllers/InlineWrapper.svelte +12 -41
- package/dist/components/FormerControllers/InlineWrapperPreview.svelte +4 -26
- package/dist/components/FormerControllers/NativeSelectCtrl.svelte +3 -29
- package/dist/components/FormerControllers/NumberInputCtrl.svelte +9 -35
- package/dist/components/FormerControllers/PasswordInputCtrl.svelte +4 -23
- package/dist/components/FormerControllers/SwitchCtrl.svelte +3 -20
- package/dist/components/FormerControllers/TextAreaCtrl.svelte +3 -24
- package/dist/components/FormerControllers/TextInputCtrl.svelte +3 -24
- package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte +73 -120
- package/dist/components/Gridler/components/Gridler.svelte +338 -442
- package/dist/components/Gridler/components/GridlerCanvas.svelte +720 -1115
- package/dist/components/Gridler/components/GridlerEditor.svelte +2 -15
- package/dist/components/Gridler/components/GridlerFull.svelte +520 -841
- package/dist/components/Gridler/components/GridlerFull.svelte.d.ts +1 -1
- package/dist/components/Gridler/components/GridlerFullWithFormerPreview.svelte +194 -147
- package/dist/components/Gridler/components/GridlerSearch.svelte +7 -16
- package/dist/components/Gridler/components/GridlerSearchToggle.svelte +7 -19
- package/dist/components/Gridler/types.d.ts +1 -1
- package/dist/components/Icons/IconAdd.svelte +1 -3
- package/dist/components/Icons/IconAlertCircle.svelte +1 -3
- package/dist/components/Icons/IconAlertTriangle.svelte +1 -3
- package/dist/components/Icons/IconCamera.svelte +1 -3
- package/dist/components/Icons/IconClose.svelte +1 -3
- package/dist/components/Icons/IconEdit.svelte +1 -3
- package/dist/components/Icons/IconFilter.svelte +1 -3
- package/dist/components/Icons/IconSearch.svelte +1 -3
- package/dist/components/Icons/IconSort.svelte +1 -3
- package/dist/components/Icons/IconTrash.svelte +1 -3
- package/dist/components/Portal/Portal.svelte +14 -38
- package/dist/components/Screenshot/Screenshot.svelte +7 -13
- package/dist/components/Svark/Svark.svelte +680 -852
- package/dist/components/Svark/SvarkContextMenu.svelte +44 -63
- package/dist/components/Svark/SvarkInfiniteLoader.svelte +2 -6
- package/dist/components/Svark/SvarkPager.svelte +2 -28
- package/dist/components/Svark/SvarkSelectionDemo.svelte +4 -8
- package/dist/components/Svark/SvarkTopBar.svelte +9 -74
- package/dist/components/SvarkGrid/SvarkGrid.svelte +877 -1123
- package/dist/components/SvarkGrid/components/SvarkColumnFilterForm.svelte +45 -58
- package/dist/components/SvarkGrid/components/SvarkContextMenu.svelte +44 -63
- package/dist/components/SvarkGrid/components/SvarkHeaderFilterCell.svelte +41 -75
- package/dist/components/SvarkGrid/components/SvarkInfiniteLoader.svelte +2 -6
- package/dist/components/SvarkGrid/components/SvarkPager.svelte +2 -28
- package/dist/components/SvarkGrid/components/SvarkSearchPopover.svelte +8 -16
- package/dist/components/SvarkGrid/components/SvarkTopBar.svelte +6 -62
- package/dist/components/SvarkGrid/internal/SvarkGridView.svelte +345 -507
- package/dist/components/VTree/VTree.svelte +349 -431
- package/dist/components/VTree/VTreeContextMenu.svelte +21 -33
- package/dist/components/VTree/VTreeEventsDemo.svelte +48 -58
- package/dist/components/VTree/VTreeRow.svelte +13 -65
- package/dist/components/VTree/VTreeSearch.svelte +6 -13
- package/dist/components/VTree/VTreeVirtualViewport.svelte +37 -106
- package/package.json +30 -29
|
@@ -1,154 +1,124 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
body = nextHtml;
|
|
119
|
-
}
|
|
120
|
-
syncEditorContent(nextHtml);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
$effect(() => {
|
|
125
|
-
tipex?.setEditable(!readonly);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
$effect(() => {
|
|
129
|
-
const editor = tipex;
|
|
130
|
-
if (!editor) {
|
|
131
|
-
insertText = undefined;
|
|
132
|
-
insertHtml = undefined;
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
insertText = (text: string) => {
|
|
136
|
-
editor.chain().focus().insertContent(text).run();
|
|
137
|
-
};
|
|
138
|
-
insertHtml = (html: string) => {
|
|
139
|
-
editor.chain().focus().insertContent(html).run();
|
|
140
|
-
};
|
|
141
|
-
});
|
|
142
|
-
const editorClass = $derived(
|
|
143
|
-
hideHeader
|
|
144
|
-
? 'min-h-full bg-surface-50-900'
|
|
145
|
-
: 'min-h-full rounded-container-token border border-surface-200-800 bg-surface-50-900'
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const contentPaddingClass = $derived(hideHeader ? 'p-0' : 'p-4');
|
|
149
|
-
|
|
1
|
+
<script lang="ts">import { Tipex } from "@friendofsvelte/tipex";
|
|
2
|
+
import "@friendofsvelte/tipex/styles/index.css";
|
|
3
|
+
import { blobToString } from "../utils.js";
|
|
4
|
+
let { value, filename, hideHeader = false, readonly = false, onChange, insertText = $bindable(), insertHtml = $bindable() } = $props();
|
|
5
|
+
let tipex = $state(undefined);
|
|
6
|
+
let body = $state("");
|
|
7
|
+
let lastBlobHtml = $state("");
|
|
8
|
+
let syncingFromValue = false;
|
|
9
|
+
let fileInputEl = $state(undefined);
|
|
10
|
+
async function fileToDataUrl(file) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const reader = new FileReader();
|
|
13
|
+
reader.onload = () => {
|
|
14
|
+
if (typeof reader.result === "string") {
|
|
15
|
+
resolve(reader.result);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
reject(new Error("Could not convert image to data URL."));
|
|
19
|
+
};
|
|
20
|
+
reader.onerror = () => reject(reader.error ?? new Error("Could not read image file."));
|
|
21
|
+
reader.readAsDataURL(file);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function insertEmbeddedImages(files, dropPoint) {
|
|
25
|
+
if (!tipex || readonly || files.length === 0) return;
|
|
26
|
+
if (dropPoint) {
|
|
27
|
+
const pos = tipex.view.posAtCoords(dropPoint);
|
|
28
|
+
if (pos?.pos !== undefined) {
|
|
29
|
+
tipex.chain().focus().setTextSelection(pos.pos).run();
|
|
30
|
+
} else {
|
|
31
|
+
tipex.commands.focus();
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
tipex.commands.focus();
|
|
35
|
+
}
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const src = await fileToDataUrl(file);
|
|
38
|
+
tipex.chain().focus().setImage({
|
|
39
|
+
src,
|
|
40
|
+
alt: file.name
|
|
41
|
+
}).run();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function handlePaste(event) {
|
|
45
|
+
if (readonly || !event.clipboardData) return;
|
|
46
|
+
const files = Array.from(event.clipboardData.files).filter((file) => file.type.startsWith("image/"));
|
|
47
|
+
if (files.length === 0) return;
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
await insertEmbeddedImages(files);
|
|
50
|
+
}
|
|
51
|
+
async function handleDrop(event) {
|
|
52
|
+
if (readonly || !event.dataTransfer) return;
|
|
53
|
+
const files = Array.from(event.dataTransfer.files).filter((file) => file.type.startsWith("image/"));
|
|
54
|
+
if (files.length === 0) return;
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
await insertEmbeddedImages(files, {
|
|
57
|
+
left: event.clientX,
|
|
58
|
+
top: event.clientY
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function openImagePicker() {
|
|
62
|
+
if (readonly) return;
|
|
63
|
+
fileInputEl?.click();
|
|
64
|
+
}
|
|
65
|
+
async function handleFileInputChange(event) {
|
|
66
|
+
const input = event.currentTarget;
|
|
67
|
+
const files = Array.from(input.files ?? []).filter((file) => file.type.startsWith("image/"));
|
|
68
|
+
if (files.length > 0) {
|
|
69
|
+
await insertEmbeddedImages(files);
|
|
70
|
+
}
|
|
71
|
+
input.value = "";
|
|
72
|
+
}
|
|
73
|
+
function syncEditorContent(nextHtml) {
|
|
74
|
+
if (!tipex) return;
|
|
75
|
+
const currentHtml = tipex.getHTML();
|
|
76
|
+
if (currentHtml === nextHtml) return;
|
|
77
|
+
syncingFromValue = true;
|
|
78
|
+
tipex.commands.setContent(nextHtml, false);
|
|
79
|
+
body = tipex.getHTML();
|
|
80
|
+
syncingFromValue = false;
|
|
81
|
+
}
|
|
82
|
+
function handleUpdate() {
|
|
83
|
+
if (readonly || syncingFromValue || !tipex) return;
|
|
84
|
+
const nextHtml = tipex.getHTML();
|
|
85
|
+
body = nextHtml;
|
|
86
|
+
if (nextHtml !== lastBlobHtml) {
|
|
87
|
+
onChange?.(new Blob([nextHtml], { type: "text/html" }));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
$effect(() => {
|
|
91
|
+
blobToString(value).then((nextHtml) => {
|
|
92
|
+
lastBlobHtml = nextHtml;
|
|
93
|
+
if (body !== nextHtml) {
|
|
94
|
+
body = nextHtml;
|
|
95
|
+
}
|
|
96
|
+
syncEditorContent(nextHtml);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
$effect(() => {
|
|
100
|
+
tipex?.setEditable(!readonly);
|
|
101
|
+
});
|
|
102
|
+
$effect(() => {
|
|
103
|
+
const editor = tipex;
|
|
104
|
+
if (!editor) {
|
|
105
|
+
insertText = undefined;
|
|
106
|
+
insertHtml = undefined;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
insertText = (text) => {
|
|
110
|
+
editor.chain().focus().insertContent(text).run();
|
|
111
|
+
};
|
|
112
|
+
insertHtml = (html) => {
|
|
113
|
+
editor.chain().focus().insertContent(html).run();
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
const editorClass = $derived(hideHeader ? "min-h-full bg-surface-50-900" : "min-h-full rounded-container-token border border-surface-200-800 bg-surface-50-900");
|
|
117
|
+
const contentPaddingClass = $derived(hideHeader ? "p-0" : "p-4");
|
|
150
118
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
151
|
-
function echain(editor
|
|
119
|
+
function echain(editor) {
|
|
120
|
+
return echain(editor) ?? {};
|
|
121
|
+
}
|
|
152
122
|
</script>
|
|
153
123
|
|
|
154
124
|
{#snippet controlComponent(editor: TipexEditor)}
|
|
@@ -312,6 +282,8 @@ function echain(editor: TipexEditor): any { return echain(editor) ?? {}; }
|
|
|
312
282
|
|
|
313
283
|
<div
|
|
314
284
|
class="flex h-full w-full flex-col overflow-hidden bg-surface-50-950"
|
|
285
|
+
role="region"
|
|
286
|
+
aria-label="Text editor"
|
|
315
287
|
onpaste={handlePaste}
|
|
316
288
|
ondrop={handleDrop}
|
|
317
289
|
ondragover={(event) => {
|
|
@@ -1,129 +1,108 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let cancelled = false;
|
|
109
|
-
|
|
110
|
-
blob.slice(0, 32).arrayBuffer().then((buffer) => {
|
|
111
|
-
if (cancelled) return;
|
|
112
|
-
const bytes = new Uint8Array(buffer);
|
|
113
|
-
headerHex = bytesToHex(bytes);
|
|
114
|
-
headerAscii = bytesToAscii(bytes);
|
|
115
|
-
sniffedType = detectFromHeader(bytes);
|
|
116
|
-
}).catch(() => {
|
|
117
|
-
if (cancelled) return;
|
|
118
|
-
headerHex = '';
|
|
119
|
-
headerAscii = '';
|
|
120
|
-
sniffedType = 'Could not inspect file header';
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return () => {
|
|
124
|
-
cancelled = true;
|
|
125
|
-
};
|
|
126
|
-
});
|
|
1
|
+
<script lang="ts">import { getEditorType, getExtFromFilename } from "../utils.js";
|
|
2
|
+
let { value, filename, contentType, note } = $props();
|
|
3
|
+
let headerHex = $state("");
|
|
4
|
+
let headerAscii = $state("");
|
|
5
|
+
let sniffedType = $state("Unknown");
|
|
6
|
+
const ext = $derived(filename ? getExtFromFilename(filename) : "");
|
|
7
|
+
const blobType = $derived(value?.type || contentType || "");
|
|
8
|
+
const size = $derived(value?.size ?? 0);
|
|
9
|
+
const inferredViewer = $derived(getEditorType({
|
|
10
|
+
filename,
|
|
11
|
+
contentType: blobType
|
|
12
|
+
}));
|
|
13
|
+
const lastModified = $derived(typeof File !== "undefined" && value instanceof File && Number.isFinite(value.lastModified) ? value.lastModified : undefined);
|
|
14
|
+
function formatBytes(bytes) {
|
|
15
|
+
if (!bytes) return "0 B";
|
|
16
|
+
const units = [
|
|
17
|
+
"B",
|
|
18
|
+
"KB",
|
|
19
|
+
"MB",
|
|
20
|
+
"GB"
|
|
21
|
+
];
|
|
22
|
+
let next = bytes;
|
|
23
|
+
let unitIndex = 0;
|
|
24
|
+
while (next >= 1024 && unitIndex < units.length - 1) {
|
|
25
|
+
next /= 1024;
|
|
26
|
+
unitIndex += 1;
|
|
27
|
+
}
|
|
28
|
+
const rounded = next >= 10 || unitIndex === 0 ? next.toFixed(0) : next.toFixed(1);
|
|
29
|
+
return `${rounded} ${units[unitIndex]}`;
|
|
30
|
+
}
|
|
31
|
+
function formatDateTime(ms) {
|
|
32
|
+
if (!ms) return null;
|
|
33
|
+
try {
|
|
34
|
+
return new Date(ms).toLocaleString();
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function bytesToHex(bytes) {
|
|
40
|
+
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0").toUpperCase()).join(" ");
|
|
41
|
+
}
|
|
42
|
+
function bytesToAscii(bytes) {
|
|
43
|
+
return Array.from(bytes).map((byte) => byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : ".").join("");
|
|
44
|
+
}
|
|
45
|
+
function detectFromHeader(bytes) {
|
|
46
|
+
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) {
|
|
47
|
+
return "PNG image";
|
|
48
|
+
}
|
|
49
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
50
|
+
return "JPEG image";
|
|
51
|
+
}
|
|
52
|
+
if (bytes.length >= 6 && bytesToAscii(bytes.slice(0, 6)).startsWith("GIF8")) {
|
|
53
|
+
return "GIF image";
|
|
54
|
+
}
|
|
55
|
+
if (bytes.length >= 4 && bytesToAscii(bytes.slice(0, 4)) === "%PDF") {
|
|
56
|
+
return "PDF document";
|
|
57
|
+
}
|
|
58
|
+
if (bytes.length >= 4 && bytesToAscii(bytes.slice(0, 4)) === "PK..") {
|
|
59
|
+
return "ZIP-based archive or document";
|
|
60
|
+
}
|
|
61
|
+
if (bytes.length >= 12 && bytesToAscii(bytes.slice(0, 4)) === "RIFF" && bytesToAscii(bytes.slice(8, 12)) === "WEBP") {
|
|
62
|
+
return "WEBP image";
|
|
63
|
+
}
|
|
64
|
+
if (bytes.length >= 12 && bytesToAscii(bytes.slice(4, 8)) === "ftyp") {
|
|
65
|
+
return "ISO Base Media file (mp4/mov/heic family)";
|
|
66
|
+
}
|
|
67
|
+
if (bytes.length >= 3 && bytesToAscii(bytes.slice(0, 3)) === "ID3") {
|
|
68
|
+
return "MP3 audio";
|
|
69
|
+
}
|
|
70
|
+
if (bytes.length >= 5 && bytesToAscii(bytes.slice(0, 5)).toLowerCase() === "<html") {
|
|
71
|
+
return "HTML document";
|
|
72
|
+
}
|
|
73
|
+
if (bytes.length >= 1 && bytes[0] === 60) {
|
|
74
|
+
return "XML/HTML-like text";
|
|
75
|
+
}
|
|
76
|
+
if (bytes.length > 0) {
|
|
77
|
+
return "Unknown binary/text payload";
|
|
78
|
+
}
|
|
79
|
+
return "Empty file";
|
|
80
|
+
}
|
|
81
|
+
$effect(() => {
|
|
82
|
+
const blob = value;
|
|
83
|
+
if (!blob) {
|
|
84
|
+
headerHex = "";
|
|
85
|
+
headerAscii = "";
|
|
86
|
+
sniffedType = "No binary payload available";
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
let cancelled = false;
|
|
90
|
+
blob.slice(0, 32).arrayBuffer().then((buffer) => {
|
|
91
|
+
if (cancelled) return;
|
|
92
|
+
const bytes = new Uint8Array(buffer);
|
|
93
|
+
headerHex = bytesToHex(bytes);
|
|
94
|
+
headerAscii = bytesToAscii(bytes);
|
|
95
|
+
sniffedType = detectFromHeader(bytes);
|
|
96
|
+
}).catch(() => {
|
|
97
|
+
if (cancelled) return;
|
|
98
|
+
headerHex = "";
|
|
99
|
+
headerAscii = "";
|
|
100
|
+
sniffedType = "Could not inspect file header";
|
|
101
|
+
});
|
|
102
|
+
return () => {
|
|
103
|
+
cancelled = true;
|
|
104
|
+
};
|
|
105
|
+
});
|
|
127
106
|
</script>
|
|
128
107
|
|
|
129
108
|
<div class="flex h-full w-full flex-col gap-4 overflow-auto p-5 text-surface-700-300">
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
<script lang="ts">import IconAlertCircle from "../../Icons/IconAlertCircle.svelte";
|
|
2
|
+
let { value, filename } = $props();
|
|
3
|
+
let src = $state("");
|
|
4
|
+
let srcType = $state(undefined);
|
|
5
|
+
let err = $state(null);
|
|
6
|
+
$effect(() => {
|
|
7
|
+
if (!value) {
|
|
8
|
+
src = "";
|
|
9
|
+
srcType = undefined;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
err = null;
|
|
13
|
+
const url = URL.createObjectURL(value);
|
|
14
|
+
src = url;
|
|
15
|
+
srcType = value.type || undefined;
|
|
16
|
+
return () => URL.revokeObjectURL(url);
|
|
17
|
+
});
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<div class="flex h-full w-full flex-col items-center justify-center gap-4 bg-black p-4">
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
let { value: _value, filename }: ImageViewerProps = $props();
|
|
1
|
+
<script lang="ts">let { value: _value, filename } = $props();
|
|
2
|
+
export {};
|
|
4
3
|
</script>
|
|
5
4
|
|
|
6
5
|
<div class="card p-4 text-surface-700-300">
|