@warkypublic/svelix 0.1.46 → 0.2.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.
Files changed (129) hide show
  1. package/README.md +93 -33
  2. package/dist/components/BetterMenu/BetterMenu.svelte +21 -37
  3. package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte +21 -31
  4. package/dist/components/BetterMenu/BetterMenuPreview.svelte +27 -29
  5. package/dist/components/BetterMenu/MenuRenderer.svelte +40 -51
  6. package/dist/components/Boxer/Boxer.svelte +492 -602
  7. package/dist/components/Boxer/BoxerTarget.svelte +6 -46
  8. package/dist/components/Boxer/BoxerTarget.svelte.d.ts +1 -0
  9. package/dist/components/Boxer/types.d.ts +0 -1
  10. package/dist/components/Button.svelte +19 -43
  11. package/dist/components/ButtonPreview.svelte +2 -11
  12. package/dist/components/CardGrid/CardGrid.svelte +489 -614
  13. package/dist/components/CardGrid/CardGrid.svelte.d.ts +4 -0
  14. package/dist/components/CardGrid/CardGridFilterPanel.svelte +111 -90
  15. package/dist/components/CardGrid/DefaultCard.svelte +86 -97
  16. package/dist/components/CardGrid/DefaultCard.svelte.d.ts +1 -0
  17. package/dist/components/CardGrid/ImageCardStory.svelte +87 -36
  18. package/dist/components/CardGrid/cardGrid.d.ts +24 -0
  19. package/dist/components/CardGrid/cardGrid.js +20 -0
  20. package/dist/components/ContentEditor/CollaboraInsertTextDemo.svelte +5 -10
  21. package/dist/components/ContentEditor/ContentEditor.svelte +24 -50
  22. package/dist/components/ContentEditor/subcomponents/AudioPlayer.svelte +17 -17
  23. package/dist/components/ContentEditor/subcomponents/CollaboraEditor.svelte +244 -309
  24. package/dist/components/ContentEditor/subcomponents/EmailViewer.svelte +2 -3
  25. package/dist/components/ContentEditor/subcomponents/ImageViewer.svelte +14 -15
  26. package/dist/components/ContentEditor/subcomponents/MarkdownViewer.svelte +74 -90
  27. package/dist/components/ContentEditor/subcomponents/MonacoEditor.svelte +75 -103
  28. package/dist/components/ContentEditor/subcomponents/Office365Editor.svelte +234 -297
  29. package/dist/components/ContentEditor/subcomponents/PdfViewer.svelte +14 -20
  30. package/dist/components/ContentEditor/subcomponents/TextEditor.svelte +122 -150
  31. package/dist/components/ContentEditor/subcomponents/UnknownFile.svelte +105 -126
  32. package/dist/components/ContentEditor/subcomponents/VideoPlayer.svelte +17 -17
  33. package/dist/components/ContentEditor/subcomponents/ZipViewer.svelte +2 -3
  34. package/dist/components/ErrorBoundary/ErrorBoundary.svelte +41 -58
  35. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte +8 -16
  36. package/dist/components/Former/Former.svelte +306 -424
  37. package/dist/components/Former/FormerButtonArea.svelte +17 -68
  38. package/dist/components/Former/FormerDrawer.svelte +16 -80
  39. package/dist/components/Former/FormerDrawerPreview.svelte +99 -89
  40. package/dist/components/Former/FormerModal.svelte +16 -74
  41. package/dist/components/Former/FormerModalPreview.svelte +99 -89
  42. package/dist/components/Former/FormerPreview.svelte +76 -78
  43. package/dist/components/Former/FormerRestApiPreview.svelte +55 -48
  44. package/dist/components/Former/formerOverlayHelpers.svelte.d.ts +11 -0
  45. package/dist/components/Former/formerOverlayHelpers.svelte.js +38 -0
  46. package/dist/components/Former/index.d.ts +1 -0
  47. package/dist/components/Former/index.js +1 -0
  48. package/dist/components/FormerControllers/ButtonCtrl.svelte +13 -49
  49. package/dist/components/FormerControllers/CtrlFieldShell.svelte +15 -0
  50. package/dist/components/FormerControllers/CtrlFieldShell.svelte.d.ts +12 -0
  51. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte +285 -457
  52. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte +95 -136
  53. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte +3 -62
  54. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte +143 -217
  55. package/dist/components/FormerControllers/IconButtonCtrl.svelte +13 -47
  56. package/dist/components/FormerControllers/InlineWrapper.svelte +12 -41
  57. package/dist/components/FormerControllers/InlineWrapperPreview.svelte +4 -26
  58. package/dist/components/FormerControllers/NativeSelectCtrl.svelte +7 -39
  59. package/dist/components/FormerControllers/NumberInputCtrl.svelte +21 -43
  60. package/dist/components/FormerControllers/PasswordInputCtrl.svelte +8 -33
  61. package/dist/components/FormerControllers/SwitchCtrl.svelte +4 -20
  62. package/dist/components/FormerControllers/TextAreaCtrl.svelte +7 -34
  63. package/dist/components/FormerControllers/TextInputCtrl.svelte +7 -34
  64. package/dist/components/FormerControllers/ctrlLoading.svelte.d.ts +8 -0
  65. package/dist/components/FormerControllers/ctrlLoading.svelte.js +43 -0
  66. package/dist/components/FormerControllers/index.d.ts +2 -0
  67. package/dist/components/FormerControllers/index.js +2 -0
  68. package/dist/components/FormerControllers/utils.d.ts +1 -0
  69. package/dist/components/FormerControllers/utils.js +3 -0
  70. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte +73 -120
  71. package/dist/components/Gridler/components/Gridler.svelte +344 -442
  72. package/dist/components/Gridler/components/GridlerCanvas.svelte +720 -1115
  73. package/dist/components/Gridler/components/GridlerEditor.svelte +2 -15
  74. package/dist/components/Gridler/components/GridlerFull.svelte +521 -841
  75. package/dist/components/Gridler/components/GridlerFull.svelte.d.ts +1 -1
  76. package/dist/components/Gridler/components/GridlerFullWithFormerPreview.svelte +194 -147
  77. package/dist/components/Gridler/components/GridlerSearch.svelte +7 -16
  78. package/dist/components/Gridler/components/GridlerSearchToggle.svelte +7 -19
  79. package/dist/components/Gridler/types.d.ts +1 -1
  80. package/dist/components/Gridler/utils/serverPaging.d.ts +1 -0
  81. package/dist/components/Gridler/utils/serverPaging.js +7 -0
  82. package/dist/components/Icons/IconAdd.svelte +1 -3
  83. package/dist/components/Icons/IconAlertCircle.svelte +1 -3
  84. package/dist/components/Icons/IconAlertTriangle.svelte +1 -3
  85. package/dist/components/Icons/IconCamera.svelte +1 -3
  86. package/dist/components/Icons/IconClose.svelte +1 -3
  87. package/dist/components/Icons/IconEdit.svelte +1 -3
  88. package/dist/components/Icons/IconFilter.svelte +1 -3
  89. package/dist/components/Icons/IconSearch.svelte +1 -3
  90. package/dist/components/Icons/IconSort.svelte +1 -3
  91. package/dist/components/Icons/IconTrash.svelte +1 -3
  92. package/dist/components/Portal/Portal.svelte +14 -38
  93. package/dist/components/Screenshot/Screenshot.svelte +7 -13
  94. package/dist/components/Svark/Svark.svelte +680 -852
  95. package/dist/components/Svark/SvarkContextMenu.svelte +44 -63
  96. package/dist/components/Svark/SvarkInfiniteLoader.svelte +2 -6
  97. package/dist/components/Svark/SvarkPager.svelte +2 -28
  98. package/dist/components/Svark/SvarkSelectionDemo.svelte +4 -8
  99. package/dist/components/Svark/SvarkTopBar.svelte +9 -74
  100. package/dist/components/SvarkGrid/SvarkGrid.svelte +877 -1123
  101. package/dist/components/SvarkGrid/components/SvarkColumnFilterForm.svelte +45 -58
  102. package/dist/components/SvarkGrid/components/SvarkContextMenu.svelte +44 -63
  103. package/dist/components/SvarkGrid/components/SvarkHeaderFilterCell.svelte +41 -75
  104. package/dist/components/SvarkGrid/components/SvarkInfiniteLoader.svelte +2 -6
  105. package/dist/components/SvarkGrid/components/SvarkPager.svelte +2 -28
  106. package/dist/components/SvarkGrid/components/SvarkSearchPopover.svelte +8 -16
  107. package/dist/components/SvarkGrid/components/SvarkTopBar.svelte +6 -62
  108. package/dist/components/SvarkGrid/internal/SvarkGridView.svelte +345 -507
  109. package/dist/components/VTree/VTree.svelte +454 -431
  110. package/dist/components/VTree/VTree.utils.d.ts +23 -0
  111. package/dist/components/VTree/VTree.utils.js +73 -0
  112. package/dist/components/VTree/VTreeContextMenu.svelte +21 -33
  113. package/dist/components/VTree/VTreeControlledSelectionDemo.svelte +20 -0
  114. package/dist/components/VTree/VTreeControlledSelectionDemo.svelte.d.ts +18 -0
  115. package/dist/components/VTree/VTreeCustomizationDemo.svelte +23 -0
  116. package/dist/components/VTree/VTreeCustomizationDemo.svelte.d.ts +18 -0
  117. package/dist/components/VTree/VTreeEventsDemo.svelte +48 -58
  118. package/dist/components/VTree/VTreeInteractionDemo.svelte +29 -0
  119. package/dist/components/VTree/VTreeInteractionDemo.svelte.d.ts +18 -0
  120. package/dist/components/VTree/VTreeMultiSelectDemo.svelte +27 -0
  121. package/dist/components/VTree/VTreeMultiSelectDemo.svelte.d.ts +18 -0
  122. package/dist/components/VTree/VTreeRow.svelte +77 -115
  123. package/dist/components/VTree/VTreeRow.svelte.d.ts +34 -18
  124. package/dist/components/VTree/VTreeSearch.svelte +6 -13
  125. package/dist/components/VTree/VTreeVirtualViewport.svelte +54 -143
  126. package/dist/components/VTree/VTreeVirtualViewport.svelte.d.ts +35 -18
  127. package/dist/components/VTree/types.d.ts +39 -1
  128. package/llm/COMPONENT_GUIDE.md +25 -0
  129. package/package.json +51 -50
