omni-viewer-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/i18n/catalog.de.d.ts +1 -0
- package/dist/i18n/catalog.de.js +68 -0
- package/dist/i18n/catalog.en.js +3 -0
- package/dist/i18n/catalog.fr.d.ts +1 -0
- package/dist/i18n/catalog.fr.js +68 -0
- package/dist/i18n/catalog.it.d.ts +1 -0
- package/dist/i18n/catalog.it.js +68 -0
- package/dist/i18n/catalog.ja.d.ts +1 -0
- package/dist/i18n/catalog.ja.js +288 -0
- package/dist/i18n/catalog.ko.js +372 -26
- package/dist/i18n/catalog.th.d.ts +1 -0
- package/dist/i18n/catalog.th.js +68 -0
- package/dist/i18n/catalog.zh-cn.d.ts +1 -0
- package/dist/i18n/catalog.zh-cn.js +265 -0
- package/dist/i18n/catalog.zh-tw.d.ts +1 -0
- package/dist/i18n/catalog.zh-tw.js +52 -0
- package/dist/i18n/index.d.ts +7 -0
- package/dist/i18n/index.js +35 -2
- package/dist/parsers/archive/index.d.ts +16 -2
- package/dist/parsers/archive/index.js +39 -11
- package/dist/parsers/archive/model.d.ts +20 -0
- package/dist/parsers/audio/index.d.ts +3 -0
- package/dist/parsers/audio/index.js +5 -0
- package/dist/parsers/video/index.d.ts +3 -0
- package/dist/parsers/video/index.js +3 -0
- package/dist/styles/archive.css +1 -0
- package/dist/styles/audio.css +8 -0
- package/dist/styles/ppt.css +1 -1
- package/dist/viewers/archive/index.d.ts +17 -1
- package/dist/viewers/archive/index.js +124 -21
- package/dist/viewers/archive/styles.d.ts +1 -1
- package/dist/viewers/archive/styles.js +1 -0
- package/dist/viewers/audio/styles.js +8 -0
- package/dist/viewers/ppt/chart.js +11 -4
- package/dist/viewers/ppt/render.js +30 -3
- package/dist/viewers/ppt/styles.d.ts +1 -1
- package/dist/viewers/ppt/styles.js +1 -1
- package/package.json +2 -2
- package/dist/viewers/hwp/self-loading.d.ts +0 -2
- package/dist/viewers/hwp/self-loading.js +0 -6
package/dist/styles/ppt.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
:host,.omni-viewer--ppt{color:var(--omni-fg,#ddd);font:13px system-ui}.omni-ppt{height:100%;display:flex;flex-direction:column;background:var(--omni-bg,#181818)}.omni-ppt__toolbar{display:flex;gap:6px;align-items:center;padding:8px;border-bottom:1px solid var(--omni-border,#444)}.omni-ppt__toolbar button,.omni-ppt__toolbar select{background:var(--omni-control-bg,#292929);color:inherit;border:1px solid var(--omni-border,#555);border-radius:4px;padding:4px 8px}.omni-ppt__viewport{overflow:auto;flex:1;padding:20px}.omni-ppt__slides{display:flex;flex-direction:column;align-items:center;gap:20px}.omni-ppt__slide{position:relative;overflow:hidden;box-shadow:0 2px 12px #0008;transform-origin:top center;flex:none}.omni-ppt__element{position:absolute;box-sizing:border-box;overflow:hidden}.omni-ppt__text{white-space:pre-wrap}.omni-ppt__text p{margin:0}.omni-ppt__image{width:100%;height:100%;object-fit:
|
|
1
|
+
:host,.omni-viewer--ppt{color:var(--omni-fg,#ddd);font:13px system-ui}.omni-ppt{height:100%;display:flex;flex-direction:column;background:var(--omni-bg,#181818)}.omni-ppt__toolbar{display:flex;gap:6px;align-items:center;padding:8px;border-bottom:1px solid var(--omni-border,#444)}.omni-ppt__toolbar button,.omni-ppt__toolbar select{background:var(--omni-control-bg,#292929);color:inherit;border:1px solid var(--omni-border,#555);border-radius:4px;padding:4px 8px}.omni-ppt__viewport{overflow:auto;flex:1;padding:20px}.omni-ppt__slides{display:flex;flex-direction:column;align-items:center;gap:20px}.omni-ppt__slide{position:relative;overflow:hidden;box-shadow:0 2px 12px #0008;transform-origin:top center;flex:none}.omni-ppt__element{position:absolute;box-sizing:border-box;overflow:hidden}.omni-ppt__text{white-space:pre-wrap;word-break:break-word}.omni-ppt__text p{margin:0 0 4px}.omni-ppt__text[data-text-preset="cover-title"] .omni-ppt__text-line,.omni-ppt__text[data-text-preset="cover-subtitle"] .omni-ppt__text-line{margin-bottom:0}.omni-ppt__image{width:100%;height:100%;object-fit:cover;object-position:center}.omni-ppt__table{border-collapse:collapse;width:100%;height:100%;color:#111}.omni-ppt__table th,.omni-ppt__table td{border:1px solid #777;padding:3px;vertical-align:top}.omni-ppt__shape{width:100%;height:100%}.omni-ppt__empty{padding:32px;text-align:center}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FileSaveService, HostContext } from '../../host/index.js';
|
|
2
|
-
import { type ArchiveDecoder } from '../../parsers/archive/index.js';
|
|
2
|
+
import { type ArchiveDecoder, type ArchiveEntrySaver, type ArchiveStreamDecoder } from '../../parsers/archive/index.js';
|
|
3
3
|
import type { ResourceLimits } from '../../parsers/types.js';
|
|
4
4
|
import { type MountOptions, type ViewerHandle, type ViewerInput } from '../types.js';
|
|
5
5
|
export * from '../../parsers/archive/index.js';
|
|
@@ -17,9 +17,25 @@ export declare const ARCHIVE_VIEWER_META: {
|
|
|
17
17
|
export interface ArchiveViewerDeps extends ArchiveDecoder {
|
|
18
18
|
}
|
|
19
19
|
export type ArchiveViewerContext = HostContext & {
|
|
20
|
+
/** Buffered save: core extracts the entry and hands over its bytes. */
|
|
20
21
|
save?: FileSaveService;
|
|
22
|
+
/** Streaming save: the adapter pipes the entry to disk itself, so a
|
|
23
|
+
* multi-GB entry is never materialized in memory. Preferred over `save`
|
|
24
|
+
* when present. */
|
|
25
|
+
saveEntry?: ArchiveEntrySaver;
|
|
21
26
|
};
|
|
22
27
|
export interface ArchiveMountOptions extends MountOptions {
|
|
23
28
|
limits?: ResourceLimits;
|
|
29
|
+
createObjectUrl?(blob: Blob): string;
|
|
30
|
+
revokeObjectUrl?(url: string): void;
|
|
31
|
+
}
|
|
32
|
+
/** Lazy streaming input: the adapter's path-based decoder opens the archive
|
|
33
|
+
* without core ever holding the full file bytes (opt-in, opposite the buffered
|
|
34
|
+
* {@link ViewerInput} path used by vscode/chrome/web). */
|
|
35
|
+
export interface ArchiveStreamingSource extends ArchiveStreamDecoder {
|
|
36
|
+
fileName: string;
|
|
37
|
+
totalSize?: number;
|
|
38
|
+
lastModified?: number;
|
|
24
39
|
}
|
|
25
40
|
export declare function mountArchiveViewer(input: ViewerInput, container: HTMLElement, ctx: ArchiveViewerContext, deps: ArchiveViewerDeps, options?: ArchiveMountOptions): Promise<ViewerHandle>;
|
|
41
|
+
export declare function mountArchiveViewer(source: ArchiveStreamingSource, container: HTMLElement, ctx: ArchiveViewerContext, options?: ArchiveMountOptions): Promise<ViewerHandle>;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { ARCHIVE_DEFAULT_LIMITS, ArchiveError, parseArchive } from '../../parsers/archive/index.js';
|
|
1
|
+
import { ARCHIVE_DEFAULT_LIMITS, ArchiveError, openArchiveStream, parseArchive } from '../../parsers/archive/index.js';
|
|
2
|
+
import { audioMimeType } from '../../parsers/audio/index.js';
|
|
3
|
+
import { videoMimeType } from '../../parsers/video/index.js';
|
|
4
|
+
import { detectImageMime } from '../image/decode.js';
|
|
2
5
|
import { MountAbortedError, VIEWER_ROOT_CLASS } from '../types.js';
|
|
3
6
|
import { createArchiveController } from './controller.js';
|
|
4
7
|
import { tryDecodeArchiveEntryPreview } from './archive-preview-decoder.js';
|
|
@@ -8,6 +11,17 @@ export * from '../../parsers/archive/index.js';
|
|
|
8
11
|
export { createArchiveController } from './controller.js';
|
|
9
12
|
export { archiveViewerCss } from './styles.js';
|
|
10
13
|
export const ARCHIVE_VIEWER_META = { id: 'archive', displayNameKey: 'archive.title', extensions: ['zip', 'jar', 'apk', 'tar', 'tgz', 'tar.gz', 'tbz', 'tbz2', 'tar.bz2', 'txz', 'tar.xz', 'gz', 'bz2', 'xz', '7z', 'rar', 'dmg'], priority: 5, requiredServices: [], optionalServices: ['save'], inputOwnership: 'consumes' };
|
|
14
|
+
/** Ceiling for extracting an audio entry into memory for inline playback.
|
|
15
|
+
* Larger than the text/hex preview budget, but bounded to avoid huge allocations. */
|
|
16
|
+
const ARCHIVE_AUDIO_PREVIEW_BYTES = 64 * 1024 * 1024;
|
|
17
|
+
/** Ceiling for extracting an image entry into memory for inline preview. */
|
|
18
|
+
const ARCHIVE_IMAGE_PREVIEW_BYTES = 32 * 1024 * 1024;
|
|
19
|
+
/** Ceiling for extracting a video entry into memory for inline playback. The
|
|
20
|
+
* whole entry is buffered into a blob URL, so this is the largest of the caps. */
|
|
21
|
+
const ARCHIVE_VIDEO_PREVIEW_BYTES = 128 * 1024 * 1024;
|
|
22
|
+
const IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']);
|
|
23
|
+
const hasImageExtension = (path) => IMAGE_EXTENSIONS.has(path.toLowerCase().split('.').pop() ?? '');
|
|
24
|
+
const blobPart = (data) => (data.buffer instanceof ArrayBuffer ? data : new Uint8Array(data));
|
|
11
25
|
const element = (tag, className, text) => {
|
|
12
26
|
const node = document.createElement(tag);
|
|
13
27
|
if (className)
|
|
@@ -39,10 +53,26 @@ const archiveFormat = (name) => {
|
|
|
39
53
|
const lower = name.toLocaleLowerCase();
|
|
40
54
|
return ['tar.gz', 'tar.bz2', 'tar.xz'].find(ext => lower.endsWith(`.${ext}`))?.toUpperCase() ?? (lower.split('.').pop()?.toUpperCase() || 'ARCHIVE');
|
|
41
55
|
};
|
|
42
|
-
export async function mountArchiveViewer(
|
|
56
|
+
export async function mountArchiveViewer(inputOrSource, container, ctx, depsOrOptions, maybeOptions) {
|
|
57
|
+
const streaming = !('data' in inputOrSource);
|
|
58
|
+
const options = (streaming ? depsOrOptions : maybeOptions) ?? {};
|
|
43
59
|
if (options.signal?.aborted)
|
|
44
60
|
throw new MountAbortedError();
|
|
45
|
-
|
|
61
|
+
let fileName;
|
|
62
|
+
let totalBytes;
|
|
63
|
+
let parsed;
|
|
64
|
+
if (streaming) {
|
|
65
|
+
const source = inputOrSource;
|
|
66
|
+
fileName = source.fileName;
|
|
67
|
+
totalBytes = source.totalSize;
|
|
68
|
+
parsed = await openArchiveStream(source, { fileName, ...(source.totalSize !== undefined ? { totalSize: source.totalSize } : {}), ...(options.signal ? { signal: options.signal } : {}), ...(options.limits ? { limits: options.limits } : {}) });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const input = inputOrSource;
|
|
72
|
+
fileName = input.fileName;
|
|
73
|
+
totalBytes = input.data.byteLength;
|
|
74
|
+
parsed = await parseArchive(input.data, depsOrOptions, { fileName, ...(options.signal ? { signal: options.signal } : {}), ...(options.limits ? { limits: options.limits } : {}) });
|
|
75
|
+
}
|
|
46
76
|
if (parsed.outcome.result.status === 'failed')
|
|
47
77
|
throw new Error(parsed.outcome.result.failure.messageKey);
|
|
48
78
|
if (options.signal?.aborted) {
|
|
@@ -64,9 +94,9 @@ export async function mountArchiveViewer(input, container, ctx, deps, options =
|
|
|
64
94
|
const wrap = element('section', `${VIEWER_ROOT_CLASS} omni-viewer--archive`);
|
|
65
95
|
const hero = element('header', 'omni-archive__hero');
|
|
66
96
|
const heading = element('div');
|
|
67
|
-
heading.append(element('div', 'omni-archive__eyebrow', t('archive.preview')), element('h1', '',
|
|
97
|
+
heading.append(element('div', 'omni-archive__eyebrow', t('archive.preview')), element('h1', '', fileName), element('p', 'omni-archive__subtitle', t('archive.summary')));
|
|
68
98
|
const pills = element('div', 'omni-archive__pills');
|
|
69
|
-
pills.append(element('span', 'omni-archive__pill', archiveFormat(
|
|
99
|
+
pills.append(element('span', 'omni-archive__pill', archiveFormat(fileName)), element('span', 'omni-archive__pill', t('archive.entries', { count: entries.length })), element('span', 'omni-archive__pill', formatSize(totalBytes)));
|
|
70
100
|
hero.append(heading, pills);
|
|
71
101
|
const controls = element('section', 'omni-archive__controls');
|
|
72
102
|
const label = element('label', 'omni-archive__search-label', t('archive.filter'));
|
|
@@ -107,11 +137,13 @@ export async function mountArchiveViewer(input, container, ctx, deps, options =
|
|
|
107
137
|
const previewMeta = element('p', 'omni-archive__preview-meta', t('archive.selectHint'));
|
|
108
138
|
const preview = element('pre', 'omni-archive__preview', t('archive.noSelection'));
|
|
109
139
|
preview.setAttribute('aria-live', 'polite');
|
|
140
|
+
const previewMedia = element('div', 'omni-archive__preview-media');
|
|
141
|
+
previewMedia.style.display = 'none';
|
|
110
142
|
const saveButton = element('button', 'omni-archive__save', t('archive.saveEntry'));
|
|
111
143
|
saveButton.type = 'button';
|
|
112
|
-
saveButton.hidden = !ctx.save;
|
|
144
|
+
saveButton.hidden = !ctx.save && !ctx.saveEntry;
|
|
113
145
|
saveButton.disabled = true;
|
|
114
|
-
previewPanel.append(previewHeader, previewMeta, preview, saveButton);
|
|
146
|
+
previewPanel.append(previewHeader, previewMeta, preview, previewMedia, saveButton);
|
|
115
147
|
workspace.append(tableWrap, previewPanel);
|
|
116
148
|
wrap.append(hero, controls, stats, workspace);
|
|
117
149
|
root.append(wrap);
|
|
@@ -119,7 +151,52 @@ export async function mountArchiveViewer(input, container, ctx, deps, options =
|
|
|
119
151
|
let extraction;
|
|
120
152
|
let saveExtraction;
|
|
121
153
|
let selectedEntry;
|
|
122
|
-
|
|
154
|
+
let previewUrl;
|
|
155
|
+
const createUrl = options.createObjectUrl ?? URL.createObjectURL.bind(URL);
|
|
156
|
+
const revokeUrl = options.revokeObjectUrl ?? URL.revokeObjectURL.bind(URL);
|
|
157
|
+
// Inline display beats author CSS (.omni-archive__preview-media sets display:flex,
|
|
158
|
+
// which would otherwise override the [hidden] attribute), so the text preview and
|
|
159
|
+
// the media preview stay reliably mutually exclusive.
|
|
160
|
+
const clearPreviewMedia = () => { if (previewUrl) {
|
|
161
|
+
revokeUrl(previewUrl);
|
|
162
|
+
previewUrl = undefined;
|
|
163
|
+
} previewMedia.replaceChildren(); previewMedia.style.display = 'none'; preview.style.display = ''; };
|
|
164
|
+
const setPreview = (entry, status, meta, content) => { clearPreviewMedia(); previewTitle.textContent = entry?.path ?? t('archive.chooseFile'); previewStatus.textContent = status; previewMeta.textContent = meta; preview.textContent = content; };
|
|
165
|
+
const MEDIA_META = { audio: 'archive.audioReady', image: 'archive.imageReady', video: 'archive.videoReady' };
|
|
166
|
+
const showMediaPreview = (entry, data, kind, mimeType) => {
|
|
167
|
+
clearPreviewMedia();
|
|
168
|
+
let objectUrl;
|
|
169
|
+
try {
|
|
170
|
+
objectUrl = createUrl(new Blob([blobPart(data)], { type: mimeType }));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
setPreview(entry, t('archive.previewUnavailable'), t('archive.mediaUnsupported'), t('archive.previewUnavailable'));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
previewUrl = objectUrl;
|
|
177
|
+
let node;
|
|
178
|
+
if (kind === 'image') {
|
|
179
|
+
const img = document.createElement('img');
|
|
180
|
+
img.className = 'omni-archive__image';
|
|
181
|
+
img.alt = entry.path;
|
|
182
|
+
img.src = objectUrl;
|
|
183
|
+
node = img;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const media = document.createElement(kind);
|
|
187
|
+
media.controls = true;
|
|
188
|
+
media.preload = 'metadata';
|
|
189
|
+
media.className = `omni-archive__${kind}`;
|
|
190
|
+
media.src = objectUrl;
|
|
191
|
+
node = media;
|
|
192
|
+
}
|
|
193
|
+
previewMedia.replaceChildren(node);
|
|
194
|
+
previewMedia.style.display = 'flex';
|
|
195
|
+
preview.style.display = 'none';
|
|
196
|
+
previewTitle.textContent = entry.path;
|
|
197
|
+
previewStatus.textContent = t('archive.ready');
|
|
198
|
+
previewMeta.textContent = t(MEDIA_META[kind]);
|
|
199
|
+
};
|
|
123
200
|
const select = async (entry) => {
|
|
124
201
|
extraction?.abort();
|
|
125
202
|
extraction = undefined;
|
|
@@ -138,7 +215,10 @@ export async function mountArchiveViewer(input, container, ctx, deps, options =
|
|
|
138
215
|
extraction = new AbortController();
|
|
139
216
|
const request = extraction;
|
|
140
217
|
setPreview(entry, t('archive.loading'), t('archive.loadingHint'), t('archive.loadingContent'));
|
|
141
|
-
const
|
|
218
|
+
const audioMime = audioMimeType(entry.path);
|
|
219
|
+
const videoMime = !audioMime ? videoMimeType(entry.path) : undefined;
|
|
220
|
+
const imageCandidate = !audioMime && !videoMime && hasImageExtension(entry.path);
|
|
221
|
+
const maxPreview = audioMime ? ARCHIVE_AUDIO_PREVIEW_BYTES : videoMime ? ARCHIVE_VIDEO_PREVIEW_BYTES : imageCandidate ? ARCHIVE_IMAGE_PREVIEW_BYTES : ARCHIVE_DEFAULT_LIMITS.maxPreviewBytes;
|
|
142
222
|
if (entry.uncompressedSize !== undefined && entry.uncompressedSize > maxPreview) {
|
|
143
223
|
setPreview(entry, t('archive.previewUnavailable'), t('diag.archive.limit-exceeded'), t('archive.previewUnavailable'));
|
|
144
224
|
return;
|
|
@@ -147,6 +227,21 @@ export async function mountArchiveViewer(input, container, ctx, deps, options =
|
|
|
147
227
|
const data = await handle.extract(entry.entryId, { signal: request.signal, maxBytes: maxPreview });
|
|
148
228
|
if (local !== ticket)
|
|
149
229
|
return;
|
|
230
|
+
if (audioMime) {
|
|
231
|
+
showMediaPreview(entry, data, 'audio', audioMime);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (videoMime) {
|
|
235
|
+
showMediaPreview(entry, data, 'video', videoMime);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (imageCandidate) {
|
|
239
|
+
const imageMime = detectImageMime(data, entry.path);
|
|
240
|
+
if (imageMime) {
|
|
241
|
+
showMediaPreview(entry, data, 'image', imageMime);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
150
245
|
const decoded = tryDecodeArchiveEntryPreview(entry.path, data);
|
|
151
246
|
const text = isText(data);
|
|
152
247
|
setPreview(entry, t('archive.ready'), t(decoded ? 'archive.androidBinaryXmlReady' : text ? 'archive.textReady' : 'archive.binaryReady'), decoded?.content ?? (text ? new TextDecoder().decode(data) : hexPreview(data)));
|
|
@@ -158,24 +253,32 @@ export async function mountArchiveViewer(input, container, ctx, deps, options =
|
|
|
158
253
|
};
|
|
159
254
|
saveButton.addEventListener('click', async () => {
|
|
160
255
|
const entry = selectedEntry;
|
|
161
|
-
|
|
162
|
-
if (!entry || entry.isDirectory || !save || !handle)
|
|
256
|
+
if (!entry || entry.isDirectory || !handle || (!ctx.save && !ctx.saveEntry))
|
|
163
257
|
return;
|
|
164
258
|
saveExtraction?.abort();
|
|
165
259
|
saveExtraction = new AbortController();
|
|
166
260
|
const request = saveExtraction;
|
|
167
261
|
saveButton.disabled = true;
|
|
168
262
|
saveButton.textContent = t('archive.savingEntry');
|
|
263
|
+
const suggestedName = entry.path.split('/').filter(Boolean).pop() ?? 'archive-entry';
|
|
169
264
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
265
|
+
// Streaming save (adapter pipes the entry to disk) is preferred: a
|
|
266
|
+
// multi-GB entry never lands in memory. Buffered save is the fallback.
|
|
267
|
+
if (ctx.saveEntry) {
|
|
268
|
+
const savedName = await ctx.saveEntry.saveEntry(entry, { signal: request.signal });
|
|
269
|
+
if (!request.signal.aborted && savedName)
|
|
270
|
+
previewMeta.textContent = t('common.saved', { name: savedName });
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const configuredLimit = options.limits?.maxDecompressedBytes ?? ARCHIVE_DEFAULT_LIMITS.maxDecompressedBytes;
|
|
274
|
+
const maxBytes = Math.max(1, Math.min(entry.uncompressedSize ?? configuredLimit, configuredLimit));
|
|
275
|
+
const data = await handle.extract(entry.entryId, { signal: request.signal, maxBytes });
|
|
276
|
+
if (request.signal.aborted)
|
|
277
|
+
return;
|
|
278
|
+
await ctx.save.saveFile(suggestedName, data, entry.mimeType ?? 'application/octet-stream');
|
|
279
|
+
if (!request.signal.aborted)
|
|
280
|
+
previewMeta.textContent = t('common.saved', { name: suggestedName });
|
|
281
|
+
}
|
|
179
282
|
}
|
|
180
283
|
catch {
|
|
181
284
|
if (!request.signal.aborted)
|
|
@@ -226,5 +329,5 @@ export async function mountArchiveViewer(input, container, ctx, deps, options =
|
|
|
226
329
|
search.addEventListener('input', () => { tableWrap.scrollTop = 0; controller.dispatch({ type: 'set-search', query: search.value }); });
|
|
227
330
|
const unsubscribe = controller.subscribe(render);
|
|
228
331
|
render();
|
|
229
|
-
return { dispose() { ticket++; extraction?.abort(); saveExtraction?.abort(); unsubscribe(); wrap.remove(); void handle?.close(); handle = undefined; } };
|
|
332
|
+
return { dispose() { ticket++; extraction?.abort(); saveExtraction?.abort(); clearPreviewMedia(); unsubscribe(); wrap.remove(); void handle?.close(); handle = undefined; } };
|
|
230
333
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const archiveViewerCss = "\n.omni-viewer--archive{all:initial}.omni-viewer--archive :where(header,section,aside,div,h1,h2,p,span,label,input,table,thead,tbody,tr,th,td,pre){all:revert}\n:host,.omni-viewer--archive{display:block;min-height:100%;box-sizing:border-box;color:var(--omni-fg,#d4d4d4);background:radial-gradient(circle at top left,color-mix(in srgb,var(--omni-accent,#d97706) 14%,transparent),transparent 28%),var(--omni-bg,#1e1e1e);font:13px var(--omni-font,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif)}\n.omni-viewer--archive *{box-sizing:border-box}.omni-viewer--archive{padding:28px 20px 40px}.omni-archive__hero,.omni-archive__controls,.omni-archive__stats,.omni-archive__table-wrap,.omni-archive__preview-panel{background:var(--omni-bg-secondary,#252526);border:1px solid var(--omni-border,#3c3c3c);border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.12)}\n.omni-archive__hero{display:flex;justify-content:space-between;gap:16px;padding:24px;align-items:flex-start}.omni-archive__eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--omni-accent,#d97706);font-weight:700}.omni-archive__hero h1{margin:8px 0 6px;font-size:clamp(26px,4vw,40px);line-height:1.1}.omni-archive__subtitle,.omni-archive__preview-meta{color:var(--omni-muted-fg,#9d9d9d);line-height:1.5}.omni-archive__pills{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.omni-archive__pill{padding:10px 14px;border-radius:999px;background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent);border:1px solid color-mix(in srgb,var(--omni-accent,#d97706) 35%,transparent);font-weight:600;white-space:nowrap}\n.omni-archive__controls,.omni-archive__stats,.omni-archive__workspace{margin-top:16px}.omni-archive__controls{padding:16px}.omni-archive__search-label{display:block;font-weight:600}.omni-archive__search{display:block;width:100%;margin-top:8px;padding:12px 14px;border-radius:12px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-input-bg,#3c3c3c);color:var(--omni-fg,#d4d4d4);font:inherit}.omni-archive__stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;padding:16px}.omni-archive__stat{padding:14px;border-radius:14px;background:color-mix(in srgb,var(--omni-bg-secondary,#252526) 76%,#000);border:1px solid var(--omni-border,#3c3c3c)}.omni-archive__stat-label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__stat-value{font-size:24px;font-weight:700}\n.omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}\n.omni-archive__preview-panel{padding:18px;min-height:420px;display:flex;flex-direction:column;gap:14px}.omni-archive__preview-header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.omni-archive__preview-kicker{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__preview-header h2{margin:0;font-size:18px;line-height:1.35;word-break:break-word}.omni-archive__preview-badge{border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;background:var(--omni-badge-bg,#3a3d41);white-space:nowrap}.omni-archive__preview-meta{margin:0}.omni-archive__preview{margin:0;flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;color:var(--omni-fg,#d4d4d4);font:12px/1.55 var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}\n.omni-archive__save{align-self:flex-start;padding:8px 12px;border:1px solid var(--omni-border,#3c3c3c);border-radius:6px;background:var(--omni-button-bg,#3a3d41);color:var(--omni-fg,#d4d4d4);font:inherit;cursor:pointer}.omni-archive__save:hover:not(:disabled){background:var(--omni-button-hover-bg,#45494e)}.omni-archive__save:disabled{opacity:.55;cursor:not-allowed}.omni-archive__spacer td{padding:0!important;border:0!important}\n@media(max-width:720px){.omni-viewer--archive{padding:16px 10px 24px}.omni-archive__hero{flex-direction:column}.omni-archive__pills{justify-content:flex-start}.omni-archive__table th:nth-child(n+3),.omni-archive__table td:nth-child(n+3){display:none}.omni-archive__workspace{grid-template-columns:1fr}.omni-archive__preview-panel{min-height:280px}}\n";
|
|
1
|
+
export declare const archiveViewerCss = "\n.omni-viewer--archive{all:initial}.omni-viewer--archive :where(header,section,aside,div,h1,h2,p,span,label,input,table,thead,tbody,tr,th,td,pre){all:revert}\n:host,.omni-viewer--archive{display:block;min-height:100%;box-sizing:border-box;color:var(--omni-fg,#d4d4d4);background:radial-gradient(circle at top left,color-mix(in srgb,var(--omni-accent,#d97706) 14%,transparent),transparent 28%),var(--omni-bg,#1e1e1e);font:13px var(--omni-font,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif)}\n.omni-viewer--archive *{box-sizing:border-box}.omni-viewer--archive{padding:28px 20px 40px}.omni-archive__hero,.omni-archive__controls,.omni-archive__stats,.omni-archive__table-wrap,.omni-archive__preview-panel{background:var(--omni-bg-secondary,#252526);border:1px solid var(--omni-border,#3c3c3c);border-radius:18px;box-shadow:0 20px 40px rgba(0,0,0,.12)}\n.omni-archive__hero{display:flex;justify-content:space-between;gap:16px;padding:24px;align-items:flex-start}.omni-archive__eyebrow{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--omni-accent,#d97706);font-weight:700}.omni-archive__hero h1{margin:8px 0 6px;font-size:clamp(26px,4vw,40px);line-height:1.1}.omni-archive__subtitle,.omni-archive__preview-meta{color:var(--omni-muted-fg,#9d9d9d);line-height:1.5}.omni-archive__pills{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.omni-archive__pill{padding:10px 14px;border-radius:999px;background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent);border:1px solid color-mix(in srgb,var(--omni-accent,#d97706) 35%,transparent);font-weight:600;white-space:nowrap}\n.omni-archive__controls,.omni-archive__stats,.omni-archive__workspace{margin-top:16px}.omni-archive__controls{padding:16px}.omni-archive__search-label{display:block;font-weight:600}.omni-archive__search{display:block;width:100%;margin-top:8px;padding:12px 14px;border-radius:12px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-input-bg,#3c3c3c);color:var(--omni-fg,#d4d4d4);font:inherit}.omni-archive__stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;padding:16px}.omni-archive__stat{padding:14px;border-radius:14px;background:color-mix(in srgb,var(--omni-bg-secondary,#252526) 76%,#000);border:1px solid var(--omni-border,#3c3c3c)}.omni-archive__stat-label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__stat-value{font-size:24px;font-weight:700}\n.omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}\n.omni-archive__preview-panel{padding:18px;min-height:420px;display:flex;flex-direction:column;gap:14px}.omni-archive__preview-header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.omni-archive__preview-kicker{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__preview-header h2{margin:0;font-size:18px;line-height:1.35;word-break:break-word}.omni-archive__preview-badge{border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;background:var(--omni-badge-bg,#3a3d41);white-space:nowrap}.omni-archive__preview-meta{margin:0}.omni-archive__preview{margin:0;flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;color:var(--omni-fg,#d4d4d4);font:12px/1.55 var(--omni-mono-font,\"SFMono-Regular\",Consolas,monospace)}\n.omni-archive__save{align-self:flex-start;padding:8px 12px;border:1px solid var(--omni-border,#3c3c3c);border-radius:6px;background:var(--omni-button-bg,#3a3d41);color:var(--omni-fg,#d4d4d4);font:inherit;cursor:pointer}.omni-archive__save:hover:not(:disabled){background:var(--omni-button-hover-bg,#45494e)}.omni-archive__save:disabled{opacity:.55;cursor:not-allowed}.omni-archive__spacer td{padding:0!important;border:0!important}\n.omni-archive__preview-media{flex:1;display:flex;align-items:center;justify-content:center;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;overflow:auto}.omni-archive__audio{width:100%}.omni-archive__video{max-width:100%;max-height:100%}.omni-archive__image{max-width:100%;max-height:100%;object-fit:contain}\n@media(max-width:720px){.omni-viewer--archive{padding:16px 10px 24px}.omni-archive__hero{flex-direction:column}.omni-archive__pills{justify-content:flex-start}.omni-archive__table th:nth-child(n+3),.omni-archive__table td:nth-child(n+3){display:none}.omni-archive__workspace{grid-template-columns:1fr}.omni-archive__preview-panel{min-height:280px}}\n";
|
|
@@ -7,5 +7,6 @@ export const archiveViewerCss = `
|
|
|
7
7
|
.omni-archive__workspace{display:grid;grid-template-columns:minmax(0,1.7fr) minmax(320px,.95fr);gap:16px;align-items:start}.omni-archive__table-wrap{overflow:auto;min-width:0;max-height:70vh}.omni-archive__table{width:100%;table-layout:fixed;border-collapse:collapse}.omni-archive__table th,.omni-archive__table td{padding:12px 14px;border-bottom:1px solid var(--omni-border,#3c3c3c);text-align:left;vertical-align:top;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.omni-archive__table th:first-child{width:42%}.omni-archive__table th{position:sticky;top:0;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--omni-muted-fg,#9d9d9d);background:var(--omni-bg-secondary,#252526);z-index:1}.omni-archive__path{font-family:var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}.omni-archive__entry{height:43px;cursor:pointer;transition:background-color 120ms ease}.omni-archive__entry:hover{background:color-mix(in srgb,var(--omni-accent,#d97706) 8%,transparent)}.omni-archive__entry:focus-visible{outline:2px solid var(--omni-accent,#d97706);outline-offset:-2px}.omni-archive__entry.is-selected{background:color-mix(in srgb,var(--omni-accent,#d97706) 16%,transparent)}.omni-archive__empty{padding:26px 18px;text-align:center;color:var(--omni-muted-fg,#9d9d9d)}
|
|
8
8
|
.omni-archive__preview-panel{padding:18px;min-height:420px;display:flex;flex-direction:column;gap:14px}.omni-archive__preview-header{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.omni-archive__preview-kicker{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--omni-muted-fg,#9d9d9d);margin-bottom:6px}.omni-archive__preview-header h2{margin:0;font-size:18px;line-height:1.35;word-break:break-word}.omni-archive__preview-badge{border-radius:999px;padding:8px 12px;font-size:12px;font-weight:700;background:var(--omni-badge-bg,#3a3d41);white-space:nowrap}.omni-archive__preview-meta{margin:0}.omni-archive__preview{margin:0;flex:1;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;color:var(--omni-fg,#d4d4d4);font:12px/1.55 var(--omni-mono-font,"SFMono-Regular",Consolas,monospace)}
|
|
9
9
|
.omni-archive__save{align-self:flex-start;padding:8px 12px;border:1px solid var(--omni-border,#3c3c3c);border-radius:6px;background:var(--omni-button-bg,#3a3d41);color:var(--omni-fg,#d4d4d4);font:inherit;cursor:pointer}.omni-archive__save:hover:not(:disabled){background:var(--omni-button-hover-bg,#45494e)}.omni-archive__save:disabled{opacity:.55;cursor:not-allowed}.omni-archive__spacer td{padding:0!important;border:0!important}
|
|
10
|
+
.omni-archive__preview-media{flex:1;display:flex;align-items:center;justify-content:center;border-radius:14px;border:1px solid var(--omni-border,#3c3c3c);background:var(--omni-code-bg,#181818);padding:14px;overflow:auto}.omni-archive__audio{width:100%}.omni-archive__video{max-width:100%;max-height:100%}.omni-archive__image{max-width:100%;max-height:100%;object-fit:contain}
|
|
10
11
|
@media(max-width:720px){.omni-viewer--archive{padding:16px 10px 24px}.omni-archive__hero{flex-direction:column}.omni-archive__pills{justify-content:flex-start}.omni-archive__table th:nth-child(n+3),.omni-archive__table td:nth-child(n+3){display:none}.omni-archive__workspace{grid-template-columns:1fr}.omni-archive__preview-panel{min-height:280px}}
|
|
11
12
|
`;
|
|
@@ -61,6 +61,14 @@ export const audioViewerCss = mediaViewerCss + `
|
|
|
61
61
|
.omni-audio__spectrogram { min-height: 0; }
|
|
62
62
|
.omni-audio__spectrogram--active { min-height: 200px; }
|
|
63
63
|
|
|
64
|
+
/* Keep the WaveSurfer playhead (cursor + played-region overlay) pinned to the
|
|
65
|
+
waveform strip. Some wavesurfer spectrogram builds render the spectrogram
|
|
66
|
+
inside the main wrapper instead of our dedicated container; without this the
|
|
67
|
+
full-height playhead sweeps down across the spectrogram ("progress bar"
|
|
68
|
+
running along the bottom) rather than staying with the waveform at the top. */
|
|
69
|
+
.omni-audio__waveform ::part(cursor),
|
|
70
|
+
.omni-audio__waveform ::part(progress) { height: 128px !important; }
|
|
71
|
+
|
|
64
72
|
.omni-audio__status { color: var(--omni-muted, #9d9d9d); font-size: 12px; min-height: 16px; }
|
|
65
73
|
|
|
66
74
|
.omni-audio__loading { display: flex; justify-content: center; align-items: center; min-height: 120px; color: var(--omni-muted, #9d9d9d); }
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
const finite = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
2
|
+
function categoryLabel(ctx, name, x, h) { const parts = String(name).split(' '); if (parts.length > 1) {
|
|
3
|
+
ctx.fillText(parts[0] ?? '', x, h - 30);
|
|
4
|
+
ctx.fillText(parts.slice(1).join(' '), x, h - 14);
|
|
5
|
+
}
|
|
6
|
+
else
|
|
7
|
+
ctx.fillText(name, x, h - 16); }
|
|
2
8
|
export function computeChartBounds(data) { const explicitMin = data.valueAxis?.min, explicitMax = data.valueAxis?.max; if (data.kind === 'stackedColumn') {
|
|
3
9
|
const positive = data.categories.map((_, i) => data.series.reduce((sum, s) => sum + Math.max(0, s.values[i] ?? 0), 0)), negative = data.categories.map((_, i) => data.series.reduce((sum, s) => sum + Math.min(0, s.values[i] ?? 0), 0));
|
|
4
10
|
const min = finite(explicitMin) ? explicitMin : Math.min(0, ...negative), max = finite(explicitMax) ? explicitMax : Math.max(1, ...positive);
|
|
@@ -57,8 +63,8 @@ function stacked(ctx, w, h, data, bounds) { const a = axes(ctx, w, h, data, boun
|
|
|
57
63
|
ctx.fillStyle = series.color || '#d10000';
|
|
58
64
|
ctx.fillRect(cx - bar / 2, top, bar, height);
|
|
59
65
|
label(ctx, series, value, cx, top + height / 2, height, a.format);
|
|
60
|
-
} ctx.fillStyle = category.color ?? '#222222'; ctx.font = `400 ${Math.max(10, category.fontSizePx ?? 11)}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic'; ctx
|
|
61
|
-
function line(ctx, w, h, data, bounds) { const a = axes(ctx, w, h, data, bounds), x = (i) => data.categories.length === 1 ? a.left + a.pw / 2 : a.left + a.pw * i / Math.max(1, data.categories.length - 1), category = data.categoryAxis ?? {}; data.categories.forEach((name, i) => { ctx.fillStyle = category.color ?? '#222222'; ctx.font = `400 ${Math.max(10, category.fontSizePx ?? 11)}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic'; ctx
|
|
66
|
+
} ctx.fillStyle = category.color ?? '#222222'; ctx.font = `400 ${Math.max(10, category.fontSizePx ?? 11)}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic'; categoryLabel(ctx, name, cx, h); }); legend(ctx, w, h, data, false); }
|
|
67
|
+
function line(ctx, w, h, data, bounds) { const a = axes(ctx, w, h, data, bounds), x = (i) => data.categories.length === 1 ? a.left + a.pw / 2 : a.left + a.pw * i / Math.max(1, data.categories.length - 1), category = data.categoryAxis ?? {}; data.categories.forEach((name, i) => { ctx.fillStyle = category.color ?? '#222222'; ctx.font = `400 ${Math.max(10, category.fontSizePx ?? 11)}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic'; categoryLabel(ctx, name, x(i), h); }); data.series.forEach((series, seriesIndex) => { const points = series.values.map((value, i) => ({ value, i })).filter(point => finite(point.value)); if (!points.length)
|
|
62
68
|
return; ctx.strokeStyle = series.color || '#4472c4'; ctx.lineWidth = 3; ctx.beginPath(); points.forEach((point, i) => i ? ctx.lineTo(x(point.i), a.y(point.value)) : ctx.moveTo(x(point.i), a.y(point.value))); ctx.stroke(); for (const point of points) {
|
|
63
69
|
const px = x(point.i), py = a.y(point.value);
|
|
64
70
|
ctx.fillStyle = '#fff';
|
|
@@ -69,11 +75,12 @@ function line(ctx, w, h, data, bounds) { const a = axes(ctx, w, h, data, bounds)
|
|
|
69
75
|
ctx.stroke();
|
|
70
76
|
const style = series.dataLabel ?? {};
|
|
71
77
|
if (style.showValue !== false) {
|
|
78
|
+
const near = py < a.top + 20;
|
|
72
79
|
ctx.fillStyle = style.color ?? series.color ?? '#222';
|
|
73
80
|
ctx.font = `600 ${Math.max(9, style.fontSizePx ?? 11)}px Arial, sans-serif`;
|
|
74
81
|
ctx.textAlign = 'center';
|
|
75
|
-
ctx.textBaseline = 'bottom';
|
|
76
|
-
ctx.fillText(formatChartValue(point.value, style.numFmt ?? series.valueFormat ?? a.format), px, py - 10 - seriesIndex * 2);
|
|
82
|
+
ctx.textBaseline = near ? 'top' : 'bottom';
|
|
83
|
+
ctx.fillText(formatChartValue(point.value, style.numFmt ?? series.valueFormat ?? a.format), px, near ? py + 18 + seriesIndex * 2 : py - 10 - seriesIndex * 2);
|
|
77
84
|
}
|
|
78
85
|
} }); legend(ctx, w, h, data, true); }
|
|
79
86
|
export function drawChart(canvas, data) { const ctx = canvas.getContext('2d'); if (!ctx || !data.categories.length || !data.series.length)
|
|
@@ -28,7 +28,7 @@ function textElement(e, fallbackColor) { const box = frame(e, 'text'); if (e.tex
|
|
|
28
28
|
if (p.bullet || (likelyList && p.text.trim())) {
|
|
29
29
|
const bullet = node('span', 'omni-ppt__bullet');
|
|
30
30
|
bullet.textContent = '•';
|
|
31
|
-
bullet.style
|
|
31
|
+
Object.assign(bullet.style, { display: 'inline-block', width: '26px', marginRight: '6px', textAlign: 'center', color: p.color ?? fallbackColor });
|
|
32
32
|
line.append(bullet);
|
|
33
33
|
}
|
|
34
34
|
if (p.runs?.length) {
|
|
@@ -48,12 +48,37 @@ function textElement(e, fallbackColor) { const box = frame(e, 'text'); if (e.tex
|
|
|
48
48
|
else
|
|
49
49
|
line.append(document.createTextNode(p.text));
|
|
50
50
|
box.append(line);
|
|
51
|
-
}
|
|
51
|
+
} requestAnimationFrame(() => { if (box.scrollHeight > box.clientHeight + 2) {
|
|
52
|
+
const ratio = box.clientHeight / box.scrollHeight;
|
|
53
|
+
box.querySelectorAll('p.omni-ppt__text-line').forEach(line => { const cur = parseFloat(line.style.fontSize) || 24, next = Math.max(9, Math.floor(cur * ratio)); line.style.fontSize = `${next}px`; line.style.lineHeight = `${Math.max(12, Math.round(next * 1.18))}px`; });
|
|
54
|
+
} }); return box; }
|
|
55
|
+
function normalizeVectorFallbackLogo(img) { const w = img.naturalWidth || img.width, h = img.naturalHeight || img.height; if (!w || !h)
|
|
56
|
+
return null; const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; const ctx = canvas.getContext('2d'); if (!ctx)
|
|
57
|
+
return null; ctx.drawImage(img, 0, 0, w, h); const image = ctx.getImageData(0, 0, w, h), data = image.data, target = { r: 237, g: 30, b: 68 }; for (let i = 0; i < data.length; i += 4) {
|
|
58
|
+
const r = data[i], g = data[i + 1], b = data[i + 2], a = data[i + 3];
|
|
59
|
+
if (a === 0)
|
|
60
|
+
continue;
|
|
61
|
+
if (r > 130 && r > g * 1.25 && r > b * 1.25) {
|
|
62
|
+
data[i + 3] = 0;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const alphaRatio = Math.max(0, Math.min(1, (r + g + b) / 3 / 255));
|
|
66
|
+
data[i] = target.r;
|
|
67
|
+
data[i + 1] = target.g;
|
|
68
|
+
data[i + 2] = target.b;
|
|
69
|
+
data[i + 3] = Math.round(a * alphaRatio);
|
|
70
|
+
} ctx.putImageData(image, 0, 0); return canvas.toDataURL('image/png'); }
|
|
52
71
|
function imageElement(e) { const box = frame(e, 'image'), img = node('img', 'omni-ppt__image'); img.src = e.src ?? ''; img.alt = ''; const crop = e.srcRect; if (crop && (crop.l || crop.t || crop.r || crop.b)) {
|
|
53
72
|
const visibleW = Math.max(1, 100 - crop.l - crop.r), visibleH = Math.max(1, 100 - crop.t - crop.b), scaleW = 100 / visibleW, scaleH = 100 / visibleH;
|
|
54
73
|
Object.assign(img.style, { position: 'absolute', width: `${100 * scaleW}%`, height: `${100 * scaleH}%`, left: `${-crop.l * scaleW}%`, top: `${-crop.t * scaleH}%`, objectFit: 'fill', maxWidth: 'none', maxHeight: 'none' });
|
|
55
74
|
box.style.overflow = 'hidden';
|
|
56
|
-
}
|
|
75
|
+
} if (e.vectorFallback)
|
|
76
|
+
img.addEventListener('load', () => { try {
|
|
77
|
+
const recolored = normalizeVectorFallbackLogo(img);
|
|
78
|
+
if (recolored)
|
|
79
|
+
img.src = recolored;
|
|
80
|
+
}
|
|
81
|
+
catch { /* keep the original raster when recoloring fails */ } }, { once: true }); box.append(img); return box; }
|
|
57
82
|
function tableElement(e) { const box = frame(e, 'table'), table = node('table', 'omni-ppt__table'); (e.tableRows ?? []).forEach((row, rowIndex) => { const tr = node('tr'); row.forEach(value => { const cell = node(rowIndex === 0 ? 'th' : 'td'); cell.textContent = value; tr.append(cell); }); table.append(tr); }); box.append(table); return box; }
|
|
58
83
|
function chartElement(e) { const box = frame(e, 'chart'); if (e.chartData) {
|
|
59
84
|
const canvas = node('canvas');
|
|
@@ -115,6 +140,8 @@ function shapeElement(e) { const box = frame(e, 'shape'), w = Math.max(1, Math.a
|
|
|
115
140
|
line.setAttribute('marker-start', start);
|
|
116
141
|
if (end)
|
|
117
142
|
line.setAttribute('marker-end', end);
|
|
143
|
+
if (!e.headEnd && !e.tailEnd && w + h > 20)
|
|
144
|
+
line.setAttribute('marker-end', `url(#arrow-${suffix})`);
|
|
118
145
|
svg.append(line);
|
|
119
146
|
box.style.overflow = 'visible';
|
|
120
147
|
box.append(svg);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const pptViewerCss = ":host,.omni-viewer--ppt{color:var(--omni-fg,#ddd);font:13px system-ui}.omni-ppt{height:100%;display:flex;flex-direction:column;background:var(--omni-bg,#181818)}.omni-ppt__toolbar{display:flex;gap:6px;align-items:center;padding:8px;border-bottom:1px solid var(--omni-border,#444)}.omni-ppt__toolbar button,.omni-ppt__toolbar select{background:var(--omni-control-bg,#292929);color:inherit;border:1px solid var(--omni-border,#555);border-radius:4px;padding:4px 8px}.omni-ppt__viewport{overflow:auto;flex:1;padding:20px}.omni-ppt__slides{display:flex;flex-direction:column;align-items:center;gap:20px}.omni-ppt__slide{position:relative;overflow:hidden;box-shadow:0 2px 12px #0008;transform-origin:top center;flex:none}.omni-ppt__element{position:absolute;box-sizing:border-box;overflow:hidden}.omni-ppt__text{white-space:pre-wrap}.omni-ppt__text p{margin:0}.omni-ppt__image{width:100%;height:100%;object-fit:
|
|
1
|
+
export declare const pptViewerCss = ":host,.omni-viewer--ppt{color:var(--omni-fg,#ddd);font:13px system-ui}.omni-ppt{height:100%;display:flex;flex-direction:column;background:var(--omni-bg,#181818)}.omni-ppt__toolbar{display:flex;gap:6px;align-items:center;padding:8px;border-bottom:1px solid var(--omni-border,#444)}.omni-ppt__toolbar button,.omni-ppt__toolbar select{background:var(--omni-control-bg,#292929);color:inherit;border:1px solid var(--omni-border,#555);border-radius:4px;padding:4px 8px}.omni-ppt__viewport{overflow:auto;flex:1;padding:20px}.omni-ppt__slides{display:flex;flex-direction:column;align-items:center;gap:20px}.omni-ppt__slide{position:relative;overflow:hidden;box-shadow:0 2px 12px #0008;transform-origin:top center;flex:none}.omni-ppt__element{position:absolute;box-sizing:border-box;overflow:hidden}.omni-ppt__text{white-space:pre-wrap;word-break:break-word}.omni-ppt__text p{margin:0 0 4px}.omni-ppt__text[data-text-preset=\"cover-title\"] .omni-ppt__text-line,.omni-ppt__text[data-text-preset=\"cover-subtitle\"] .omni-ppt__text-line{margin-bottom:0}.omni-ppt__image{width:100%;height:100%;object-fit:cover;object-position:center}.omni-ppt__table{border-collapse:collapse;width:100%;height:100%;color:#111}.omni-ppt__table th,.omni-ppt__table td{border:1px solid #777;padding:3px;vertical-align:top}.omni-ppt__shape{width:100%;height:100%}.omni-ppt__empty{padding:32px;text-align:center}";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const pptViewerCss = `:host,.omni-viewer--ppt{color:var(--omni-fg,#ddd);font:13px system-ui}.omni-ppt{height:100%;display:flex;flex-direction:column;background:var(--omni-bg,#181818)}.omni-ppt__toolbar{display:flex;gap:6px;align-items:center;padding:8px;border-bottom:1px solid var(--omni-border,#444)}.omni-ppt__toolbar button,.omni-ppt__toolbar select{background:var(--omni-control-bg,#292929);color:inherit;border:1px solid var(--omni-border,#555);border-radius:4px;padding:4px 8px}.omni-ppt__viewport{overflow:auto;flex:1;padding:20px}.omni-ppt__slides{display:flex;flex-direction:column;align-items:center;gap:20px}.omni-ppt__slide{position:relative;overflow:hidden;box-shadow:0 2px 12px #0008;transform-origin:top center;flex:none}.omni-ppt__element{position:absolute;box-sizing:border-box;overflow:hidden}.omni-ppt__text{white-space:pre-wrap}.omni-ppt__text p{margin:0}.omni-ppt__image{width:100%;height:100%;object-fit:
|
|
1
|
+
export const pptViewerCss = `:host,.omni-viewer--ppt{color:var(--omni-fg,#ddd);font:13px system-ui}.omni-ppt{height:100%;display:flex;flex-direction:column;background:var(--omni-bg,#181818)}.omni-ppt__toolbar{display:flex;gap:6px;align-items:center;padding:8px;border-bottom:1px solid var(--omni-border,#444)}.omni-ppt__toolbar button,.omni-ppt__toolbar select{background:var(--omni-control-bg,#292929);color:inherit;border:1px solid var(--omni-border,#555);border-radius:4px;padding:4px 8px}.omni-ppt__viewport{overflow:auto;flex:1;padding:20px}.omni-ppt__slides{display:flex;flex-direction:column;align-items:center;gap:20px}.omni-ppt__slide{position:relative;overflow:hidden;box-shadow:0 2px 12px #0008;transform-origin:top center;flex:none}.omni-ppt__element{position:absolute;box-sizing:border-box;overflow:hidden}.omni-ppt__text{white-space:pre-wrap;word-break:break-word}.omni-ppt__text p{margin:0 0 4px}.omni-ppt__text[data-text-preset="cover-title"] .omni-ppt__text-line,.omni-ppt__text[data-text-preset="cover-subtitle"] .omni-ppt__text-line{margin-bottom:0}.omni-ppt__image{width:100%;height:100%;object-fit:cover;object-position:center}.omni-ppt__table{border-collapse:collapse;width:100%;height:100%;color:#111}.omni-ppt__table th,.omni-ppt__table td{border:1px solid #777;padding:3px;vertical-align:top}.omni-ppt__shape{width:100%;height:100%}.omni-ppt__empty{padding:32px;text-align:center}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-viewer-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared parsing + rendering core for Omni Viewer (vscode / chrome / obsidian / web)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
"pdf-lib": "^1.17.1",
|
|
315
315
|
"pdfjs-dist": "^4.10.38",
|
|
316
316
|
"proj4": "^2.20.9",
|
|
317
|
-
"puml-canvas-js": "0.
|
|
317
|
+
"puml-canvas-js": ">=0.2.0",
|
|
318
318
|
"wavesurfer.js": "^7.8.0",
|
|
319
319
|
"xlsx": ">=0.20.2",
|
|
320
320
|
"yaml": "2.8.3"
|