arcane-os 0.2.2 → 0.3.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/CHANGELOG.md +29 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
- package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
- package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
- package/browser-runtime/ai/browser-speech.mjs +2 -0
- package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
- package/browser-runtime/ai/model-controller.mjs +285 -95
- package/browser-runtime/ai/speech-worker-client.mjs +247 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
- package/browser-runtime/event-manager.mjs +1097 -1
- package/docs/architecture.md +2 -2
- package/docs/event-manager.md +155 -27
- package/docs/reference/README.md +27 -27
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1162 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +6 -3
- package/docs/reference/behavioral-testing.md +29 -6
- package/docs/reference/cli.md +117 -9
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/event-manager.md +577 -32
- package/docs/reference/inventory/package-api.json +478 -2
- package/docs/reference/inventory/runtime-components.json +108 -44
- package/docs/reference/inventory/runtime-modules.json +131 -53
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +157 -43
- package/docs/reference/runtime-components.md +258 -83
- package/docs/reference/runtime-modules.md +613 -77
- package/docs/reference/sdk-api.md +1014 -25
- package/package.json +5 -4
- package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
- package/runtime/arcane/components/app-bar.html +34 -13
- package/runtime/arcane/components/assistant-panel.html +110 -57
- package/runtime/arcane/components/calculator.html +7 -4
- package/runtime/arcane/components/chart.html +58 -17
- package/runtime/arcane/components/chat.html +606 -136
- package/runtime/arcane/components/conversation-view.html +13 -6
- package/runtime/arcane/components/dashboard-config.html +96 -59
- package/runtime/arcane/components/data-maintenance.html +69 -14
- package/runtime/arcane/components/data-view.html +53 -7
- package/runtime/arcane/components/directory-picker.html +118 -32
- package/runtime/arcane/components/document-inspector.html +47 -10
- package/runtime/arcane/components/file-drop.html +81 -35
- package/runtime/arcane/components/file-inspector.html +72 -22
- package/runtime/arcane/components/file-manager.html +374 -79
- package/runtime/arcane/components/integration-settings.html +12 -5
- package/runtime/arcane/components/local-ai-status.html +48 -19
- package/runtime/arcane/components/markdown-document.html +161 -68
- package/runtime/arcane/components/markdown-editor.html +110 -33
- package/runtime/arcane/components/media-embed.html +8 -5
- package/runtime/arcane/components/modal.html +15 -5
- package/runtime/arcane/components/output-panel.html +28 -23
- package/runtime/arcane/components/preferences-form.html +22 -4
- package/runtime/arcane/components/record-timeline.html +18 -2
- package/runtime/arcane/components/relationship-board.html +23 -3
- package/runtime/arcane/components/screen-capture.html +10 -4
- package/runtime/arcane/components/source-code-viewer.html +76 -9
- package/runtime/arcane/components/source-explanation.html +23 -3
- package/runtime/arcane/components/speech.html +462 -384
- package/runtime/arcane/components/summary-strip.html +22 -11
- package/runtime/arcane/components/table.html +39 -21
- package/runtime/arcane/components/task-progress.html +79 -21
- package/runtime/arcane/components/terminal-workspace.html +7 -4
- package/runtime/arcane/components/theme-editor.html +7 -3
- package/runtime/arcane/components/unified-inbox.html +9 -4
- package/runtime/arcane/components/voice-transcription.html +639 -98
- package/runtime/arcane/components/weather-widget.html +5 -3
- package/runtime/arcane/components/web-navigator.html +48 -8
- package/runtime/arcane/entities/Chat.js +1 -1
- package/runtime/arcane/entities/User.js +110 -23
- package/runtime/arcane/modules/AI.js +2109 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
- package/runtime/arcane/modules/AIRuntimeState.js +109 -52
- package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
- package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
- package/runtime/arcane/modules/CalculatorEngine.js +63 -3
- package/runtime/arcane/modules/CommunicationAppController.js +588 -28
- package/runtime/arcane/modules/CommunicationHub.js +590 -10
- package/runtime/arcane/modules/ComponentContracts.js +470 -0
- package/runtime/arcane/modules/ConversationTimebox.js +152 -33
- package/runtime/arcane/modules/DBLS.js +40 -7
- package/runtime/arcane/modules/DBOPFS.js +35 -11
- package/runtime/arcane/modules/DataMaintenance.js +12 -2
- package/runtime/arcane/modules/Errors.js +65 -7
- package/runtime/arcane/modules/HTMLImport.js +198 -14
- package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
- package/runtime/arcane/modules/Mail.js +738 -115
- package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
- package/runtime/arcane/modules/MailTransport.mjs +197 -39
- package/runtime/arcane/modules/Ollama.js +36 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
- package/runtime/arcane/modules/PreferenceStore.js +367 -33
- package/runtime/arcane/modules/RecordReviewStore.js +322 -23
- package/runtime/arcane/modules/ScreenCapture.js +1397 -15
- package/runtime/arcane/modules/SpeechPlayback.js +438 -41
- package/runtime/arcane/modules/TerminalClient.js +277 -12
- package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
- package/runtime/arcane/modules/ThemeManager.js +39 -7
- package/runtime/arcane/modules/TimeGuard.js +131 -20
- package/runtime/arcane/modules/WaitForComponent.js +386 -33
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/cli/main.mjs +435 -9
- package/src/event-manager.mjs +1097 -1
- package/src/import-map.mjs +21 -3
- package/src/index.mjs +13 -0
- package/src/installed-sdk-runtime.mjs +112 -0
- package/src/mail-api.mjs +22 -0
- package/src/mail-credentials.mjs +667 -0
- package/src/mail-server.mjs +1769 -0
- package/src/mail.mjs +261 -0
- package/src/sdk-browser-runtime.mjs +85 -41
- package/src/testing-loader.mjs +7 -0
- package/src/toolchain.mjs +3 -0
- package/src/workspace.mjs +1 -1
|
@@ -1,8 +1,14 @@
|
|
|
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:hidden;background:var(--arcane-surface-muted);border:1px solid var(--arcane-border);border-radius:var(--arcane-radius-large)}.preview :where(img,video){display:block;max-width:100%;max-height:34rem}.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 a compact, lower-frame-rate 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 componentURL=new URL(host.getAttribute('href')||'./arcane/components/screen-capture.html',document.baseURI);const {default:ScreenCapture}=await import(new URL('../modules/ScreenCapture.js',componentURL));const capture=new ScreenCapture();let previewUrl='';
|
|
5
|
-
|
|
4
|
+
<script type="module">const host=this,root=host.shadowRoot,preview=root.querySelector('#preview'),recording=root.querySelector('#recording'),status=root.querySelector('#status');const componentURL=new URL(host.getAttribute('href')||'./arcane/components/screen-capture.html',document.baseURI);const {default:ScreenCapture}=await import(new URL('../modules/ScreenCapture.js',componentURL));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
|
+
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
|
+
function current(generation){return !destroyed&&generation===operationGeneration}
|
|
7
|
+
function cleanupCaptureStart(originalError=null){const cleanupErrors=[];clearInterval(capture.timer);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}
|
|
8
|
+
async function onCapture(event){const mode=event.target.closest('[data-capture]')?.dataset.capture;if(!mode||destroyed)return;const generation=++operationGeneration;setButtons(true);try{if(mode==='image'){status.textContent='Choose a display to capture…';const result=await capture.captureImage();if(!current(generation))return;showResult(result,generation)}else{status.textContent='Choose a display to record…';if(mode==='video')await capture.startVideo();else await capture.startGif();if(!current(generation)){cleanupCaptureStart();return}recording.hidden=false;root.querySelector('#recordingLabel').textContent=mode==='gif'?'Recording GIF frames…':'Recording video…';status.textContent='Recording. Return here and press Stop when finished.'}}catch(error){const originalError=mode==='image'?error:cleanupCaptureStart(error);if(!current(generation))return;status.textContent=originalError.message;setButtons(false)}}
|
|
9
|
+
async function onStop(){if(destroyed)return;const generation=operationGeneration;root.querySelector('#stop').disabled=true;status.textContent='Finishing capture…';try{const result=await capture.stop();if(current(generation)&&result)showResult(result,generation)}catch(error){if(current(generation))status.textContent=error.message}finally{if(current(generation)){recording.hidden=true;root.querySelector('#stop').disabled=false;setButtons(false)}}}
|
|
10
|
+
root.querySelector('.actions').addEventListener('click',onCapture,{signal:listeners.signal});root.querySelector('#stop').addEventListener('click',onStop,{signal:listeners.signal});
|
|
6
11
|
function setButtons(value){for(const button of root.querySelectorAll('[data-capture]'))button.disabled=value}
|
|
7
|
-
function showResult(result){if(previewUrl)URL.revokeObjectURL(previewUrl);previewUrl=URL.createObjectURL(result.blob);const media=document.createElement(result.mimeType.startsWith('video/')?'video':'img');media.src=previewUrl;if(media.tagName==='VIDEO')media.controls=true;media.alt='Captured display preview';preview.replaceChildren(media);status.textContent=`Capture ready · ${(result.blob.size/1024/1024).toFixed(2)} MB`;setButtons(false);
|
|
8
|
-
|
|
12
|
+
function showResult(result,generation){if(!current(generation))return false;if(previewUrl)URL.revokeObjectURL(previewUrl);previewUrl=URL.createObjectURL(result.blob);const media=document.createElement(result.mimeType.startsWith('video/')?'video':'img');media.src=previewUrl;if(media.tagName==='VIDEO')media.controls=true;media.alt='Captured display preview';preview.replaceChildren(media);status.textContent=`Capture ready · ${(result.blob.size/1024/1024).toFixed(2)} MB`;setButtons(false);return emit('screen-capture-result',{result},{mimeType:String(result.mimeType||''),size:Number(result.blob?.size||0)},`screen-capture-${events.instanceId}-${generation}`)}
|
|
13
|
+
function destroy(){if(destroyed)return false;destroyed=true;++operationGeneration;listeners.abort('screen-capture-component-destroyed');host.ready=false;recording.hidden=true;setButtons(true);root.querySelector('#stop').disabled=true;if(previewUrl){URL.revokeObjectURL(previewUrl);previewUrl=''}let error=null;try{capture.destroy()}catch(value){error=value}finally{events.dispose()}if(error)throw error;return true}
|
|
14
|
+
host.capture=capture;host.destroy=destroy;host.ready=true;emit('screen-capture-ready',{}, {ready:true},`screen-capture-ready-${events.instanceId}`);</script>
|
|
@@ -179,6 +179,18 @@
|
|
|
179
179
|
<script type="module">
|
|
180
180
|
const host=this;
|
|
181
181
|
host.ready=false;
|
|
182
|
+
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
183
|
+
const events=createArcaneEventSource(host,{
|
|
184
|
+
source:'arcane.component.source-code-viewer',
|
|
185
|
+
eventTypes:[
|
|
186
|
+
'source-code-viewer-state',
|
|
187
|
+
'source-code-viewer-state-empty',
|
|
188
|
+
'source-code-viewer-state-error',
|
|
189
|
+
'source-code-viewer-state-loading',
|
|
190
|
+
'source-code-viewer-state-ready',
|
|
191
|
+
'source-code-viewer-ready'
|
|
192
|
+
]
|
|
193
|
+
});
|
|
182
194
|
|
|
183
195
|
const viewer=host.shadowRoot.querySelector('#viewer');
|
|
184
196
|
const status=host.shadowRoot.querySelector('#status');
|
|
@@ -210,12 +222,15 @@
|
|
|
210
222
|
text:'',
|
|
211
223
|
title:''
|
|
212
224
|
});
|
|
225
|
+
let destroyed=false;
|
|
226
|
+
let eventOperationSequence=0;
|
|
213
227
|
let loadSequence=0;
|
|
214
228
|
let options=normalizeOptions({},defaultOptions);
|
|
215
229
|
let state='empty';
|
|
216
230
|
|
|
217
231
|
host.clear=clear;
|
|
218
232
|
host.configure=configure;
|
|
233
|
+
host.destroy=destroy;
|
|
219
234
|
host.fail=fail;
|
|
220
235
|
host.focus=focusSource;
|
|
221
236
|
host.focusLine=focusLine;
|
|
@@ -289,12 +304,13 @@
|
|
|
289
304
|
}
|
|
290
305
|
|
|
291
306
|
async function load(source='',renderOptions={}){
|
|
307
|
+
if(destroyed)return false;
|
|
292
308
|
const sequence=++loadSequence;
|
|
293
309
|
transition('loading');
|
|
294
310
|
try{
|
|
295
311
|
const pending=typeof source==='function'?source():source;
|
|
296
312
|
const result=await pending;
|
|
297
|
-
if(sequence!==loadSequence)return false;
|
|
313
|
+
if(destroyed||sequence!==loadSequence)return false;
|
|
298
314
|
if(result&&typeof result==='object'&&!Array.isArray(result)){
|
|
299
315
|
return renderSource(result.text??result.value??'',{
|
|
300
316
|
...renderOptions,
|
|
@@ -303,12 +319,13 @@
|
|
|
303
319
|
}
|
|
304
320
|
return renderSource(result??'',renderOptions);
|
|
305
321
|
}catch(error){
|
|
306
|
-
if(sequence===loadSequence)showError(error);
|
|
322
|
+
if(!destroyed&&sequence===loadSequence)showError(error);
|
|
307
323
|
return false;
|
|
308
324
|
}
|
|
309
325
|
}
|
|
310
326
|
|
|
311
327
|
function render(text='',renderOptions={}){
|
|
328
|
+
if(destroyed)return false;
|
|
312
329
|
++loadSequence;
|
|
313
330
|
try{
|
|
314
331
|
return renderSource(text,renderOptions);
|
|
@@ -394,8 +411,11 @@
|
|
|
394
411
|
}
|
|
395
412
|
|
|
396
413
|
function transition(next,error=null){
|
|
414
|
+
if(destroyed)return false;
|
|
397
415
|
state=next;
|
|
398
416
|
updateStateDOM();
|
|
417
|
+
const operationId=
|
|
418
|
+
`${events.instanceId}:source-state:${(++eventOperationSequence).toString(36)}`;
|
|
399
419
|
const detail={
|
|
400
420
|
characters:current.text.length,
|
|
401
421
|
language:current.language,
|
|
@@ -404,8 +424,46 @@
|
|
|
404
424
|
state
|
|
405
425
|
};
|
|
406
426
|
if(error)detail.message=String(error?.message||'Source rendering failed.').slice(0,512);
|
|
407
|
-
|
|
408
|
-
|
|
427
|
+
const reason={
|
|
428
|
+
empty:'source-code-viewer-cleared',
|
|
429
|
+
error:'source-code-viewer-render-rejected',
|
|
430
|
+
loading:'source-code-viewer-load-started',
|
|
431
|
+
ready:'source-code-viewer-rendered'
|
|
432
|
+
}[state];
|
|
433
|
+
const publicDetail={
|
|
434
|
+
characters:detail.characters,
|
|
435
|
+
language:detail.language,
|
|
436
|
+
lines:detail.lines,
|
|
437
|
+
state:`source-code-viewer-${state}`,
|
|
438
|
+
reason,
|
|
439
|
+
...(state==='error'?{
|
|
440
|
+
code:typeof error?.code==='string'
|
|
441
|
+
?error.code
|
|
442
|
+
:'ARCANE_SOURCE_CODE_VIEWER_RENDER_REJECTED'
|
|
443
|
+
}:{})
|
|
444
|
+
};
|
|
445
|
+
const statePublication=events.dispatch(
|
|
446
|
+
'source-code-viewer-state',
|
|
447
|
+
detail,
|
|
448
|
+
{operationId,publicDetail}
|
|
449
|
+
);
|
|
450
|
+
projectArcaneDOMEvent(host,statePublication.occurrence,{bubbles:true,composed:true});
|
|
451
|
+
const exactPublication=events.dispatch(
|
|
452
|
+
`source-code-viewer-state-${state}`,
|
|
453
|
+
detail,
|
|
454
|
+
{operationId,publicDetail}
|
|
455
|
+
);
|
|
456
|
+
projectArcaneDOMEvent(host,exactPublication.occurrence,{type:`source-code-viewer-${state}`,bubbles:true,composed:true});
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function destroy(){
|
|
461
|
+
if(destroyed)return false;
|
|
462
|
+
destroyed=true;
|
|
463
|
+
loadSequence+=1;
|
|
464
|
+
host.ready=false;
|
|
465
|
+
events.dispose();
|
|
466
|
+
return true;
|
|
409
467
|
}
|
|
410
468
|
|
|
411
469
|
function updateStateDOM(){
|
|
@@ -433,9 +491,18 @@
|
|
|
433
491
|
repositoryLink.textContent=options.labels.repository;
|
|
434
492
|
transition('empty');
|
|
435
493
|
host.ready=true;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
494
|
+
const readyPublication=events.dispatch(
|
|
495
|
+
'source-code-viewer-ready',
|
|
496
|
+
{component:host,state},
|
|
497
|
+
{
|
|
498
|
+
operationId:
|
|
499
|
+
`${events.instanceId}:ready:${(++eventOperationSequence).toString(36)}`,
|
|
500
|
+
publicDetail:{
|
|
501
|
+
ready:true,
|
|
502
|
+
state:'source-code-viewer-empty',
|
|
503
|
+
reason:'source-code-viewer-ready'
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
projectArcaneDOMEvent(host,readyPublication.occurrence,{bubbles:true,composed:true});
|
|
441
508
|
</script>
|
|
@@ -47,17 +47,24 @@
|
|
|
47
47
|
const elements=root.querySelector('[data-elements]'); const application=root.querySelector('[data-application]'); const limitations=root.querySelector('[data-limitations]'); const nextSteps=root.querySelector('[data-next-steps]');
|
|
48
48
|
const evidenceList=root.querySelector('[data-evidence]'); const original=root.querySelector('[data-original]'); const originalLabel=root.querySelector('[data-original-label]'); const originalPicker=root.querySelector('[data-original-picker]');
|
|
49
49
|
const reviewForm=root.querySelector('[data-review]'); const actor=root.querySelector('[data-actor]'); const disposition=root.querySelector('[data-disposition]'); const confidence=root.querySelector('[data-confidence]'); const notes=root.querySelector('[data-notes]'); const saveState=root.querySelector('[data-save-state]');
|
|
50
|
+
const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
|
|
51
|
+
const events=createArcaneEventSource(host,{source:'arcane.component.source-explanation',eventTypes:['source-explanation-save','source-explanation-source-selected','source-explanation-ready']});
|
|
52
|
+
let destroyed=false;
|
|
50
53
|
let finding=null; let sources=[];
|
|
51
54
|
|
|
52
55
|
host.showFinding=showFinding;
|
|
53
56
|
host.populate=(value={},options={})=>showFinding(value,options);
|
|
54
57
|
host.selectSource=selectSource;
|
|
55
58
|
host.markSaved=message=>{saveState.textContent=String(message||'Saved.');};
|
|
59
|
+
host.destroy=destroy;
|
|
56
60
|
|
|
57
61
|
reviewForm.addEventListener('submit',event=>{
|
|
62
|
+
if(destroyed) return;
|
|
58
63
|
event.preventDefault(); if(!finding) return;
|
|
59
64
|
saveState.textContent='Saving…';
|
|
60
|
-
|
|
65
|
+
const detail={recordId:finding.id,classification:actor.value,status:disposition.value,attributes:{confidence:confidence.value},notes:notes.value};
|
|
66
|
+
const publication=events.dispatch('source-explanation-save',detail,{publicDetail:{hasFinding:true,hasNotes:Boolean(notes.value)}});
|
|
67
|
+
projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true});
|
|
61
68
|
});
|
|
62
69
|
originalPicker.addEventListener('change',()=>showSource(Number(originalPicker.value)));
|
|
63
70
|
|
|
@@ -110,15 +117,28 @@
|
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
function showSource(index=0){
|
|
120
|
+
if(destroyed) return null;
|
|
113
121
|
const source=sources[index]; if(!source?.pdfUrl) return null;
|
|
114
122
|
try{
|
|
115
123
|
const url=sameOrigin(source.pdfUrl); if(source.page){url.searchParams.set('arcane-pdf-page',String(Number(source.page)));url.hash=`page=${Number(source.page)}`;}
|
|
116
124
|
originalPicker.value=String(index); original.src=url.href; original.title=`Original cited source: ${source.filename||source.recordId||'document'}${source.page?`, page ${source.page}`:''}`; original.hidden=false;
|
|
117
125
|
originalLabel.textContent=`Original source${source.page?` at PDF page ${source.page}`:''}. Compare the full page before relying on the extracted text.`;
|
|
118
|
-
|
|
126
|
+
const publication=events.dispatch('source-explanation-source-selected',{index,source:{...source}},{publicDetail:{index,hasSource:true}});
|
|
127
|
+
projectArcaneDOMEvent(host,publication.occurrence,{bubbles:true,composed:true});
|
|
119
128
|
return {...source};
|
|
120
129
|
}catch(error){original.hidden=true; originalLabel.textContent=error.message; return null;}
|
|
121
130
|
}
|
|
122
131
|
|
|
123
|
-
|
|
132
|
+
function destroy(){
|
|
133
|
+
if(destroyed) return false;
|
|
134
|
+
destroyed=true;
|
|
135
|
+
host.ready=false;
|
|
136
|
+
original.removeAttribute('src');
|
|
137
|
+
events.dispose();
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
host.ready=true;
|
|
142
|
+
const readyPublication=events.dispatch('source-explanation-ready',{}, {publicDetail:{ready:true}});
|
|
143
|
+
projectArcaneDOMEvent(host,readyPublication.occurrence,{bubbles:true,composed:true});
|
|
124
144
|
</script>
|