@@ -1,154 +1,124 @@
1
- <script lang="ts">
2
- import { Tipex, type TipexEditor } from '@friendofsvelte/tipex';
3
- import '@friendofsvelte/tipex/styles/index.css';
4
- import { blobToString } from '../utils.js';
5
- import type { TextEditorProps } from '../types.js';
6
-
7
- let {
8
- value,
9
- filename,
10
- hideHeader = false,
11
- readonly = false,
12
- onChange,
13
- insertText = $bindable(),
14
- insertHtml = $bindable(),
15
- }: TextEditorProps = $props();
16
-
17
- let tipex = $state<TipexEditor>(undefined);
18
- let body = $state('');
19
- let lastBlobHtml = $state('');
20
- let syncingFromValue = false;
21
- let fileInputEl = $state<HTMLInputElement | undefined>(undefined);
22
-
23
- async function fileToDataUrl(file: File): Promise<string> {
24
- return new Promise((resolve, reject) => {
25
- const reader = new FileReader();
26
- reader.onload = () => {
27
- if (typeof reader.result === 'string') {
28
- resolve(reader.result);
29
- return;
30
- }
31
- reject(new Error('Could not convert image to data URL.'));
32
- };
33
- reader.onerror = () => reject(reader.error ?? new Error('Could not read image file.'));
34
- reader.readAsDataURL(file);
35
- });
36
- }
37
-
38
- async function insertEmbeddedImages(files: File[], dropPoint?: { left: number; top: number }): Promise<void> {
39
- if (!tipex || readonly || files.length === 0) return;
40
-
41
- if (dropPoint) {
42
- const pos = tipex.view.posAtCoords(dropPoint);
43
- if (pos?.pos !== undefined) {
44
- tipex.chain().focus().setTextSelection(pos.pos).run();
45
- } else {
46
- tipex.commands.focus();
47
- }
48
- } else {
49
- tipex.commands.focus();
50
- }
51
-
52
- for (const file of files) {
53
- const src = await fileToDataUrl(file);
54
- tipex.chain().focus().setImage({ src, alt: file.name }).run();
55
- }
56
- }
57
-
58
- async function handlePaste(event: ClipboardEvent): Promise<void> {
59
- if (readonly || !event.clipboardData) return;
60
- const files = Array.from(event.clipboardData.files).filter((file) =>
61
- file.type.startsWith('image/')
62
- );
63
- if (files.length === 0) return;
64
-
65
- event.preventDefault();
66
- await insertEmbeddedImages(files);
67
- }
68
-
69
- async function handleDrop(event: DragEvent): Promise<void> {
70
- if (readonly || !event.dataTransfer) return;
71
- const files = Array.from(event.dataTransfer.files).filter((file) =>
72
- file.type.startsWith('image/')
73
- );
74
- if (files.length === 0) return;
75
-
76
- event.preventDefault();
77
- await insertEmbeddedImages(files, { left: event.clientX, top: event.clientY });
78
- }
79
-
80
- function openImagePicker(): void {
81
- if (readonly) return;
82
- fileInputEl?.click();
83
- }
84
-
85
- async function handleFileInputChange(event: Event): Promise<void> {
86
- const input = event.currentTarget as HTMLInputElement;
87
- const files = Array.from(input.files ?? []).filter((file) => file.type.startsWith('image/'));
88
- if (files.length > 0) {
89
- await insertEmbeddedImages(files);
90
- }
91
- input.value = '';
92
- }
93
-
94
- function syncEditorContent(nextHtml: string): void {
95
- if (!tipex) return;
96
- const currentHtml = tipex.getHTML();
97
- if (currentHtml === nextHtml) return;
98
-
99
- syncingFromValue = true;
100
- tipex.commands.setContent(nextHtml, false);
101
- body = tipex.getHTML();
102
- syncingFromValue = false;
103
- }
104
-
105
- function handleUpdate(): void {
106
- if (readonly || syncingFromValue || !tipex) return;
107
- const nextHtml = tipex.getHTML();
108
- body = nextHtml;
109
- if (nextHtml !== lastBlobHtml) {
110
- onChange?.(new Blob([nextHtml], { type: 'text/html' }));
111
- }
112
- }
113
-
114
- $effect(() => {
115
- blobToString(value).then((nextHtml) => {
116
- lastBlobHtml = nextHtml;
117
- if (body !== nextHtml) {
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: TipexEditor): any { return 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
- import type { UnknownFileProps } from '../types.js';
3
- import { getEditorType, getExtFromFilename } from '../utils.js';
4
-
5
- let { value, filename, contentType, note }: UnknownFileProps = $props();
6
-
7
- let headerHex = $state('');
8
- let headerAscii = $state('');
9
- let sniffedType = $state('Unknown');
10
-
11
- const ext = $derived(filename ? getExtFromFilename(filename) : '');
12
- const blobType = $derived(value?.type || contentType || '');
13
- const size = $derived(value?.size ?? 0);
14
- const inferredViewer = $derived(getEditorType({ filename, contentType: blobType }));
15
- const lastModified = $derived(
16
- typeof File !== 'undefined' && value instanceof File && Number.isFinite(value.lastModified)
17
- ? value.lastModified
18
- : undefined
19
- );
20
-
21
- function formatBytes(bytes: number): string {
22
- if (!bytes) return '0 B';
23
- const units = ['B', 'KB', 'MB', 'GB'];
24
- let next = bytes;
25
- let unitIndex = 0;
26
-
27
- while (next >= 1024 && unitIndex < units.length - 1) {
28
- next /= 1024;
29
- unitIndex += 1;
30
- }
31
-
32
- const rounded = next >= 10 || unitIndex === 0 ? next.toFixed(0) : next.toFixed(1);
33
- return `${rounded} ${units[unitIndex]}`;
34
- }
35
-
36
- function formatDateTime(ms?: number): string | null {
37
- if (!ms) return null;
38
- try {
39
- return new Date(ms).toLocaleString();
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- function bytesToHex(bytes: Uint8Array): string {
46
- return Array.from(bytes)
47
- .map((byte) => byte.toString(16).padStart(2, '0').toUpperCase())
48
- .join(' ');
49
- }
50
-
51
- function bytesToAscii(bytes: Uint8Array): string {
52
- return Array.from(bytes)
53
- .map((byte) => (byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.'))
54
- .join('');
55
- }
56
-
57
- function detectFromHeader(bytes: Uint8Array): string {
58
- if (bytes.length >= 8 &&
59
- bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47 &&
60
- bytes[4] === 0x0D && bytes[5] === 0x0A && bytes[6] === 0x1A && bytes[7] === 0x0A) {
61
- return 'PNG image';
62
- }
63
- if (bytes.length >= 3 && bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF) {
64
- return 'JPEG image';
65
- }
66
- if (bytes.length >= 6 && bytesToAscii(bytes.slice(0, 6)).startsWith('GIF8')) {
67
- return 'GIF image';
68
- }
69
- if (bytes.length >= 4 && bytesToAscii(bytes.slice(0, 4)) === '%PDF') {
70
- return 'PDF document';
71
- }
72
- if (bytes.length >= 4 && bytesToAscii(bytes.slice(0, 4)) === 'PK..') {
73
- return 'ZIP-based archive or document';
74
- }
75
- if (bytes.length >= 12 &&
76
- bytesToAscii(bytes.slice(0, 4)) === 'RIFF' &&
77
- bytesToAscii(bytes.slice(8, 12)) === 'WEBP') {
78
- return 'WEBP image';
79
- }
80
- if (bytes.length >= 12 &&
81
- bytesToAscii(bytes.slice(4, 8)) === 'ftyp') {
82
- return 'ISO Base Media file (mp4/mov/heic family)';
83
- }
84
- if (bytes.length >= 3 && bytesToAscii(bytes.slice(0, 3)) === 'ID3') {
85
- return 'MP3 audio';
86
- }
87
- if (bytes.length >= 5 && bytesToAscii(bytes.slice(0, 5)).toLowerCase() === '<html') {
88
- return 'HTML document';
89
- }
90
- if (bytes.length >= 1 && bytes[0] === 0x3C) {
91
- return 'XML/HTML-like text';
92
- }
93
- if (bytes.length > 0) {
94
- return 'Unknown binary/text payload';
95
- }
96
- return 'Empty file';
97
- }
98
-
99
- $effect(() => {
100
- const blob = value;
101
- if (!blob) {
102
- headerHex = '';
103
- headerAscii = '';
104
- sniffedType = 'No binary payload available';
105
- return;
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
- import type { VideoPlayerProps } from '../types.js';
3
- import IconAlertCircle from '../../Icons/IconAlertCircle.svelte';
4
- let { value, filename }: VideoPlayerProps = $props();
5
-
6
- let src = $state('');
7
- let srcType = $state<string | undefined>(undefined);
8
- let err = $state<string | null>(null);
9
-
10
- $effect(() => {
11
- if (!value) { src = ''; srcType = undefined; return; }
12
- err = null;
13
- const url = URL.createObjectURL(value);
14
- src = url;
15
- srcType = value.type || undefined;
16
- return () => URL.revokeObjectURL(url);
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
- import type { ImageViewerProps } from '../types.js';
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">