arcane-os 0.5.12 → 0.5.13
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/CHANGELOG.md +11 -0
- package/docs/reference/asset-versioning.md +69 -0
- package/package.json +1 -1
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chart.html +2 -7
- package/runtime/arcane/components/dashboard-config.html +3 -8
- package/runtime/arcane/components/file-manager.html +6 -6
- package/runtime/arcane/components/markdown-document.html +1 -6
- package/runtime/arcane/components/markdown-editor.html +2 -7
- package/runtime/arcane/components/media-embed.html +1 -1
- package/runtime/arcane/components/screen-capture.html +1 -1
- package/runtime/arcane/components/voice-transcription.html +4 -9
- package/runtime/arcane/modules/HTMLImport.js +184 -13
- package/src/dev-server.mjs +72 -5
- package/src/import-map.mjs +548 -16
- package/src/installed-sdk-runtime.mjs +1 -0
- package/src/packager/core.mjs +60 -1
- package/src/workspace-runtime.mjs +19 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.5.13
|
|
6
|
+
|
|
7
|
+
- Derive local browser resource queries from the selected SDK package version
|
|
8
|
+
across managed import maps, runtime materialization, source serving and
|
|
9
|
+
packaged application resource graphs. Preserve existing query parameters,
|
|
10
|
+
fragments and ordinary caching; revalidate entry documents on navigation.
|
|
11
|
+
- Carry component and Worker module references through the same versioned
|
|
12
|
+
paths. Preserve fetched document/attachment content, CSS comments and text,
|
|
13
|
+
remote URLs, model data and user storage. Existing open documents adopt the
|
|
14
|
+
new resource graph on ordinary refresh or navigation, without a forced reload.
|
|
15
|
+
|
|
5
16
|
## 0.5.12
|
|
6
17
|
|
|
7
18
|
- Add optional `voice`, `speed`, `pauseAfterMs`, and `waitForPlayback` fields to
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Release-derived browser asset URLs
|
|
2
|
+
|
|
3
|
+
The SDK's public import-map generator, runtime materializer, source server and
|
|
4
|
+
application packager use the selected SDK package version for local browser
|
|
5
|
+
resource references. The query field is `arcaneVersion`. Its value comes from
|
|
6
|
+
SDK package metadata, not a timestamp, content measurement, or application
|
|
7
|
+
constant.
|
|
8
|
+
|
|
9
|
+
For example, an existing `./arcane/modules/HTMLImport.js?v=6#module` reference
|
|
10
|
+
retains its application revision and fragment and gains
|
|
11
|
+
`&arcaneVersion=${version}` before the fragment. Regenerating for another SDK
|
|
12
|
+
release replaces only the `arcaneVersion` value. Existing query spelling and
|
|
13
|
+
application parameters remain intact.
|
|
14
|
+
|
|
15
|
+
## Public tooling
|
|
16
|
+
|
|
17
|
+
- Run the installed SDK's `materializeInstalledSdkRuntime()` to refresh the
|
|
18
|
+
workspace runtime from that installation. Its generated local resource
|
|
19
|
+
references use the installed package version, including npm aliases.
|
|
20
|
+
- Run `arcane import-map` through the installed SDK to regenerate the managed
|
|
21
|
+
import map and its application HTML. Named SDK imports and URL-shaped module
|
|
22
|
+
entries point to versioned resources.
|
|
23
|
+
- `arcane dev` applies the same reference transformation to served source
|
|
24
|
+
without editing the source files. An explicit live SDK source mount uses the
|
|
25
|
+
version in that source checkout's `package.json`. Otherwise it uses the
|
|
26
|
+
materialized SDK version recorded by the workspace's existing semantic lock.
|
|
27
|
+
- The application packager applies resource versioning after its selected
|
|
28
|
+
adapter finishes. The resulting application can be served by an ordinary
|
|
29
|
+
static host without a JavaScript transformation service.
|
|
30
|
+
|
|
31
|
+
The shared transformation edits actual local module imports, resource URL
|
|
32
|
+
construction, HTML resource attributes and CSS resource references. It does not
|
|
33
|
+
change displayed text, prompts, application data, downloaded model content,
|
|
34
|
+
remote provider URLs, or arbitrary strings that happen to resemble filenames.
|
|
35
|
+
Worker entry references and their local module imports are versioned separately:
|
|
36
|
+
Workers do not inherit a document's import map. `HTMLImport` carries its own
|
|
37
|
+
release query into local dynamically loaded components and their resources.
|
|
38
|
+
|
|
39
|
+
Computed application URLs that have no statically identifiable resource
|
|
40
|
+
reference remain application-owned expressions. The SDK does not intercept
|
|
41
|
+
global `fetch`, `URL`, `Worker`, or DOM APIs. An import-map URL entry can resolve
|
|
42
|
+
an exact matching computed module URL, but is not a wildcard query rule.
|
|
43
|
+
|
|
44
|
+
## Navigation and caching
|
|
45
|
+
|
|
46
|
+
The SDK server sends `Cache-Control: no-cache` for application entry/managed
|
|
47
|
+
HTML and managed import-map JSON. This allows storage but requests revalidation, so an ordinary navigation
|
|
48
|
+
or refresh obtains the current entry document and release URLs. Other assets
|
|
49
|
+
retain ordinary caching; no cache or user storage is cleared.
|
|
50
|
+
|
|
51
|
+
An independently configured static host must likewise revalidate entry HTML
|
|
52
|
+
and managed import-map JSON. The generated package supplies versioned resource
|
|
53
|
+
references; it cannot configure another server's HTTP headers.
|
|
54
|
+
|
|
55
|
+
An already-open document keeps modules it has evaluated. Versioned references
|
|
56
|
+
take effect on ordinary navigation or refresh; they do not replace live module
|
|
57
|
+
instances, force a reload, restart a model, or erase a conversation. The SDK does
|
|
58
|
+
not retain earlier installed runtime trees after materialization.
|
|
59
|
+
|
|
60
|
+
## Scope and work
|
|
61
|
+
|
|
62
|
+
One invocation acts on the selected workspace/application. Runtime
|
|
63
|
+
materialization reuses its existing recursive copy; packaging reuses its
|
|
64
|
+
selected output inventory. Packaging follows resource references from the entry,
|
|
65
|
+
managed application pages and SDK runtime. Source serving recognizes those
|
|
66
|
+
resources and browser script/style/worker requests. Files included only as
|
|
67
|
+
documents or attachments are not transformed merely because they contain HTML,
|
|
68
|
+
JavaScript or CSS. Source serving transforms the requested resource response,
|
|
69
|
+
not the entire workspace, and leaves source files unchanged.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
2
|
<style>:host{display:block;max-width:34rem}.calculator{display:grid;gap:var(--arcane-space-3);padding:var(--arcane-space-4)}.display{display:grid;gap:var(--arcane-space-2)}.display input{font:600 1.3rem/1.4 ui-monospace,monospace;text-align:right}.result{min-height:2.5rem;margin:0;text-align:right;font-size:1.8rem;font-weight:750;overflow-wrap:anywhere}.keys{display:grid;gap:var(--arcane-space-2);grid-template-columns:repeat(5,1fr)}.keys button{min-width:0;padding:.7rem .35rem}.status{min-height:1.4rem;margin:0;color:var(--arcane-muted)}@media(max-width:28rem){.keys{grid-template-columns:repeat(4,1fr)}}</style>
|
|
3
3
|
<section class="calculator arcane-card"><div class="display arcane-field"><label for="expression">Expression</label><input id="expression" inputmode="decimal" autocomplete="off" spellcheck="false" value=""><p id="result" class="result" aria-live="polite">0</p></div><div id="keys" class="keys" aria-label="Calculator keypad"></div><p id="status" class="status" role="status"></p></section>
|
|
4
|
-
<script type="module">const host=this,root=host.shadowRoot,input=root.querySelector('#expression'),result=root.querySelector('#result'),status=root.querySelector('#status'),keys=root.querySelector('#keys');const
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,input=root.querySelector('#expression'),result=root.querySelector('#result'),status=root.querySelector('#status'),keys=root.querySelector('#keys');const {default:CalculatorEngine}=await import('../modules/CalculatorEngine.js');const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.calculator',eventTypes:['calculation-complete','calculation-error','calculator-ready']}),lifecycleController=new AbortController();const engine=new CalculatorEngine(),definitions=['7','8','9','/','sqrt(','4','5','6','*','^','1','2','3','-','%','0','.','(',')','+','pi','e','C','⌫','='];let destroyed=false,operationSequence=0;
|
|
5
5
|
for(const value of definitions){const button=document.createElement('button');button.type='button';button.className=value==='='?'arcane-button':'arcane-button arcane-button--secondary';button.textContent=value;button.dataset.value=value;keys.append(button)}
|
|
6
6
|
keys.addEventListener('click',event=>{const value=event.target.closest('[data-value]')?.dataset.value;if(!value)return;if(value==='C'){input.value='';result.textContent='0';status.textContent='';return}if(value==='⌫'){input.value=input.value.slice(0,-1);return}if(value==='='){calculate();return}input.value+=value;input.focus()},{signal:lifecycleController.signal});input.addEventListener('keydown',event=>{if(event.key==='Enter'){event.preventDefault();calculate()}},{signal:lifecycleController.signal});
|
|
7
7
|
function nextOperationId(){operationSequence+=1;return `${events.descriptor.instanceId}:calculate:${operationSequence}`}
|
|
@@ -162,16 +162,11 @@
|
|
|
162
162
|
|
|
163
163
|
<script type="module">
|
|
164
164
|
const host=this;
|
|
165
|
-
const
|
|
166
|
-
host.getAttribute('href')||'./arcane/components/chart.html',
|
|
167
|
-
document.baseURI
|
|
168
|
-
);
|
|
169
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
170
|
-
const {default:loadChartLibrary}=await import(moduleURL('ChartLibrary.js'));
|
|
165
|
+
const {default:loadChartLibrary}=await import('../modules/ChartLibrary.js');
|
|
171
166
|
const {
|
|
172
167
|
normalizeChartOptions,
|
|
173
168
|
normalizeChartRows
|
|
174
|
-
}=await import(
|
|
169
|
+
}=await import('../modules/ComponentContracts.js');
|
|
175
170
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
176
171
|
const events=createArcaneEventSource(host,{source:'arcane.component.chart',eventTypes:['chart-remove','chart-ready']});
|
|
177
172
|
const lifecycleController=new AbortController();
|
|
@@ -11,18 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
<script type="module">
|
|
13
13
|
const host=this;
|
|
14
|
-
const
|
|
15
|
-
host.getAttribute('href')||'./arcane/components/dashboard-config.html',
|
|
16
|
-
document.baseURI
|
|
17
|
-
);
|
|
18
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
19
|
-
const {default:waitForComponent}=await import(moduleURL('WaitForComponent.js'));
|
|
14
|
+
const {default:waitForComponent}=await import('../modules/WaitForComponent.js');
|
|
20
15
|
const {
|
|
21
16
|
effectiveDashboardVisibility,
|
|
22
17
|
normalizeDashboardDefinitions,
|
|
23
18
|
normalizeDashboardOptions,
|
|
24
19
|
normalizeDashboardVisibility
|
|
25
|
-
}=await import(
|
|
20
|
+
}=await import('../modules/ComponentContracts.js');
|
|
26
21
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
27
22
|
const events=createArcaneEventSource(host,{source:'arcane.component.dashboard-config',eventTypes:['dashboard-config-closed','dashboard-config-opened','dashboard-config-change','dashboard-config-ready']});
|
|
28
23
|
const lifecycleController=new AbortController();
|
|
@@ -232,7 +227,7 @@
|
|
|
232
227
|
const items=document.createElement('section');
|
|
233
228
|
|
|
234
229
|
styles.rel='stylesheet';
|
|
235
|
-
styles.href='./arcane/css/dashboard-config.css?v=2';
|
|
230
|
+
styles.href=new URL('./arcane/css/dashboard-config.css?v=2',document.baseURI).href;
|
|
236
231
|
heading.dataset.dashboardConfigHeading='';
|
|
237
232
|
message.dataset.dashboardConfigDescription='';
|
|
238
233
|
empty.className='dashboard-config-empty';
|
|
@@ -825,8 +825,8 @@
|
|
|
825
825
|
icon.className='tree-entry-icon';
|
|
826
826
|
icon.alt='';
|
|
827
827
|
icon.src=entry.kind==='directory'
|
|
828
|
-
?'./arcane/img/folder.svg'
|
|
829
|
-
:'./arcane/img/doc.svg';
|
|
828
|
+
?new URL('./arcane/img/folder.svg',document.baseURI).href
|
|
829
|
+
:new URL('./arcane/img/doc.svg',document.baseURI).href;
|
|
830
830
|
name.className='tree-entry-name';
|
|
831
831
|
name.innerText=entry.name;
|
|
832
832
|
count.className='tree-file-count';
|
|
@@ -1404,7 +1404,7 @@
|
|
|
1404
1404
|
deleteButton.innerText='Delete File';
|
|
1405
1405
|
deleteIcon.alt='';
|
|
1406
1406
|
deleteIcon.className='action-icon';
|
|
1407
|
-
deleteIcon.src='./arcane/img/trash.svg';
|
|
1407
|
+
deleteIcon.src=new URL('./arcane/img/trash.svg',document.baseURI).href;
|
|
1408
1408
|
deleteButton.prepend(deleteIcon);
|
|
1409
1409
|
deleteButton.addEventListener(
|
|
1410
1410
|
'click',
|
|
@@ -1610,7 +1610,7 @@
|
|
|
1610
1610
|
|
|
1611
1611
|
fileIcon.alt='';
|
|
1612
1612
|
fileIcon.className='icon file-icon';
|
|
1613
|
-
fileIcon.src='./arcane/img/doc.svg';
|
|
1613
|
+
fileIcon.src=new URL('./arcane/img/doc.svg',document.baseURI).href;
|
|
1614
1614
|
fileDetails.className='file-details';
|
|
1615
1615
|
fileLabel.className='file-name';
|
|
1616
1616
|
fileLabel.innerText=fileName;
|
|
@@ -1633,7 +1633,7 @@
|
|
|
1633
1633
|
|
|
1634
1634
|
deleteIcon.alt='';
|
|
1635
1635
|
deleteIcon.className='action-icon';
|
|
1636
|
-
deleteIcon.src='./arcane/img/trash.svg';
|
|
1636
|
+
deleteIcon.src=new URL('./arcane/img/trash.svg',document.baseURI).href;
|
|
1637
1637
|
|
|
1638
1638
|
openButton.addEventListener(
|
|
1639
1639
|
'click',
|
|
@@ -1933,7 +1933,7 @@
|
|
|
1933
1933
|
directory.dataset.directory=tableName;
|
|
1934
1934
|
folder.className='file folder';
|
|
1935
1935
|
folderIcon.className='icon';
|
|
1936
|
-
folderIcon.src='./arcane/img/folder.svg';
|
|
1936
|
+
folderIcon.src=new URL('./arcane/img/folder.svg',document.baseURI).href;
|
|
1937
1937
|
folderName.innerText=tableName;
|
|
1938
1938
|
fileCount.className='file-count';
|
|
1939
1939
|
fileCount.innerText=`${files.length} ${files.length===1?'file':'files'}`;
|
|
@@ -214,12 +214,7 @@
|
|
|
214
214
|
const host=this;
|
|
215
215
|
host.ready=false;
|
|
216
216
|
|
|
217
|
-
const
|
|
218
|
-
host.getAttribute('href')||'./arcane/components/markdown-document.html',
|
|
219
|
-
document.baseURI
|
|
220
|
-
);
|
|
221
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
222
|
-
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
217
|
+
const {default:MD}=await import('../modules/MD.js?v=2');
|
|
223
218
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
224
219
|
const events=createArcaneEventSource(host,{source:'arcane.component.markdown-document',eventTypes:['markdown-document-state','markdown-document-empty','markdown-document-error','markdown-document-loading','markdown-document-rendered','markdown-document-navigate','markdown-document-ready']});
|
|
225
220
|
const lifecycleController=new AbortController();
|
|
@@ -75,16 +75,11 @@
|
|
|
75
75
|
|
|
76
76
|
<script type="module">
|
|
77
77
|
const host=this;
|
|
78
|
-
const
|
|
79
|
-
host.getAttribute('href')||'./arcane/components/markdown-editor.html',
|
|
80
|
-
document.baseURI
|
|
81
|
-
);
|
|
82
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
83
|
-
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
78
|
+
const {default:MD}=await import('../modules/MD.js?v=2');
|
|
84
79
|
const {
|
|
85
80
|
applyMarkdownFormat,
|
|
86
81
|
normalizeMarkdownOptions
|
|
87
|
-
}=await import(
|
|
82
|
+
}=await import('../modules/ComponentContracts.js');
|
|
88
83
|
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
89
84
|
const events=createArcaneEventSource(host,{source:'arcane.component.markdown-editor',eventTypes:['markdown-editor-change','markdown-editor-saved','markdown-editor-ready']});
|
|
90
85
|
const lifecycleController=new AbortController();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
2
|
<style>:host{display:block}.player-shell{display:grid;gap:var(--arcane-space-4)}.loader{display:flex;gap:var(--arcane-space-2)}.loader input{min-width:0;width:100%}.player{aspect-ratio:16/9;display:grid;place-items:center;background:var(--arcane-surface-muted);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-large)}iframe{width:100%;height:100%;border:0}.empty{text-align:center;color:var(--arcane-muted);padding:var(--arcane-space-5)}.footer{align-items:center;display:flex;gap:var(--arcane-space-3);justify-content:space-between}.status{margin:0;color:var(--arcane-muted)}@media(max-width:36rem){.loader,.footer{align-items:stretch;flex-direction:column}}</style>
|
|
3
3
|
<section class="player-shell"><form id="loader" class="loader"><label class="visually-hidden" for="mediaUrl">Video or playlist address</label><input id="mediaUrl" type="text" inputmode="url" autocomplete="off" spellcheck="false" placeholder="Paste a YouTube video or playlist URL"><button class="arcane-button" type="submit">Play</button></form><div class="player"><div id="empty" class="empty"><strong>Choose something to play</strong><p>Paste a YouTube or YouTube Music video or playlist address.</p></div><iframe id="frame" title="Embedded media player" hidden allow="autoplay; encrypted-media; picture-in-picture; fullscreen" allowfullscreen></iframe></div><div class="footer"><p id="status" class="status" role="status" aria-live="polite">Nothing loaded.</p><button id="platform" class="arcane-button arcane-button--secondary" type="button">Open platform</button></div></section>
|
|
4
|
-
<script type="module">const host=this,root=host.shadowRoot,form=root.querySelector('#loader'),input=root.querySelector('#mediaUrl'),frame=root.querySelector('#frame'),empty=root.querySelector('#empty'),status=root.querySelector('#status');const
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,form=root.querySelector('#loader'),input=root.querySelector('#mediaUrl'),frame=root.querySelector('#frame'),empty=root.querySelector('#empty'),status=root.querySelector('#status');const {parseYouTubeMedia,youtubeEmbedUrl}=await import('../modules/YouTubeMedia.js');const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.media-embed',eventTypes:['media-open-platform','media-load','media-error','media-embed-ready']}),lifecycleController=new AbortController();let platformUrl='https://www.youtube.com/',privacyEnhanced=false,destroyed=false,operationSequence=0;
|
|
5
5
|
function nextOperationId(kind){operationSequence+=1;return `${events.descriptor.instanceId}:${kind}:${operationSequence}`}
|
|
6
6
|
function emit(type,detail={},publicDetail={},operationId=null){if(destroyed)return false;const publication=events.dispatch(type,detail,{publicDetail,operationId});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true})}
|
|
7
7
|
host.configure=options=>{if(destroyed)return false;platformUrl=String(options?.platformUrl||platformUrl);if(Object.hasOwn(options||{},'privacyEnhanced'))privacyEnhanced=options.privacyEnhanced===true;if(options?.placeholder)input.placeholder=String(options.placeholder);if(options?.openLabel)root.querySelector('#platform').textContent=String(options.openLabel);if(options?.initialValue){input.value=String(options.initialValue);load(input.value)}return host};host.load=load;host.destroy=destroy;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
2
|
<style>:host{display:block}.capture{display:grid;gap:var(--arcane-space-4)}.actions{display:grid;gap:var(--arcane-space-3);grid-template-columns:repeat(3,minmax(0,1fr))}.action{display:grid;gap:var(--arcane-space-2);padding:var(--arcane-space-4);text-align:left}.action strong{font-size:1.05rem}.action span{color:var(--arcane-muted);font-size:.85rem}.recording{align-items:center;display:flex;gap:var(--arcane-space-3);justify-content:space-between;padding:var(--arcane-space-3);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-medium)}.preview{display:grid;place-items:center;min-height:16rem;overflow:auto;background:var(--arcane-surface-muted);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-large)}.preview :where(img,video){display:block}.empty{color:var(--arcane-muted);text-align:center}.status{margin:0;color:var(--arcane-muted)}@media(max-width:44rem){.actions{grid-template-columns:1fr}}</style>
|
|
3
3
|
<section class="capture"><div class="actions"><button class="action arcane-card" type="button" data-capture="image"><strong>Screenshot</strong><span>Capture the selected display as PNG.</span></button><button class="action arcane-card" type="button" data-capture="video"><strong>Record video</strong><span>Record the selected display in a browser-supported video format.</span></button><button class="action arcane-card" type="button" data-capture="gif"><strong>Record GIF</strong><span>Record the selected display as an animated GIF.</span></button></div><div id="recording" class="recording" hidden><span id="recordingLabel">Recording…</span><button id="stop" class="arcane-button" type="button">Stop recording</button></div><div id="preview" class="preview"><div class="empty"><strong>No capture yet</strong><p>Your capture remains on this device unless you choose to save it.</p></div></div><p id="status" class="status" role="status" aria-live="polite">Choose a capture type. Your browser will ask which display to share.</p></section>
|
|
4
|
-
<script type="module">const host=this,root=host.shadowRoot,preview=root.querySelector('#preview'),recording=root.querySelector('#recording'),status=root.querySelector('#status');const
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,preview=root.querySelector('#preview'),recording=root.querySelector('#recording'),status=root.querySelector('#status');const {default:ScreenCapture}=await import('../modules/ScreenCapture.js');const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.screen-capture',eventTypes:['screen-capture-result','screen-capture-ready']});const capture=new ScreenCapture(),listeners=new AbortController();let destroyed=false,operationGeneration=0,previewUrl='';
|
|
5
5
|
function emit(type,detail={},publicDetail={},operationId=null){if(destroyed)return false;const publication=events.dispatch(type,detail,{operationId,publicDetail});return projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true})}
|
|
6
6
|
function current(generation){return !destroyed&&generation===operationGeneration}
|
|
7
7
|
function cleanupCaptureStart(originalError=null){const cleanupErrors=[];if(capture.recorder&&capture.recorder.state!=='inactive'){try{capture.recorder.stop()}catch(error){cleanupErrors.push(error)}}try{capture.stopTracks(capture.stream)}catch(error){cleanupErrors.push(error)}try{capture.reset()}catch(error){cleanupErrors.push(error)}for(const error of cleanupErrors)console.error('Error cleaning up rejected screen capture start:',error);return originalError}
|
|
@@ -84,21 +84,16 @@
|
|
|
84
84
|
]
|
|
85
85
|
}
|
|
86
86
|
);
|
|
87
|
-
const
|
|
88
|
-
host.getAttribute('href')||'./arcane/components/voice-transcription.html',
|
|
89
|
-
document.baseURI
|
|
90
|
-
);
|
|
91
|
-
const moduleURL=name=>new URL(`../modules/${name}`,componentURL).href;
|
|
92
|
-
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
87
|
+
const {default:MD}=await import('../modules/MD.js?v=2');
|
|
93
88
|
const {
|
|
94
89
|
appendTranscription,
|
|
95
90
|
createSTTActivationController,
|
|
96
91
|
normalizeVoiceOptions
|
|
97
|
-
}=await import(
|
|
92
|
+
}=await import('../modules/ComponentContracts.js');
|
|
98
93
|
const {
|
|
99
94
|
requestAIRuntimeIntent,
|
|
100
95
|
subscribeAIRuntimeState
|
|
101
|
-
}=await import(
|
|
96
|
+
}=await import('../modules/AIRuntimeState.js');
|
|
102
97
|
const description=host.shadowRoot.querySelector('#description');
|
|
103
98
|
const transcriptElement=host.shadowRoot.querySelector('#transcript');
|
|
104
99
|
const status=host.shadowRoot.querySelector('#status');
|
|
@@ -598,7 +593,7 @@
|
|
|
598
593
|
return options.transcribe(file,context);
|
|
599
594
|
}
|
|
600
595
|
if(typeof globalThis.ai?.fetchSTT!=='function'){
|
|
601
|
-
await import(
|
|
596
|
+
await import('../modules/AI.js');
|
|
602
597
|
}
|
|
603
598
|
if(typeof globalThis.ai?.fetchSTT!=='function'){
|
|
604
599
|
const error=new Error('Speech transcription is not configured.');
|
|
@@ -12,6 +12,44 @@ globalThis[htmlImportHostRegistryKey]=htmlImportHostRegistry;
|
|
|
12
12
|
|
|
13
13
|
let htmlImportScriptId=0;
|
|
14
14
|
|
|
15
|
+
const htmlImportModuleURL=new URL(import.meta.url);
|
|
16
|
+
const htmlImportAssetVersion=htmlImportModuleURL.searchParams.get('arcaneVersion');
|
|
17
|
+
|
|
18
|
+
function versionComponentResource(value,baseHref){
|
|
19
|
+
if(!htmlImportAssetVersion||typeof value!=='string'||!value||value.startsWith('#')){
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
let resolvedURL;
|
|
23
|
+
try{
|
|
24
|
+
resolvedURL=new URL(value,baseHref);
|
|
25
|
+
}catch{
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
if(resolvedURL.protocol!==htmlImportModuleURL.protocol
|
|
29
|
+
||resolvedURL.origin!==htmlImportModuleURL.origin){
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const fragmentIndex=value.indexOf('#');
|
|
34
|
+
const fragment=fragmentIndex<0?'':value.slice(fragmentIndex);
|
|
35
|
+
const resource=fragmentIndex<0?value:value.slice(0,fragmentIndex);
|
|
36
|
+
const queryIndex=resource.indexOf('?');
|
|
37
|
+
const pathname=queryIndex<0?resource:resource.slice(0,queryIndex);
|
|
38
|
+
const query=queryIndex<0?'':resource.slice(queryIndex+1);
|
|
39
|
+
const versionField=`arcaneVersion=${encodeURIComponent(htmlImportAssetVersion)}`;
|
|
40
|
+
let replaced=false;
|
|
41
|
+
const fields=query?query.split('&').map(function versionQueryField(field){
|
|
42
|
+
if(!new URLSearchParams(field).has('arcaneVersion'))return field;
|
|
43
|
+
if(replaced)return null;
|
|
44
|
+
replaced=true;
|
|
45
|
+
return versionField;
|
|
46
|
+
}).filter(function retainQueryField(field){
|
|
47
|
+
return field!==null;
|
|
48
|
+
}):[];
|
|
49
|
+
if(!replaced)fields.push(versionField);
|
|
50
|
+
return `${pathname}?${fields.join('&')}${fragment}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
15
53
|
function componentRuntimeRoot(resolvedHref){
|
|
16
54
|
const componentURL=new URL(resolvedHref);
|
|
17
55
|
const componentMarker='/arcane/components/';
|
|
@@ -32,28 +70,157 @@ function resolveComponentResource(value,runtimeRoot){
|
|
|
32
70
|
}
|
|
33
71
|
|
|
34
72
|
function resolveComponentStyleResources(styleText,runtimeRoot){
|
|
35
|
-
if(!runtimeRoot)return styleText;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
73
|
+
if(!runtimeRoot&&!htmlImportAssetVersion)return styleText;
|
|
74
|
+
const parts=[];
|
|
75
|
+
let copiedThrough=0;
|
|
76
|
+
let index=0;
|
|
77
|
+
|
|
78
|
+
function afterComment(start){
|
|
79
|
+
const end=styleText.indexOf('*/',start+2);
|
|
80
|
+
return end<0?styleText.length:end+2;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function afterEscape(start){
|
|
84
|
+
let end=start+1;
|
|
85
|
+
if(/[\da-f]/iu.test(styleText[end]||'')){
|
|
86
|
+
const limit=end+6;
|
|
87
|
+
while(end<limit&&/[\da-f]/iu.test(styleText[end]||''))end+=1;
|
|
88
|
+
if(/[\t\n\f\r ]/u.test(styleText[end]||'')){
|
|
89
|
+
end+=styleText[end]==='\r'&&styleText[end+1]==='\n'?2:1;
|
|
90
|
+
}
|
|
91
|
+
return end;
|
|
41
92
|
}
|
|
42
|
-
|
|
93
|
+
return end+(styleText[end]==='\r'&&styleText[end+1]==='\n'?2:1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function afterString(start){
|
|
97
|
+
const quote=styleText[start];
|
|
98
|
+
let end=start+1;
|
|
99
|
+
while(end<styleText.length){
|
|
100
|
+
if(styleText[end]==='\\'){
|
|
101
|
+
end=afterEscape(end);
|
|
102
|
+
}else if(styleText[end]===quote){
|
|
103
|
+
return end+1;
|
|
104
|
+
}else if(/[\n\r\f]/u.test(styleText[end])){
|
|
105
|
+
return end;
|
|
106
|
+
}else{
|
|
107
|
+
end+=1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return styleText.length;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function afterWhitespaceAndComments(start){
|
|
114
|
+
let end=start;
|
|
115
|
+
while(end<styleText.length){
|
|
116
|
+
if(/[\t\n\f\r ]/u.test(styleText[end])){
|
|
117
|
+
end+=1;
|
|
118
|
+
}else if(styleText.startsWith('/*',end)){
|
|
119
|
+
end=afterComment(end);
|
|
120
|
+
}else{
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return end;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function appendResource(start,end){
|
|
128
|
+
const resourcePath=styleText.slice(start,end);
|
|
129
|
+
// CSS escapes retain their original spelling until their URL syntax is decoded.
|
|
130
|
+
if(resourcePath.includes('\\'))return;
|
|
131
|
+
const resolved=versionComponentResource(
|
|
132
|
+
resolveComponentResource(resourcePath,runtimeRoot),
|
|
133
|
+
document.baseURI
|
|
134
|
+
);
|
|
135
|
+
if(resolved===resourcePath)return;
|
|
136
|
+
parts.push(styleText.slice(copiedThrough,start),resolved);
|
|
137
|
+
copiedThrough=end;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
while(index<styleText.length){
|
|
141
|
+
if(styleText.startsWith('/*',index)){
|
|
142
|
+
index=afterComment(index);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if(styleText[index]==='"'||styleText[index]==="'"){
|
|
146
|
+
index=afterString(index);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const atRule=styleText[index]==='@';
|
|
151
|
+
const hashToken=styleText[index]==='#';
|
|
152
|
+
const nameStart=atRule||hashToken?index+1:index;
|
|
153
|
+
let nameEnd=nameStart;
|
|
154
|
+
while(nameEnd<styleText.length&&/[-\w\u0080-\uFFFF\\]/u.test(styleText[nameEnd])){
|
|
155
|
+
nameEnd=styleText[nameEnd]==='\\'?afterEscape(nameEnd):nameEnd+1;
|
|
156
|
+
}
|
|
157
|
+
if(nameEnd===nameStart){
|
|
158
|
+
index+=1;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const name=styleText.slice(nameStart,nameEnd).toLowerCase();
|
|
162
|
+
index=nameEnd;
|
|
163
|
+
if(hashToken)continue;
|
|
164
|
+
if(atRule&&name==='import'){
|
|
165
|
+
const start=afterWhitespaceAndComments(nameEnd);
|
|
166
|
+
if(styleText[start]==='"'||styleText[start]==="'"){
|
|
167
|
+
const end=afterString(start);
|
|
168
|
+
if(styleText[end-1]===styleText[start])appendResource(start+1,end-1);
|
|
169
|
+
index=end;
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if(atRule||name!=='url'||styleText[nameEnd]!=='(')continue;
|
|
174
|
+
|
|
175
|
+
let start=nameEnd+1;
|
|
176
|
+
while(start<styleText.length&&/[\t\n\f\r ]/u.test(styleText[start]))start+=1;
|
|
177
|
+
if(styleText[start]==='"'||styleText[start]==="'"){
|
|
178
|
+
const end=afterString(start);
|
|
179
|
+
const close=afterWhitespaceAndComments(end);
|
|
180
|
+
if(styleText[end-1]===styleText[start]&&styleText[close]===')'){
|
|
181
|
+
appendResource(start+1,end-1);
|
|
182
|
+
index=close+1;
|
|
183
|
+
}else{
|
|
184
|
+
index=end;
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let end=start;
|
|
190
|
+
while(end<styleText.length&&!/[\t\n\f\r )'"(]/u.test(styleText[end])){
|
|
191
|
+
end=styleText[end]==='\\'?afterEscape(end):end+1;
|
|
192
|
+
}
|
|
193
|
+
let close=end;
|
|
194
|
+
while(close<styleText.length&&/[\t\n\f\r ]/u.test(styleText[close]))close+=1;
|
|
195
|
+
if(styleText[close]===')'){
|
|
196
|
+
appendResource(start,end);
|
|
197
|
+
index=close+1;
|
|
198
|
+
}else{
|
|
199
|
+
index=end;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if(parts.length===0)return styleText;
|
|
203
|
+
parts.push(styleText.slice(copiedThrough));
|
|
204
|
+
return parts.join('');
|
|
43
205
|
}
|
|
44
206
|
|
|
45
207
|
function createComponentFragment(html,resolvedHref){
|
|
46
208
|
const template=document.createElement('template');
|
|
47
209
|
template.innerHTML=html;
|
|
48
210
|
const runtimeRoot=componentRuntimeRoot(resolvedHref);
|
|
49
|
-
if(!runtimeRoot)return template.content;
|
|
50
211
|
|
|
51
212
|
for(const element of template.content.querySelectorAll('[href],[src]')){
|
|
52
213
|
for(const attribute of ['href','src']){
|
|
53
214
|
if(!element.hasAttribute(attribute))continue;
|
|
54
215
|
const value=element.getAttribute(attribute);
|
|
55
216
|
const resolved=resolveComponentResource(value,runtimeRoot);
|
|
56
|
-
|
|
217
|
+
const navigation=attribute==='href'
|
|
218
|
+
&&['a','area','base'].includes(element.localName);
|
|
219
|
+
const versioned=navigation?resolved:versionComponentResource(
|
|
220
|
+
resolved,
|
|
221
|
+
element.localName==='script'?resolvedHref:document.baseURI
|
|
222
|
+
);
|
|
223
|
+
if(versioned!==value)element.setAttribute(attribute,versioned);
|
|
57
224
|
}
|
|
58
225
|
}
|
|
59
226
|
for(const style of template.content.querySelectorAll('style')){
|
|
@@ -69,7 +236,8 @@ function resolveRelativeDynamicImports(source,baseHref){
|
|
|
69
236
|
return source.replace(
|
|
70
237
|
/\bimport\s*\(\s*(['"])(\.{1,2}\/[^'"]+)\1\s*\)/gu,
|
|
71
238
|
function resolveDynamicImport(_match,_quote,specifier){
|
|
72
|
-
|
|
239
|
+
const resolved=versionComponentResource(new URL(specifier,baseHref).href,baseHref);
|
|
240
|
+
return `import(${JSON.stringify(resolved)})`;
|
|
73
241
|
}
|
|
74
242
|
);
|
|
75
243
|
}
|
|
@@ -138,8 +306,8 @@ class HTMLImport extends HTMLElement {
|
|
|
138
306
|
|
|
139
307
|
try{
|
|
140
308
|
const resolvedURL=new URL(href,document.baseURI);
|
|
141
|
-
resolvedHref=resolvedURL.href;
|
|
142
|
-
const response=await fetch(
|
|
309
|
+
resolvedHref=versionComponentResource(resolvedURL.href,document.baseURI);
|
|
310
|
+
const response=await fetch(resolvedHref,{
|
|
143
311
|
cache:'default',
|
|
144
312
|
method:'GET',
|
|
145
313
|
signal:controller.signal
|
|
@@ -308,7 +476,10 @@ class HTMLImport extends HTMLElement {
|
|
|
308
476
|
let sourceBaseHref=contentBaseHref;
|
|
309
477
|
const scriptSource=script.getAttribute('src');
|
|
310
478
|
if(scriptSource!==null){
|
|
311
|
-
const requestedScriptHref=
|
|
479
|
+
const requestedScriptHref=versionComponentResource(
|
|
480
|
+
new URL(scriptSource,contentBaseHref).href,
|
|
481
|
+
contentBaseHref
|
|
482
|
+
);
|
|
312
483
|
const response=await fetch(requestedScriptHref,{
|
|
313
484
|
cache:'default',
|
|
314
485
|
method:'GET',
|