@eclipse-lyra/extension-media-viewer 0.0.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.json.d.ts +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/media-viewer-extension-B1XTrSmY.js +275 -0
- package/dist/media-viewer-extension-B1XTrSmY.js.map +1 -0
- package/dist/media-viewer-extension.d.ts +50 -0
- package/dist/media-viewer-extension.d.ts.map +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
"namespace": "extensions",
|
|
3
|
+
"en": {
|
|
4
|
+
"EXT_MEDIAVIEWER_NAME": "Media Viewer",
|
|
5
|
+
"EXT_MEDIAVIEWER_DESC": "View PDF, image, and HTML files in the browser"
|
|
6
|
+
},
|
|
7
|
+
"de": {
|
|
8
|
+
"EXT_MEDIAVIEWER_NAME": "Medien-Viewer",
|
|
9
|
+
"EXT_MEDIAVIEWER_DESC": "PDF-, Bild- und HTML-Dateien im Browser anzeigen"
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default _default;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { contributionRegistry, SYSTEM_LANGUAGE_BUNDLES, i18nLazy, extensionRegistry } from "@eclipse-lyra/core";
|
|
2
|
+
import pkg from "../package.json";
|
|
3
|
+
const namespace = "extensions";
|
|
4
|
+
const en = { "EXT_MEDIAVIEWER_NAME": "Media Viewer", "EXT_MEDIAVIEWER_DESC": "View PDF, image, and HTML files in the browser" };
|
|
5
|
+
const de = { "EXT_MEDIAVIEWER_NAME": "Medien-Viewer", "EXT_MEDIAVIEWER_DESC": "PDF-, Bild- und HTML-Dateien im Browser anzeigen" };
|
|
6
|
+
const bundle = {
|
|
7
|
+
namespace,
|
|
8
|
+
en,
|
|
9
|
+
de
|
|
10
|
+
};
|
|
11
|
+
contributionRegistry.registerContribution(SYSTEM_LANGUAGE_BUNDLES, bundle);
|
|
12
|
+
const t = i18nLazy("extensions");
|
|
13
|
+
extensionRegistry.registerExtension({
|
|
14
|
+
id: pkg.name,
|
|
15
|
+
name: t("EXT_MEDIAVIEWER_NAME"),
|
|
16
|
+
description: t("EXT_MEDIAVIEWER_DESC"),
|
|
17
|
+
loader: () => import("./media-viewer-extension-B1XTrSmY.js"),
|
|
18
|
+
icon: "image"
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { extensionRegistry, i18nLazy, contributionRegistry, SYSTEM_LANGUAGE_BUNDLES } from '@eclipse-lyra/core';\nimport bundle from './i18n.json';\nimport pkg from '../package.json';\n\ncontributionRegistry.registerContribution(SYSTEM_LANGUAGE_BUNDLES, bundle as any);\n\nconst t = i18nLazy('extensions');\n\nextensionRegistry.registerExtension({\n id: pkg.name,\n name: t('EXT_MEDIAVIEWER_NAME'),\n description: t('EXT_MEDIAVIEWER_DESC'),\n loader: () => import(\"./media-viewer-extension\"),\n icon: \"image\",\n \n \n});\n"],"names":[],"mappings":";;;;;;;;;;AAIA,qBAAqB,qBAAqB,yBAAyB,MAAa;AAEhF,MAAM,IAAI,SAAS,YAAY;AAE/B,kBAAkB,kBAAkB;AAAA,EAClC,IAAI,IAAI;AAAA,EACR,MAAM,EAAE,sBAAsB;AAAA,EAC9B,aAAa,EAAE,sBAAsB;AAAA,EACrC,QAAQ,MAAM,OAAO,sCAA0B;AAAA,EAC/C,MAAM;AAGR,CAAC;"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { property, state, customElement } from "lit/decorators.js";
|
|
2
|
+
import { html, nothing, css } from "lit";
|
|
3
|
+
import { LyraPart, File, editorRegistry } from "@eclipse-lyra/core";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
7
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
8
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
9
|
+
if (decorator = decorators[i])
|
|
10
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
11
|
+
if (kind && result) __defProp(target, key, result);
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
const SUPPORTED_EXTENSIONS = [
|
|
15
|
+
".pdf",
|
|
16
|
+
".jpg",
|
|
17
|
+
".jpeg",
|
|
18
|
+
".png",
|
|
19
|
+
".gif",
|
|
20
|
+
".svg",
|
|
21
|
+
".webp",
|
|
22
|
+
".bmp",
|
|
23
|
+
".ico"
|
|
24
|
+
];
|
|
25
|
+
const getFileIcon = (fileName) => {
|
|
26
|
+
const lowerName = fileName.toLowerCase();
|
|
27
|
+
if (lowerName.endsWith(".pdf")) return "file-pdf";
|
|
28
|
+
if (lowerName.match(/\.(jpg|jpeg|png|gif|webp|bmp|ico|svg)$/)) return "image";
|
|
29
|
+
return "file";
|
|
30
|
+
};
|
|
31
|
+
const isSupportedMediaFile = (file) => {
|
|
32
|
+
const fileName = file.getName().toLowerCase();
|
|
33
|
+
return SUPPORTED_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
34
|
+
};
|
|
35
|
+
const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp", ".bmp", ".ico"];
|
|
36
|
+
const isImageFile = (file) => {
|
|
37
|
+
const name = file.getName().toLowerCase();
|
|
38
|
+
return IMAGE_EXTENSIONS.some((ext) => name.endsWith(ext));
|
|
39
|
+
};
|
|
40
|
+
editorRegistry.registerEditorInputHandler({
|
|
41
|
+
editorId: "system.media-viewer",
|
|
42
|
+
label: "Media viewer",
|
|
43
|
+
canHandle: (input) => input instanceof File && isSupportedMediaFile(input),
|
|
44
|
+
handle: async (input) => {
|
|
45
|
+
const editorInput = {
|
|
46
|
+
title: input.getName(),
|
|
47
|
+
data: input,
|
|
48
|
+
key: input.getName(),
|
|
49
|
+
icon: getFileIcon(input.getName()),
|
|
50
|
+
noOverflow: false,
|
|
51
|
+
state: {}
|
|
52
|
+
};
|
|
53
|
+
editorInput.widgetFactory = () => html`
|
|
54
|
+
<lyra-media-viewer .input=${editorInput}></lyra-media-viewer>`;
|
|
55
|
+
return editorInput;
|
|
56
|
+
},
|
|
57
|
+
ranking: 1e3
|
|
58
|
+
});
|
|
59
|
+
let LyraMediaViewer = class extends LyraPart {
|
|
60
|
+
constructor() {
|
|
61
|
+
super(...arguments);
|
|
62
|
+
this.imageNaturalWidth = 0;
|
|
63
|
+
this.imageNaturalHeight = 0;
|
|
64
|
+
this.overlays = [];
|
|
65
|
+
this.onImageLoad = (e) => {
|
|
66
|
+
const img = e.target;
|
|
67
|
+
if (img?.naturalWidth && img?.naturalHeight) {
|
|
68
|
+
this.imageNaturalWidth = img.naturalWidth;
|
|
69
|
+
this.imageNaturalHeight = img.naturalHeight;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
getMediaUrl() {
|
|
74
|
+
return this.mediaUrl;
|
|
75
|
+
}
|
|
76
|
+
getIsImage() {
|
|
77
|
+
return !!(this.input?.data instanceof File && this.mediaUrl && isImageFile(this.input.data));
|
|
78
|
+
}
|
|
79
|
+
getImageDimensions() {
|
|
80
|
+
return { width: this.imageNaturalWidth, height: this.imageNaturalHeight };
|
|
81
|
+
}
|
|
82
|
+
/** Set overlays drawn on the image (e.g. from object detection or other extensions). Coordinates in 0–1. */
|
|
83
|
+
setOverlays(overlays) {
|
|
84
|
+
this.overlays = overlays ?? [];
|
|
85
|
+
}
|
|
86
|
+
getOverlays() {
|
|
87
|
+
return [...this.overlays];
|
|
88
|
+
}
|
|
89
|
+
clearOverlays() {
|
|
90
|
+
this.overlays = [];
|
|
91
|
+
}
|
|
92
|
+
doClose() {
|
|
93
|
+
if (this.mediaUrl && this.mediaUrl.startsWith("blob:")) {
|
|
94
|
+
URL.revokeObjectURL(this.mediaUrl);
|
|
95
|
+
}
|
|
96
|
+
this.input = void 0;
|
|
97
|
+
this.mediaUrl = void 0;
|
|
98
|
+
this.overlays = [];
|
|
99
|
+
this.imageNaturalWidth = 0;
|
|
100
|
+
this.imageNaturalHeight = 0;
|
|
101
|
+
}
|
|
102
|
+
async doInitUI() {
|
|
103
|
+
await this.loadMedia();
|
|
104
|
+
}
|
|
105
|
+
async loadMedia() {
|
|
106
|
+
if (!this.input?.data || !(this.input.data instanceof File)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const file = this.input.data;
|
|
110
|
+
const url = await file.getContents({ uri: true });
|
|
111
|
+
this.mediaUrl = url;
|
|
112
|
+
this.overlays = [];
|
|
113
|
+
}
|
|
114
|
+
render() {
|
|
115
|
+
if (!this.mediaUrl) {
|
|
116
|
+
return html`
|
|
117
|
+
<div style="display: flex; align-items: center; justify-content: center; height: 100%;">
|
|
118
|
+
<wa-spinner></wa-spinner>
|
|
119
|
+
</div>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
const isImage = this.getIsImage();
|
|
123
|
+
if (isImage) {
|
|
124
|
+
const w = this.imageNaturalWidth || 1;
|
|
125
|
+
const h = this.imageNaturalHeight || 1;
|
|
126
|
+
const aspectRatio = w / h;
|
|
127
|
+
return html`
|
|
128
|
+
<div class="image-container">
|
|
129
|
+
<div
|
|
130
|
+
class="image-wrapper"
|
|
131
|
+
style="aspect-ratio: ${aspectRatio};"
|
|
132
|
+
>
|
|
133
|
+
<img
|
|
134
|
+
class="media-image"
|
|
135
|
+
src="${this.mediaUrl}"
|
|
136
|
+
alt=""
|
|
137
|
+
@load=${this.onImageLoad}
|
|
138
|
+
/>
|
|
139
|
+
${this.overlays.length > 0 ? html`
|
|
140
|
+
<div class="overlay-layer">
|
|
141
|
+
${this.overlays.map((o) => o.type === "bbox" ? html`
|
|
142
|
+
<div
|
|
143
|
+
class="overlay-bbox"
|
|
144
|
+
style="left: ${o.left * 100}%; top: ${o.top * 100}%; width: ${o.width * 100}%; height: ${o.height * 100}%;${o.color ? ` --overlay-color: ${o.color};` : ""}"
|
|
145
|
+
title="${o.label ?? ""}"
|
|
146
|
+
>
|
|
147
|
+
${o.label ? html`<span class="overlay-label">${o.label}</span>` : nothing}
|
|
148
|
+
</div>
|
|
149
|
+
` : o.type === "mask" ? html`
|
|
150
|
+
<img
|
|
151
|
+
class="overlay-mask"
|
|
152
|
+
src="${o.dataUrl}"
|
|
153
|
+
alt="${o.label ?? ""}"
|
|
154
|
+
title="${o.label ?? ""}"
|
|
155
|
+
/>
|
|
156
|
+
` : nothing)}
|
|
157
|
+
</div>
|
|
158
|
+
` : nothing}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
return html`
|
|
164
|
+
<div class="media-iframe-container">
|
|
165
|
+
<iframe
|
|
166
|
+
src="${this.mediaUrl}"
|
|
167
|
+
class="media-iframe"
|
|
168
|
+
title="Media Viewer"></iframe>
|
|
169
|
+
</div>
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
LyraMediaViewer.styles = css`
|
|
174
|
+
:host {
|
|
175
|
+
display: flex;
|
|
176
|
+
flex-direction: column;
|
|
177
|
+
position: relative;
|
|
178
|
+
width: 100%;
|
|
179
|
+
height: 100%;
|
|
180
|
+
background: var(--wa-color-surface-default, #1a1a1a);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.image-container {
|
|
184
|
+
position: relative;
|
|
185
|
+
width: 100%;
|
|
186
|
+
height: 100%;
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: center;
|
|
190
|
+
overflow: hidden;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.image-wrapper {
|
|
194
|
+
position: relative;
|
|
195
|
+
max-width: 100%;
|
|
196
|
+
max-height: 100%;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.media-iframe-container {
|
|
200
|
+
position: absolute;
|
|
201
|
+
inset: 0;
|
|
202
|
+
min-height: 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.media-iframe {
|
|
206
|
+
display: block;
|
|
207
|
+
width: 100%;
|
|
208
|
+
height: 100%;
|
|
209
|
+
border: 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.media-image {
|
|
213
|
+
display: block;
|
|
214
|
+
width: 100%;
|
|
215
|
+
height: 100%;
|
|
216
|
+
object-fit: contain;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.overlay-layer {
|
|
220
|
+
position: absolute;
|
|
221
|
+
inset: 0;
|
|
222
|
+
pointer-events: none;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.overlay-bbox {
|
|
226
|
+
position: absolute;
|
|
227
|
+
border: 2px solid var(--overlay-color, var(--wa-color-brand-fill, #0d6efd));
|
|
228
|
+
background: color-mix(in srgb, var(--overlay-color, #0d6efd) 15%, transparent);
|
|
229
|
+
pointer-events: auto;
|
|
230
|
+
box-sizing: border-box;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.overlay-label {
|
|
234
|
+
position: absolute;
|
|
235
|
+
left: 0;
|
|
236
|
+
top: -1.25em;
|
|
237
|
+
font-size: 0.7rem;
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
background: var(--overlay-color, var(--wa-color-brand-fill, #0d6efd));
|
|
240
|
+
color: var(--wa-color-surface-default, #fff);
|
|
241
|
+
padding: 1px 4px;
|
|
242
|
+
border-radius: 2px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.overlay-mask {
|
|
246
|
+
position: absolute;
|
|
247
|
+
inset: 0;
|
|
248
|
+
width: 100%;
|
|
249
|
+
height: 100%;
|
|
250
|
+
object-fit: fill;
|
|
251
|
+
pointer-events: none;
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
__decorateClass([
|
|
255
|
+
property({ attribute: false })
|
|
256
|
+
], LyraMediaViewer.prototype, "input", 2);
|
|
257
|
+
__decorateClass([
|
|
258
|
+
state()
|
|
259
|
+
], LyraMediaViewer.prototype, "mediaUrl", 2);
|
|
260
|
+
__decorateClass([
|
|
261
|
+
state()
|
|
262
|
+
], LyraMediaViewer.prototype, "imageNaturalWidth", 2);
|
|
263
|
+
__decorateClass([
|
|
264
|
+
state()
|
|
265
|
+
], LyraMediaViewer.prototype, "imageNaturalHeight", 2);
|
|
266
|
+
__decorateClass([
|
|
267
|
+
state()
|
|
268
|
+
], LyraMediaViewer.prototype, "overlays", 2);
|
|
269
|
+
LyraMediaViewer = __decorateClass([
|
|
270
|
+
customElement("lyra-media-viewer")
|
|
271
|
+
], LyraMediaViewer);
|
|
272
|
+
export {
|
|
273
|
+
LyraMediaViewer
|
|
274
|
+
};
|
|
275
|
+
//# sourceMappingURL=media-viewer-extension-B1XTrSmY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-viewer-extension-B1XTrSmY.js","sources":["../src/media-viewer-extension.ts"],"sourcesContent":["import { customElement, property, state } from \"lit/decorators.js\";\nimport { css, html, nothing } from \"lit\";\nimport { LyraPart, EditorInput, editorRegistry, File } from \"@eclipse-lyra/core\";\n\nconst SUPPORTED_EXTENSIONS = [\n '.pdf',\n '.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.bmp', '.ico'\n];\n\nconst getFileIcon = (fileName: string): string => {\n const lowerName = fileName.toLowerCase();\n if (lowerName.endsWith('.pdf')) return 'file-pdf';\n if (lowerName.match(/\\.(jpg|jpeg|png|gif|webp|bmp|ico|svg)$/)) return 'image';\n return 'file';\n};\n\nconst isSupportedMediaFile = (file: File): boolean => {\n const fileName = file.getName().toLowerCase();\n return SUPPORTED_EXTENSIONS.some(ext => fileName.endsWith(ext));\n};\n\nconst IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.bmp', '.ico'];\n\nconst isImageFile = (file: File): boolean => {\n const name = file.getName().toLowerCase();\n return IMAGE_EXTENSIONS.some(ext => name.endsWith(ext));\n};\n\n/** Normalized bounding box overlay (0–1). Used by extensions to draw on the image. */\nexport interface BboxOverlay {\n type: 'bbox';\n left: number;\n top: number;\n width: number;\n height: number;\n label?: string;\n /** CSS color for border and label (e.g. by category). */\n color?: string;\n}\n\n/** Segment mask overlay: PNG data URL (alpha where segment is), drawn over the image. */\nexport interface MaskOverlay {\n type: 'mask';\n /** Data URL of the mask image (e.g. image/png with alpha). */\n dataUrl: string;\n label?: string;\n color?: string;\n}\n\nexport type MediaViewerOverlay = BboxOverlay | MaskOverlay;\n\neditorRegistry.registerEditorInputHandler({\n editorId: \"system.media-viewer\",\n label: \"Media viewer\",\n canHandle: input => input instanceof File && isSupportedMediaFile(input),\n handle: async (input: File) => {\n const editorInput = {\n title: input.getName(),\n data: input,\n key: input.getName(),\n icon: getFileIcon(input.getName()),\n noOverflow: false,\n state: {},\n } as EditorInput\n editorInput.widgetFactory = () => html`\n <lyra-media-viewer .input=${editorInput}></lyra-media-viewer>`\n return editorInput;\n },\n ranking: 1000\n})\n\n@customElement('lyra-media-viewer')\nexport class LyraMediaViewer extends LyraPart {\n @property({attribute: false})\n public input?: EditorInput\n\n @state()\n private mediaUrl?: string\n\n @state()\n private imageNaturalWidth = 0\n\n @state()\n private imageNaturalHeight = 0\n\n @state()\n private overlays: MediaViewerOverlay[] = []\n\n getMediaUrl(): string | undefined {\n return this.mediaUrl;\n }\n\n getIsImage(): boolean {\n return !!(this.input?.data instanceof File && this.mediaUrl && isImageFile(this.input.data));\n }\n\n getImageDimensions(): { width: number; height: number } {\n return { width: this.imageNaturalWidth, height: this.imageNaturalHeight };\n }\n\n /** Set overlays drawn on the image (e.g. from object detection or other extensions). Coordinates in 0–1. */\n setOverlays(overlays: MediaViewerOverlay[]): void {\n this.overlays = overlays ?? [];\n }\n\n getOverlays(): MediaViewerOverlay[] {\n return [...this.overlays];\n }\n\n clearOverlays(): void {\n this.overlays = [];\n }\n\n protected doClose() {\n if (this.mediaUrl && this.mediaUrl.startsWith('blob:')) {\n URL.revokeObjectURL(this.mediaUrl)\n }\n this.input = undefined\n this.mediaUrl = undefined\n this.overlays = []\n this.imageNaturalWidth = 0\n this.imageNaturalHeight = 0\n }\n\n protected async doInitUI() {\n await this.loadMedia()\n }\n\n private async loadMedia() {\n if (!this.input?.data || !(this.input.data instanceof File)) {\n return\n }\n\n const file = this.input.data\n const url = await file.getContents({uri: true}) as string\n this.mediaUrl = url\n this.overlays = []\n }\n\n private onImageLoad = (e: Event): void => {\n const img = e.target as HTMLImageElement;\n if (img?.naturalWidth && img?.naturalHeight) {\n this.imageNaturalWidth = img.naturalWidth;\n this.imageNaturalHeight = img.naturalHeight;\n }\n }\n\n protected render() {\n if (!this.mediaUrl) {\n return html`\n <div style=\"display: flex; align-items: center; justify-content: center; height: 100%;\">\n <wa-spinner></wa-spinner>\n </div>\n `\n }\n\n const isImage = this.getIsImage();\n if (isImage) {\n const w = this.imageNaturalWidth || 1;\n const h = this.imageNaturalHeight || 1;\n const aspectRatio = w / h;\n return html`\n <div class=\"image-container\">\n <div\n class=\"image-wrapper\"\n style=\"aspect-ratio: ${aspectRatio};\"\n >\n <img\n class=\"media-image\"\n src=\"${this.mediaUrl}\"\n alt=\"\"\n @load=${this.onImageLoad}\n />\n ${this.overlays.length > 0 ? html`\n <div class=\"overlay-layer\">\n ${this.overlays.map((o) => o.type === 'bbox' ? html`\n <div\n class=\"overlay-bbox\"\n style=\"left: ${o.left * 100}%; top: ${o.top * 100}%; width: ${o.width * 100}%; height: ${o.height * 100}%;${o.color ? ` --overlay-color: ${o.color};` : ''}\"\n title=\"${o.label ?? ''}\"\n >\n ${o.label ? html`<span class=\"overlay-label\">${o.label}</span>` : nothing}\n </div>\n ` : o.type === 'mask' ? html`\n <img\n class=\"overlay-mask\"\n src=\"${o.dataUrl}\"\n alt=\"${o.label ?? ''}\"\n title=\"${o.label ?? ''}\"\n />\n ` : nothing)}\n </div>\n ` : nothing}\n </div>\n </div>\n `;\n }\n\n return html`\n <div class=\"media-iframe-container\">\n <iframe\n src=\"${this.mediaUrl}\"\n class=\"media-iframe\"\n title=\"Media Viewer\"></iframe>\n </div>\n `\n }\n\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--wa-color-surface-default, #1a1a1a);\n }\n\n .image-container {\n position: relative;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n\n .image-wrapper {\n position: relative;\n max-width: 100%;\n max-height: 100%;\n }\n\n .media-iframe-container {\n position: absolute;\n inset: 0;\n min-height: 0;\n }\n\n .media-iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n }\n\n .media-image {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: contain;\n }\n\n .overlay-layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n }\n\n .overlay-bbox {\n position: absolute;\n border: 2px solid var(--overlay-color, var(--wa-color-brand-fill, #0d6efd));\n background: color-mix(in srgb, var(--overlay-color, #0d6efd) 15%, transparent);\n pointer-events: auto;\n box-sizing: border-box;\n }\n\n .overlay-label {\n position: absolute;\n left: 0;\n top: -1.25em;\n font-size: 0.7rem;\n white-space: nowrap;\n background: var(--overlay-color, var(--wa-color-brand-fill, #0d6efd));\n color: var(--wa-color-surface-default, #fff);\n padding: 1px 4px;\n border-radius: 2px;\n }\n\n .overlay-mask {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: fill;\n pointer-events: none;\n }\n `\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lyra-media-viewer': LyraMediaViewer\n }\n}\n\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { LyraPart, EditorInput } from '@eclipse-lyra/core';
|
|
2
|
+
/** Normalized bounding box overlay (0–1). Used by extensions to draw on the image. */
|
|
3
|
+
export interface BboxOverlay {
|
|
4
|
+
type: 'bbox';
|
|
5
|
+
left: number;
|
|
6
|
+
top: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
label?: string;
|
|
10
|
+
/** CSS color for border and label (e.g. by category). */
|
|
11
|
+
color?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Segment mask overlay: PNG data URL (alpha where segment is), drawn over the image. */
|
|
14
|
+
export interface MaskOverlay {
|
|
15
|
+
type: 'mask';
|
|
16
|
+
/** Data URL of the mask image (e.g. image/png with alpha). */
|
|
17
|
+
dataUrl: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
color?: string;
|
|
20
|
+
}
|
|
21
|
+
export type MediaViewerOverlay = BboxOverlay | MaskOverlay;
|
|
22
|
+
export declare class LyraMediaViewer extends LyraPart {
|
|
23
|
+
input?: EditorInput;
|
|
24
|
+
private mediaUrl?;
|
|
25
|
+
private imageNaturalWidth;
|
|
26
|
+
private imageNaturalHeight;
|
|
27
|
+
private overlays;
|
|
28
|
+
getMediaUrl(): string | undefined;
|
|
29
|
+
getIsImage(): boolean;
|
|
30
|
+
getImageDimensions(): {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
};
|
|
34
|
+
/** Set overlays drawn on the image (e.g. from object detection or other extensions). Coordinates in 0–1. */
|
|
35
|
+
setOverlays(overlays: MediaViewerOverlay[]): void;
|
|
36
|
+
getOverlays(): MediaViewerOverlay[];
|
|
37
|
+
clearOverlays(): void;
|
|
38
|
+
protected doClose(): void;
|
|
39
|
+
protected doInitUI(): Promise<void>;
|
|
40
|
+
private loadMedia;
|
|
41
|
+
private onImageLoad;
|
|
42
|
+
protected render(): import('lit-html').TemplateResult<1>;
|
|
43
|
+
static styles: import('lit').CSSResult;
|
|
44
|
+
}
|
|
45
|
+
declare global {
|
|
46
|
+
interface HTMLElementTagNameMap {
|
|
47
|
+
'lyra-media-viewer': LyraMediaViewer;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=media-viewer-extension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-viewer-extension.d.ts","sourceRoot":"","sources":["../src/media-viewer-extension.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAwB,MAAM,oBAAoB,CAAC;AA0BjF,sFAAsF;AACtF,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,yFAAyF;AACzF,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,WAAW,CAAC;AAsB3D,qBACa,eAAgB,SAAQ,QAAQ;IAElC,KAAK,CAAC,EAAE,WAAW,CAAA;IAG1B,OAAO,CAAC,QAAQ,CAAC,CAAQ;IAGzB,OAAO,CAAC,iBAAiB,CAAI;IAG7B,OAAO,CAAC,kBAAkB,CAAI;IAG9B,OAAO,CAAC,QAAQ,CAA2B;IAE3C,WAAW,IAAI,MAAM,GAAG,SAAS;IAIjC,UAAU,IAAI,OAAO;IAIrB,kBAAkB,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAIvD,4GAA4G;IAC5G,WAAW,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAIjD,WAAW,IAAI,kBAAkB,EAAE;IAInC,aAAa,IAAI,IAAI;IAIrB,SAAS,CAAC,OAAO;cAWD,QAAQ;YAIV,SAAS;IAWvB,OAAO,CAAC,WAAW,CAMlB;IAED,SAAS,CAAC,MAAM;IA6DhB,MAAM,CAAC,MAAM,0BAgFZ;CACJ;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,mBAAmB,EAAE,eAAe,CAAA;KACvC;CACJ"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eclipse-lyra/extension-media-viewer",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@eclipse-lyra/core": "*"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.9.3",
|
|
17
|
+
"vite": "^7.1.12",
|
|
18
|
+
"vite-plugin-dts": "^4.5.4"
|
|
19
|
+
},
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "vite build"
|
|
27
|
+
}
|
|
28
|
+
}
|