arcane-os 0.1.0-dev.5
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 +154 -0
- package/COMMERCIAL-LICENSE.md +13 -0
- package/LICENSE +661 -0
- package/NOTICE +70 -0
- package/README.md +448 -0
- package/bin/arcane-test.mjs +741 -0
- package/bin/arcane.mjs +5 -0
- package/docs/architecture.md +234 -0
- package/docs/compatibility.md +36 -0
- package/docs/event-manager.md +166 -0
- package/docs/platform-targets.md +108 -0
- package/docs/publishing.md +203 -0
- package/docs/reference/README.md +117 -0
- package/docs/reference/arcane-ollama.md +288 -0
- package/docs/reference/availability-and-normalization.md +123 -0
- package/docs/reference/behavioral-testing.md +86 -0
- package/docs/reference/cli.md +569 -0
- package/docs/reference/core/README.md +62 -0
- package/docs/reference/core/arcane-ai-contracts.md +873 -0
- package/docs/reference/core/arcane-api.md +601 -0
- package/docs/reference/core/arcane-entities.md +65 -0
- package/docs/reference/core/arcane-events.md +134 -0
- package/docs/reference/core/ollama-module.md +181 -0
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
- package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
- package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
- package/docs/reference/event-manager.md +957 -0
- package/docs/reference/inventory/package-api.json +2632 -0
- package/docs/reference/inventory/runtime-components.json +934 -0
- package/docs/reference/inventory/runtime-entities.json +26 -0
- package/docs/reference/inventory/runtime-modules.json +1249 -0
- package/docs/reference/protocols.md +242 -0
- package/docs/reference/runtime-components.md +1098 -0
- package/docs/reference/runtime-entities.md +303 -0
- package/docs/reference/runtime-modules.md +2010 -0
- package/docs/reference/sdk-api.md +4901 -0
- package/docs/roadmap.md +79 -0
- package/docs/work-amplification.md +124 -0
- package/node_modules/event-pubsub/CHANGELOG.md +55 -0
- package/node_modules/event-pubsub/MIGRATION.md +70 -0
- package/node_modules/event-pubsub/README.md +363 -0
- package/node_modules/event-pubsub/SECURITY.md +37 -0
- package/node_modules/event-pubsub/index.js +141 -0
- package/node_modules/event-pubsub/licence +21 -0
- package/node_modules/event-pubsub/package.json +59 -0
- package/node_modules/strong-type/README.md +408 -0
- package/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/strong-type/index.js +1151 -0
- package/node_modules/strong-type/licence +21 -0
- package/node_modules/strong-type/node.js +125 -0
- package/node_modules/strong-type/package.json +61 -0
- package/package.json +95 -0
- package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
- package/runtime/arcane/components/app-bar.html +468 -0
- package/runtime/arcane/components/assistant-panel.html +715 -0
- package/runtime/arcane/components/calculator.html +8 -0
- package/runtime/arcane/components/chart.html +655 -0
- package/runtime/arcane/components/chat.html +1225 -0
- package/runtime/arcane/components/conversation-view.html +13 -0
- package/runtime/arcane/components/dashboard-config.html +341 -0
- package/runtime/arcane/components/data-maintenance.html +112 -0
- package/runtime/arcane/components/data-view.html +92 -0
- package/runtime/arcane/components/directory-picker.html +197 -0
- package/runtime/arcane/components/document-inspector.html +252 -0
- package/runtime/arcane/components/file-drop.html +264 -0
- package/runtime/arcane/components/file-inspector.html +293 -0
- package/runtime/arcane/components/file-manager.html +1715 -0
- package/runtime/arcane/components/header.html +142 -0
- package/runtime/arcane/components/integration-settings.html +14 -0
- package/runtime/arcane/components/local-ai-status.html +360 -0
- package/runtime/arcane/components/markdown-document.html +1048 -0
- package/runtime/arcane/components/markdown-editor.html +360 -0
- package/runtime/arcane/components/media-embed.html +8 -0
- package/runtime/arcane/components/modal.html +402 -0
- package/runtime/arcane/components/output-panel.html +259 -0
- package/runtime/arcane/components/preferences-form.html +135 -0
- package/runtime/arcane/components/record-timeline.html +105 -0
- package/runtime/arcane/components/relationship-board.html +116 -0
- package/runtime/arcane/components/screen-capture.html +8 -0
- package/runtime/arcane/components/source-code-viewer.html +441 -0
- package/runtime/arcane/components/source-explanation.html +124 -0
- package/runtime/arcane/components/speech.html +365 -0
- package/runtime/arcane/components/summary-strip.html +177 -0
- package/runtime/arcane/components/table.html +77 -0
- package/runtime/arcane/components/task-progress.html +282 -0
- package/runtime/arcane/components/terminal-workspace.html +65 -0
- package/runtime/arcane/components/theme-editor.html +41 -0
- package/runtime/arcane/components/theme-switcher.html +46 -0
- package/runtime/arcane/components/unified-inbox.html +20 -0
- package/runtime/arcane/components/voice-transcription.html +476 -0
- package/runtime/arcane/components/weather-widget.html +8 -0
- package/runtime/arcane/components/web-navigator.html +239 -0
- package/runtime/arcane/css/communications.css +1 -0
- package/runtime/arcane/css/dashboard-config.css +45 -0
- package/runtime/arcane/css/document-site.css +981 -0
- package/runtime/arcane/css/layout.css +438 -0
- package/runtime/arcane/css/primitives.css +321 -0
- package/runtime/arcane/css/theme.css +112 -0
- package/runtime/arcane/css/utility-workspace.css +1 -0
- package/runtime/arcane/entities/ApiModelRecord.js +20 -0
- package/runtime/arcane/entities/Calculation.js +13 -0
- package/runtime/arcane/entities/Chat.js +581 -0
- package/runtime/arcane/entities/CommunicationMessage.js +29 -0
- package/runtime/arcane/entities/CommunicationThread.js +21 -0
- package/runtime/arcane/entities/Document.js +10 -0
- package/runtime/arcane/entities/File.js +143 -0
- package/runtime/arcane/entities/Image.js +135 -0
- package/runtime/arcane/entities/IntentEnvelope.js +834 -0
- package/runtime/arcane/entities/Preference.js +83 -0
- package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
- package/runtime/arcane/entities/TerminalSession.js +46 -0
- package/runtime/arcane/entities/Theme.js +107 -0
- package/runtime/arcane/entities/User.js +1046 -0
- package/runtime/arcane/entities/Weather.js +23 -0
- package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
- package/runtime/arcane/img/arrow-left.png +0 -0
- package/runtime/arcane/img/arrow-right.png +0 -0
- package/runtime/arcane/img/doc.svg +5 -0
- package/runtime/arcane/img/folder.svg +4 -0
- package/runtime/arcane/img/image.svg +5 -0
- package/runtime/arcane/img/refresh.png +0 -0
- package/runtime/arcane/img/send.svg +9 -0
- package/runtime/arcane/img/trash.svg +5 -0
- package/runtime/arcane/img/upload.svg +5 -0
- package/runtime/arcane/modules/AI.js +2048 -0
- package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
- package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
- package/runtime/arcane/modules/AIResponseLength.js +42 -0
- package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
- package/runtime/arcane/modules/AnsiText.js +53 -0
- package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
- package/runtime/arcane/modules/AppDataScope.js +245 -0
- package/runtime/arcane/modules/AppearancePreferences.js +28 -0
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
- package/runtime/arcane/modules/AsyncBoundary.js +161 -0
- package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
- package/runtime/arcane/modules/CalculatorEngine.js +20 -0
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
- package/runtime/arcane/modules/ChartLibrary.js +35 -0
- package/runtime/arcane/modules/ChatRecords.js +13 -0
- package/runtime/arcane/modules/CommunicationAppController.js +43 -0
- package/runtime/arcane/modules/CommunicationHub.js +16 -0
- package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
- package/runtime/arcane/modules/ComponentContracts.js +586 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
- package/runtime/arcane/modules/ConversationActionItems.js +488 -0
- package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
- package/runtime/arcane/modules/ConversationTimebox.js +527 -0
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
- package/runtime/arcane/modules/DBLS.js +171 -0
- package/runtime/arcane/modules/DBOPFS.js +1154 -0
- package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
- package/runtime/arcane/modules/DataMaintenance.js +91 -0
- package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
- package/runtime/arcane/modules/DirectoryPicker.js +109 -0
- package/runtime/arcane/modules/DocumentNavigation.js +223 -0
- package/runtime/arcane/modules/Errors.js +1025 -0
- package/runtime/arcane/modules/GifEncoder.js +29 -0
- package/runtime/arcane/modules/HTMLImport.js +114 -0
- package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
- package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
- package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
- package/runtime/arcane/modules/MD.js +111 -0
- package/runtime/arcane/modules/Mail.js +352 -0
- package/runtime/arcane/modules/MailTransport.mjs +180 -0
- package/runtime/arcane/modules/Marked.min.js +71 -0
- package/runtime/arcane/modules/MemoryRecords.js +44 -0
- package/runtime/arcane/modules/MessageAdvisory.js +38 -0
- package/runtime/arcane/modules/ModelDefinition.js +189 -0
- package/runtime/arcane/modules/Ollama.js +74 -0
- package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
- package/runtime/arcane/modules/OllamaSettings.js +24 -0
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
- package/runtime/arcane/modules/PreferenceStore.js +109 -0
- package/runtime/arcane/modules/QRCode.min.js +1 -0
- package/runtime/arcane/modules/Questionnaire.js +61 -0
- package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
- package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
- package/runtime/arcane/modules/RecordReviewStore.js +94 -0
- package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
- package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
- package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
- package/runtime/arcane/modules/ScreenCapture.js +20 -0
- package/runtime/arcane/modules/SpeechPlayback.js +581 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
- package/runtime/arcane/modules/SystemAppearance.js +20 -0
- package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
- package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
- package/runtime/arcane/modules/TerminalClient.js +52 -0
- package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
- package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
- package/runtime/arcane/modules/ThemeManager.js +131 -0
- package/runtime/arcane/modules/TimeGuard.js +149 -0
- package/runtime/arcane/modules/ToolCallRouter.js +83 -0
- package/runtime/arcane/modules/WaitForComponent.js +102 -0
- package/runtime/arcane/modules/YouTubeMedia.js +16 -0
- package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
- package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
- package/runtime/arcane/modules/uPlot.min.css +1 -0
- package/runtime/arcane/security/arcane-network-policy.json +6 -0
- package/runtime/strong-type/index.js +352 -0
- package/runtime/strong-type/licence +21 -0
- package/runtime/strong-type/package.json +45 -0
- package/schemas/arcane-app-bundle.schema.json +125 -0
- package/schemas/arcane-app.schema.json +329 -0
- package/schemas/arcane-lock.schema.json +86 -0
- package/schemas/arcane-package.schema.json +224 -0
- package/schemas/cli-event.schema.json +122 -0
- package/schemas/event-stack.schema.json +152 -0
- package/schemas/native-build-plan.schema.json +176 -0
- package/schemas/target-adapter.schema.json +117 -0
- package/src/app-descriptor.mjs +500 -0
- package/src/cli/main.mjs +561 -0
- package/src/constants.mjs +31 -0
- package/src/dev-server.mjs +718 -0
- package/src/doctor.mjs +315 -0
- package/src/dom-event-instrumentation.mjs +594 -0
- package/src/errors.mjs +75 -0
- package/src/event-manager.mjs +1342 -0
- package/src/event-queue.mjs +138 -0
- package/src/events.mjs +219 -0
- package/src/index.mjs +177 -0
- package/src/integrated-provider-loader.mjs +432 -0
- package/src/native-plan.mjs +698 -0
- package/src/native-provider-loader.mjs +1126 -0
- package/src/packager/core.mjs +2691 -0
- package/src/process.mjs +353 -0
- package/src/release-bundle.mjs +2523 -0
- package/src/repository.mjs +90 -0
- package/src/runtime.mjs +452 -0
- package/src/scaffold.mjs +380 -0
- package/src/targets/index.mjs +436 -0
- package/src/templates/assets/app-icon.png +0 -0
- package/src/templates/workspace-template.mjs +388 -0
- package/src/testing-loader.mjs +9 -0
- package/src/testing.mjs +427 -0
- package/src/toolchain.mjs +1335 -0
- package/src/update-check.mjs +307 -0
- package/src/workspace.mjs +449 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
|
+
<style>
|
|
3
|
+
:host{display:block}
|
|
4
|
+
.task-progress{overflow:hidden}
|
|
5
|
+
.heading-copy{display:grid;gap:var(--arcane-space-1)}
|
|
6
|
+
.heading-copy p{color:var(--arcane-muted);margin:0}
|
|
7
|
+
.overall{display:grid;gap:var(--arcane-space-2);min-width:min(16rem,100%)}
|
|
8
|
+
.overall-label{font-variant-numeric:tabular-nums;text-align:end}
|
|
9
|
+
progress{accent-color:var(--arcane-action);width:100%}
|
|
10
|
+
.tasks{list-style:none;margin:0;padding:0}
|
|
11
|
+
.task{
|
|
12
|
+
align-items:center;
|
|
13
|
+
border-bottom:1px solid var(--arcane-border);
|
|
14
|
+
display:grid;
|
|
15
|
+
gap:var(--arcane-space-3);
|
|
16
|
+
grid-template-columns:auto minmax(0,1fr) auto;
|
|
17
|
+
padding:var(--arcane-space-3) var(--arcane-space-4);
|
|
18
|
+
}
|
|
19
|
+
.task:last-child{border-bottom:0}
|
|
20
|
+
.task-copy{display:grid;gap:var(--arcane-space-1);min-width:0}
|
|
21
|
+
.task-name{font-weight:650;overflow-wrap:anywhere}
|
|
22
|
+
.task-message{color:var(--arcane-muted);font-size:.88rem;overflow-wrap:anywhere}
|
|
23
|
+
.task-status{text-transform:capitalize}
|
|
24
|
+
.empty{margin:var(--arcane-space-4)}
|
|
25
|
+
@media(max-width:34rem){
|
|
26
|
+
.task{grid-template-columns:auto minmax(0,1fr)}
|
|
27
|
+
.task-status{grid-column:2}
|
|
28
|
+
.overall-label{text-align:start}
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
31
|
+
|
|
32
|
+
<section id="panel" class="task-progress arcane-card" aria-labelledby="taskProgressTitle">
|
|
33
|
+
<header class="arcane-card__header">
|
|
34
|
+
<div class="heading-copy">
|
|
35
|
+
<h2 id="taskProgressTitle" class="arcane-section-heading">Progress</h2>
|
|
36
|
+
<p id="description"></p>
|
|
37
|
+
</div>
|
|
38
|
+
<div id="overall" class="overall" hidden>
|
|
39
|
+
<progress id="overallProgress" max="100" value="0"></progress>
|
|
40
|
+
<span id="overallLabel" class="overall-label"></span>
|
|
41
|
+
</div>
|
|
42
|
+
</header>
|
|
43
|
+
<p id="empty" class="empty arcane-state arcane-state--empty">No tasks yet.</p>
|
|
44
|
+
<ol id="tasks" class="tasks" aria-live="polite"></ol>
|
|
45
|
+
</section>
|
|
46
|
+
|
|
47
|
+
<script type="module">
|
|
48
|
+
const host=this;
|
|
49
|
+
const root=host.shadowRoot;
|
|
50
|
+
const panel=root.querySelector('#panel');
|
|
51
|
+
const heading=root.querySelector('#taskProgressTitle');
|
|
52
|
+
const description=root.querySelector('#description');
|
|
53
|
+
const overall=root.querySelector('#overall');
|
|
54
|
+
const overallProgress=root.querySelector('#overallProgress');
|
|
55
|
+
const overallLabel=root.querySelector('#overallLabel');
|
|
56
|
+
const empty=root.querySelector('#empty');
|
|
57
|
+
const list=root.querySelector('#tasks');
|
|
58
|
+
|
|
59
|
+
const allowedStatuses=new Set(['pending','running','complete','failed','canceled']);
|
|
60
|
+
let activeRun=Promise.resolve([]);
|
|
61
|
+
let options={};
|
|
62
|
+
let running=false;
|
|
63
|
+
let tasks=[];
|
|
64
|
+
|
|
65
|
+
host.clear=clear;
|
|
66
|
+
host.configure=configure;
|
|
67
|
+
host.runTasks=runTasks;
|
|
68
|
+
host.setTasks=setTasks;
|
|
69
|
+
host.updateTask=updateTask;
|
|
70
|
+
|
|
71
|
+
Object.defineProperties(host,{
|
|
72
|
+
running:{get:()=>running},
|
|
73
|
+
snapshot:{get:getSnapshot},
|
|
74
|
+
tasks:{get:getTasks,set:setTasks}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function datasetOptions(){
|
|
78
|
+
return {
|
|
79
|
+
description:host.dataset.description||'',
|
|
80
|
+
emptyLabel:host.dataset.emptyLabel||'No tasks yet.',
|
|
81
|
+
title:host.dataset.title||'Progress'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function configure(input={}){
|
|
86
|
+
options={...datasetOptions(),...options,...input};
|
|
87
|
+
heading.innerText=`${options.title||'Progress'}`;
|
|
88
|
+
description.innerText=`${options.description||''}`;
|
|
89
|
+
description.hidden=!description.innerText;
|
|
90
|
+
empty.innerText=`${options.emptyLabel||'No tasks yet.'}`;
|
|
91
|
+
return {...options};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setTasks(values=[]){
|
|
95
|
+
const ids=new Set();
|
|
96
|
+
tasks=Array.from(values||[]).map((value,index)=>{
|
|
97
|
+
const task=normalizeTask(value,index);
|
|
98
|
+
if(ids.has(task.id)){
|
|
99
|
+
task.id=`${task.id}-${index+1}`;
|
|
100
|
+
}
|
|
101
|
+
ids.add(task.id);
|
|
102
|
+
return task;
|
|
103
|
+
});
|
|
104
|
+
render();
|
|
105
|
+
emitChange();
|
|
106
|
+
return getTasks();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeTask(value={},index=0){
|
|
110
|
+
const status=allowedStatuses.has(value.status)?value.status:'pending';
|
|
111
|
+
return {
|
|
112
|
+
id:`${value.id??`task-${index+1}`}`,
|
|
113
|
+
message:`${value.message??''}`,
|
|
114
|
+
name:`${value.name??value.label??`Task ${index+1}`}`,
|
|
115
|
+
status
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function updateTask(id,update={}){
|
|
120
|
+
const index=typeof id==='number'?id:tasks.findIndex(task=>task.id===`${id}`);
|
|
121
|
+
if(index<0||index>=tasks.length){
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const current=tasks[index];
|
|
125
|
+
const status=allowedStatuses.has(update.status)?update.status:current.status;
|
|
126
|
+
tasks[index]={
|
|
127
|
+
...current,
|
|
128
|
+
...update,
|
|
129
|
+
id:current.id,
|
|
130
|
+
message:`${update.message??current.message}`,
|
|
131
|
+
name:`${update.name??current.name}`,
|
|
132
|
+
status
|
|
133
|
+
};
|
|
134
|
+
render();
|
|
135
|
+
emitChange(tasks[index]);
|
|
136
|
+
return {...tasks[index]};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function render(){
|
|
140
|
+
const fragment=document.createDocumentFragment();
|
|
141
|
+
for(const task of tasks){
|
|
142
|
+
const item=document.createElement('li');
|
|
143
|
+
const light=document.createElement('span');
|
|
144
|
+
const copy=document.createElement('span');
|
|
145
|
+
const name=document.createElement('span');
|
|
146
|
+
const message=document.createElement('span');
|
|
147
|
+
const status=document.createElement('span');
|
|
148
|
+
|
|
149
|
+
item.className='task';
|
|
150
|
+
item.dataset.taskId=task.id;
|
|
151
|
+
item.dataset.status=task.status;
|
|
152
|
+
light.className='arcane-status-light';
|
|
153
|
+
light.dataset.status=task.status;
|
|
154
|
+
light.setAttribute('aria-hidden','true');
|
|
155
|
+
copy.className='task-copy';
|
|
156
|
+
name.className='task-name';
|
|
157
|
+
name.innerText=task.name;
|
|
158
|
+
message.className='task-message';
|
|
159
|
+
message.innerText=task.message;
|
|
160
|
+
message.hidden=!task.message;
|
|
161
|
+
status.className='task-status arcane-badge';
|
|
162
|
+
status.dataset.status=statusTone(task.status);
|
|
163
|
+
status.innerText=statusLabel(task.status);
|
|
164
|
+
copy.append(name,message);
|
|
165
|
+
item.append(light,copy,status);
|
|
166
|
+
fragment.append(item);
|
|
167
|
+
}
|
|
168
|
+
list.replaceChildren(fragment);
|
|
169
|
+
empty.hidden=tasks.length>0;
|
|
170
|
+
list.hidden=!tasks.length;
|
|
171
|
+
syncOverall();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function syncOverall(){
|
|
175
|
+
const completed=tasks.filter(task=>['complete','failed','canceled'].includes(task.status)).length;
|
|
176
|
+
const percent=tasks.length?Math.round(completed/tasks.length*100):0;
|
|
177
|
+
overall.hidden=!tasks.length;
|
|
178
|
+
overallProgress.value=percent;
|
|
179
|
+
overallLabel.innerText=tasks.length?`${completed} of ${tasks.length} finished`:'';
|
|
180
|
+
panel.setAttribute('aria-busy',`${running}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function runTasks(title='Progress',jobs=[]){
|
|
184
|
+
if(running){
|
|
185
|
+
return activeRun;
|
|
186
|
+
}
|
|
187
|
+
const runnable=Array.from(jobs||[]).filter(job=>job&&typeof job.task==='function');
|
|
188
|
+
if(!runnable.length){
|
|
189
|
+
setTasks([]);
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
configure({title});
|
|
193
|
+
setTasks(runnable.map((job,index)=>({
|
|
194
|
+
id:job.id??`task-${index+1}`,
|
|
195
|
+
message:job.message||'',
|
|
196
|
+
name:job.name||job.label||`Task ${index+1}`,
|
|
197
|
+
status:'pending'
|
|
198
|
+
})));
|
|
199
|
+
running=true;
|
|
200
|
+
syncOverall();
|
|
201
|
+
host.dispatchEvent(new CustomEvent('task-progress-started',{
|
|
202
|
+
bubbles:true,
|
|
203
|
+
composed:true,
|
|
204
|
+
detail:getSnapshot()
|
|
205
|
+
}));
|
|
206
|
+
activeRun=Promise.allSettled(runnable.map(async(job,index)=>{
|
|
207
|
+
updateTask(index,{status:'running'});
|
|
208
|
+
try{
|
|
209
|
+
const value=await job.task();
|
|
210
|
+
updateTask(index,{status:'complete',message:job.completeMessage||''});
|
|
211
|
+
return value;
|
|
212
|
+
}catch(error){
|
|
213
|
+
updateTask(index,{status:'failed',message:job.errorMessage||error?.message||'Unable to complete.'});
|
|
214
|
+
host.dispatchEvent(new CustomEvent('task-progress-error',{
|
|
215
|
+
bubbles:true,
|
|
216
|
+
composed:true,
|
|
217
|
+
detail:{error,id:tasks[index]?.id,index,task:{...tasks[index]}}
|
|
218
|
+
}));
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}));
|
|
222
|
+
const results=await activeRun;
|
|
223
|
+
running=false;
|
|
224
|
+
syncOverall();
|
|
225
|
+
host.dispatchEvent(new CustomEvent('task-progress-complete',{
|
|
226
|
+
bubbles:true,
|
|
227
|
+
composed:true,
|
|
228
|
+
detail:{...getSnapshot(),results:results.slice()}
|
|
229
|
+
}));
|
|
230
|
+
return results;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function statusLabel(status){
|
|
234
|
+
return {
|
|
235
|
+
canceled:'Canceled',
|
|
236
|
+
complete:'Complete',
|
|
237
|
+
failed:'Failed',
|
|
238
|
+
pending:'Pending',
|
|
239
|
+
running:'In progress'
|
|
240
|
+
}[status]||status;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function statusTone(status){
|
|
244
|
+
return {
|
|
245
|
+
complete:'success',
|
|
246
|
+
failed:'error',
|
|
247
|
+
pending:'warning',
|
|
248
|
+
running:'warning'
|
|
249
|
+
}[status]||status;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getTasks(){
|
|
253
|
+
return tasks.map(task=>({...task}));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getSnapshot(){
|
|
257
|
+
return {running,tasks:getTasks()};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function emitChange(task=null){
|
|
261
|
+
host.dispatchEvent(new CustomEvent('task-progress-change',{
|
|
262
|
+
bubbles:true,
|
|
263
|
+
composed:true,
|
|
264
|
+
detail:{...getSnapshot(),task:task?{...task}:null}
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function clear(){
|
|
269
|
+
if(running){
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
tasks=[];
|
|
273
|
+
render();
|
|
274
|
+
emitChange();
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
configure(datasetOptions());
|
|
279
|
+
render();
|
|
280
|
+
host.ready=true;
|
|
281
|
+
host.dispatchEvent(new CustomEvent('task-progress-ready',{bubbles:true,composed:true}));
|
|
282
|
+
</script>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
|
+
<style>
|
|
3
|
+
:host{display:block;height:100%;min-height:18rem;--terminal-bg:var(--background,rgb(244, 246, 251));--terminal-fg:var(--text-color,rgb(23, 34, 56));--terminal-muted:var(--muted-text-color,rgb(95, 107, 130));--terminal-accent:var(--focus-color,rgb(118, 87, 213));--terminal-danger:var(--danger-color,rgb(165, 56, 56));--terminal-font:ui-monospace,"Cascadia Code","SFMono-Regular",Consolas,monospace;--terminal-font-size:14px;--terminal-line-height:1.55;--terminal-opacity:1}
|
|
4
|
+
*{box-sizing:border-box}.workspace{background:color-mix(in srgb,var(--terminal-bg) calc(var(--terminal-opacity)*100%),transparent);border:1px solid color-mix(in srgb,var(--terminal-accent) 20%,transparent);border-radius:.85rem;color:var(--terminal-fg);display:grid;grid-template-rows:auto auto minmax(0,1fr) auto;height:100%;overflow:hidden;box-shadow:0 1.5rem 5rem rgba(0,0,0,.28)}
|
|
5
|
+
.tabs,.toolbar{align-items:center;background:color-mix(in srgb,var(--terminal-bg) 88%,white 12%);display:flex;gap:.35rem;min-width:0;padding:.45rem .65rem}.tabs{border-bottom:1px solid color-mix(in srgb,var(--terminal-fg) 10%,transparent)}
|
|
6
|
+
.tab{align-items:center;background:transparent;border:1px solid transparent;border-radius:.45rem;color:var(--terminal-muted);display:flex;font:600 .78rem/1 var(--terminal-font);gap:.35rem;max-width:13rem;padding:.55rem .65rem}.tab[aria-selected="true"]{background:color-mix(in srgb,var(--terminal-accent) 12%,transparent);border-color:color-mix(in srgb,var(--terminal-accent) 30%,transparent);color:var(--terminal-fg)}.tab-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{border:0;background:transparent;color:inherit;cursor:pointer;padding:0 .15rem}.new-session,.tool{background:transparent;border:1px solid transparent;border-radius:.4rem;color:var(--terminal-muted);cursor:pointer;font:600 .78rem/1 var(--terminal-font);padding:.55rem}.new-session:hover,.tool:hover{border-color:color-mix(in srgb,var(--terminal-accent) 25%,transparent);color:var(--terminal-fg)}
|
|
7
|
+
.toolbar{border-bottom:1px solid color-mix(in srgb,var(--terminal-fg) 8%,transparent);justify-content:flex-end}.connection{align-items:center;display:flex;gap:.4rem;margin-right:auto;color:var(--terminal-muted);font:600 .72rem/1 var(--terminal-font);text-transform:uppercase;letter-spacing:.08em}.connection::before{background:var(--terminal-muted);border-radius:50%;content:"";height:.48rem;width:.48rem}.connection[data-state="running"]::before{background:var(--terminal-accent);box-shadow:0 0 .7rem var(--terminal-accent)}.connection[data-state="error"]::before,.connection[data-state="exited"]::before{background:var(--terminal-danger)}
|
|
8
|
+
.search{background:var(--terminal-bg);border:1px solid color-mix(in srgb,var(--terminal-fg) 16%,transparent);border-radius:.4rem;color:var(--terminal-fg);font:inherit;max-width:15rem;padding:.4rem .55rem}.search[hidden]{display:none}
|
|
9
|
+
.output{font:var(--terminal-font-size)/var(--terminal-line-height) var(--terminal-font);overflow:auto;overscroll-behavior:contain;padding:1rem 1.1rem;scrollbar-color:color-mix(in srgb,var(--terminal-accent) 30%,transparent) transparent;white-space:pre-wrap;word-break:break-word}.entry{min-height:1em}.entry.command{color:var(--terminal-accent);font-weight:650}.entry.system{color:var(--terminal-muted)}.entry.error{color:var(--terminal-danger)}mark{background:color-mix(in srgb,var(--terminal-accent) 38%,transparent);color:inherit}
|
|
10
|
+
.prompt{align-items:flex-start;border-top:1px solid color-mix(in srgb,var(--terminal-fg) 10%,transparent);display:grid;font:var(--terminal-font-size)/var(--terminal-line-height) var(--terminal-font);gap:.6rem;grid-template-columns:auto minmax(0,1fr);padding:.75rem 1.1rem}.prompt-label{color:var(--terminal-accent);font-weight:800;padding-top:.34rem}.input{background:transparent;border:0;color:var(--terminal-fg);font:inherit;min-height:2rem;outline:0;padding:.3rem 0;resize:none;width:100%}.input::placeholder{color:var(--terminal-muted)}
|
|
11
|
+
.ansi-bold{font-weight:800}.ansi-dim{opacity:.65}.ansi-italic{font-style:italic}.ansi-underline{text-decoration:underline}.ansi-red,.ansi-bright-red{color:rgb(255, 107, 122)}.ansi-green,.ansi-bright-green{color:rgb(99, 242, 162)}.ansi-yellow,.ansi-bright-yellow{color:rgb(255, 209, 102)}.ansi-blue,.ansi-bright-blue{color:rgb(121, 184, 255)}.ansi-magenta,.ansi-bright-magenta{color:rgb(210, 168, 255)}.ansi-cyan,.ansi-bright-cyan{color:rgb(114, 233, 238)}.ansi-black{color:rgb(135, 147, 141)}.ansi-white,.ansi-bright-white{color:rgb(255, 255, 255)}
|
|
12
|
+
@media(max-width:44rem){.toolbar .tool[data-secondary]{display:none}.workspace{border-radius:0}.output{padding:.8rem}.prompt{padding:.65rem .8rem}}
|
|
13
|
+
</style>
|
|
14
|
+
<section class="workspace" aria-label="Terminal workspace">
|
|
15
|
+
<nav class="tabs" id="tabs" aria-label="Terminal sessions"></nav>
|
|
16
|
+
<div class="toolbar">
|
|
17
|
+
<span class="connection" id="connection" data-state="starting">Starting</span>
|
|
18
|
+
<input class="search" id="search" type="search" placeholder="Find in output" aria-label="Find in terminal output" hidden>
|
|
19
|
+
<button class="tool" id="find" type="button" title="Find (Ctrl+Shift+F)">Find</button>
|
|
20
|
+
<button class="tool" id="copy" data-secondary type="button" title="Copy selected text">Copy</button>
|
|
21
|
+
<button class="tool" id="clear" type="button" title="Clear terminal">Clear</button>
|
|
22
|
+
<button class="tool" id="interrupt" type="button" title="Interrupt process (Ctrl+C)">Ctrl+C</button>
|
|
23
|
+
<button class="tool" id="settings" type="button" title="Terminal settings">Settings</button>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="output" id="output" role="log" aria-live="polite" aria-label="Terminal output" tabindex="0"></div>
|
|
26
|
+
<form class="prompt" id="prompt"><label class="prompt-label" id="promptLabel" for="input">arcane ❯</label><textarea class="input" id="input" rows="1" autocomplete="off" autocapitalize="off" spellcheck="false" placeholder="Enter a command"></textarea></form>
|
|
27
|
+
</section>
|
|
28
|
+
<script type="module">
|
|
29
|
+
const {parseAnsi,stripAnsi}=await import('../modules/AnsiText.js');
|
|
30
|
+
const host=this,root=host.shadowRoot;
|
|
31
|
+
const tabs=root.querySelector('#tabs'),output=root.querySelector('#output'),input=root.querySelector('#input'),prompt=root.querySelector('#prompt'),promptLabel=root.querySelector('#promptLabel'),connection=root.querySelector('#connection'),search=root.querySelector('#search');
|
|
32
|
+
const sessions=new Map();let activeId='',history=[],historyIndex=0,completions=[];
|
|
33
|
+
host.configure=configure;host.addSession=addSession;host.removeSession=removeSession;host.activateSession=activateSession;host.append=append;host.clear=clear;host.setState=setState;host.setTheme=setTheme;host.focus=()=>input.focus();host.ready=true;
|
|
34
|
+
prompt.addEventListener('submit',event=>{event.preventDefault();submit();});
|
|
35
|
+
input.addEventListener('keydown',event=>{
|
|
36
|
+
if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();submit();}
|
|
37
|
+
else if(event.key==='ArrowUp'){event.preventDefault();recall(-1);}
|
|
38
|
+
else if(event.key==='ArrowDown'){event.preventDefault();recall(1);}
|
|
39
|
+
else if(event.key==='Tab'){event.preventDefault();complete();}
|
|
40
|
+
else if(event.key.toLowerCase()==='c'&&event.ctrlKey){const selected=globalThis.getSelection?.()?.toString();if(!selected){event.preventDefault();emit('terminal-interrupt',{sessionId:activeId});}}
|
|
41
|
+
else if(event.key.toLowerCase()==='f'&&event.ctrlKey&&event.shiftKey){event.preventDefault();toggleSearch(true);}
|
|
42
|
+
requestAnimationFrame(resizeInput);
|
|
43
|
+
});
|
|
44
|
+
root.querySelector('#find').onclick=()=>toggleSearch();root.querySelector('#clear').onclick=()=>clear();root.querySelector('#interrupt').onclick=()=>emit('terminal-interrupt',{sessionId:activeId});root.querySelector('#settings').onclick=()=>emit('terminal-settings');
|
|
45
|
+
root.querySelector('#copy').onclick=()=>navigator.clipboard?.writeText(globalThis.getSelection?.()?.toString()||'');
|
|
46
|
+
search.addEventListener('input',render);search.addEventListener('keydown',event=>{if(event.key==='Escape')toggleSearch(false);});
|
|
47
|
+
function configure(options={}){history=Array.from(options.history||[]);historyIndex=history.length;completions=Array.from(options.completions||[]);promptLabel.textContent=String(options.prompt||'arcane ❯');if(options.theme)setTheme(options.theme);return host;}
|
|
48
|
+
function addSession(session){sessions.set(session.id,{...session,entries:session.entries||[]});if(!activeId)activeId=session.id;renderTabs();render();setState(session.state||'starting');}
|
|
49
|
+
function removeSession(id){sessions.delete(id);if(activeId===id)activeId=sessions.keys().next().value||'';renderTabs();render();}
|
|
50
|
+
function activateSession(id){if(!sessions.has(id))return;activeId=id;renderTabs();render();setState(sessions.get(id).state||'running');input.focus();emit('terminal-session-select',{sessionId:id});}
|
|
51
|
+
function append(text,kind='output',sessionId=activeId){const session=sessions.get(sessionId);if(!session)return;session.entries.push({text:String(text??''),kind});if(session.entries.length>4000)session.entries.splice(0,session.entries.length-4000);if(sessionId===activeId)render(true);}
|
|
52
|
+
function clear(sessionId=activeId){const session=sessions.get(sessionId);if(session)session.entries=[];render();emit('terminal-clear',{sessionId});}
|
|
53
|
+
function setState(state,id=activeId){const session=sessions.get(id);if(session)session.state=state;if(id===activeId){connection.dataset.state=state;connection.textContent=state;}}
|
|
54
|
+
function setTheme(theme={}){for(const [key,value] of Object.entries(theme)){const name=`--terminal-${key.replace(/[A-Z]/g,match=>`-${match.toLowerCase()}`)}`;host.style.setProperty(name,String(value));}}
|
|
55
|
+
function submit(){const line=input.value.trimEnd();if(!line.trim())return;if(history.at(-1)!==line)history.push(line);historyIndex=history.length;append(`${promptLabel.textContent} ${line}\n`,'command');input.value='';resizeInput();emit('terminal-submit',{sessionId:activeId,line,history:[...history]});}
|
|
56
|
+
function recall(direction){if(!history.length)return;historyIndex=Math.max(0,Math.min(history.length,historyIndex+direction));input.value=historyIndex===history.length?'':history[historyIndex];resizeInput();input.setSelectionRange(input.value.length,input.value.length);}
|
|
57
|
+
function complete(){const prefix=input.value.split(/\s/).at(-1).toLowerCase();const matches=completions.filter(value=>value.toLowerCase().startsWith(prefix));if(matches.length===1)input.value=input.value.slice(0,-prefix.length)+matches[0];else if(matches.length>1)append(`${matches.join(' ')}\n`,'system');resizeInput();}
|
|
58
|
+
function renderTabs(){const fragment=document.createDocumentFragment();for(const session of sessions.values()){const button=document.createElement('button');button.className='tab';button.type='button';button.setAttribute('role','tab');button.setAttribute('aria-selected',String(session.id===activeId));button.innerHTML=`<span class="tab-label"></span><span class="tab-close" aria-label="Close session">×</span>`;button.querySelector('.tab-label').textContent=session.title||session.shell||'Terminal';button.onclick=event=>{if(event.target.classList.contains('tab-close'))emit('terminal-session-close',{sessionId:session.id});else activateSession(session.id);};fragment.append(button);}const add=document.createElement('button');add.className='new-session';add.type='button';add.textContent='+';add.title='New terminal session';add.onclick=()=>emit('terminal-session-new');fragment.append(add);tabs.replaceChildren(fragment);}
|
|
59
|
+
function render(stick=false){const session=sessions.get(activeId);const atEnd=output.scrollHeight-output.scrollTop-output.clientHeight<48;const term=search.value.toLowerCase();const fragment=document.createDocumentFragment();for(const entry of session?.entries||[]){const row=document.createElement('div');row.className=`entry ${entry.kind||'output'}`;renderText(row,entry.text,term);fragment.append(row);}output.replaceChildren(fragment);if(stick&&(atEnd||!output.scrollTop))output.scrollTop=output.scrollHeight;}
|
|
60
|
+
function renderText(row,text,term){const clean=stripAnsi(text);if(!term){for(const token of parseAnsi(text)){const span=document.createElement('span');span.textContent=token.text;const classes=[];if(token.style.bold)classes.push('ansi-bold');if(token.style.dim)classes.push('ansi-dim');if(token.style.italic)classes.push('ansi-italic');if(token.style.underline)classes.push('ansi-underline');if(token.style.foreground&&!token.style.foreground.startsWith('index-'))classes.push(`ansi-${token.style.foreground}`);span.className=classes.join(' ');row.append(span);}return;}let cursor=0,index=clean.toLowerCase().indexOf(term);while(index>=0){row.append(document.createTextNode(clean.slice(cursor,index)));const mark=document.createElement('mark');mark.textContent=clean.slice(index,index+term.length);row.append(mark);cursor=index+term.length;index=clean.toLowerCase().indexOf(term,cursor);}row.append(document.createTextNode(clean.slice(cursor)));}
|
|
61
|
+
function toggleSearch(force){search.hidden=force===undefined?!search.hidden:!force;if(!search.hidden)search.focus();else{search.value='';render();input.focus();}}
|
|
62
|
+
function resizeInput(){input.style.height='auto';input.style.height=`${Math.min(input.scrollHeight,160)}px`;}
|
|
63
|
+
function emit(type,detail={}){host.dispatchEvent(new CustomEvent(type,{bubbles:true,composed:true,detail}));}
|
|
64
|
+
host.dispatchEvent(new CustomEvent('terminal-workspace-ready',{bubbles:true,composed:true}));
|
|
65
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
|
+
<style>
|
|
3
|
+
:host{display:block;min-width:0}.intro{display:grid;gap:var(--arcane-space-1)}.intro p{margin:0}.editor{display:grid;gap:var(--arcane-space-4)}.identity{display:grid;gap:var(--arcane-space-4);grid-template-columns:2fr 1fr}.colors{display:grid;gap:var(--arcane-space-3);grid-template-columns:repeat(auto-fit,minmax(11rem,1fr))}.color-field{align-items:center;display:grid;gap:.65rem;grid-template-columns:2.5rem minmax(0,1fr)}.color-field input{height:2.5rem;padding:.15rem;width:2.5rem}.color-field label{font-weight:650}.status{margin-right:auto;min-height:1.4em}.preview{border:1px solid var(--border-color);border-radius:var(--arcane-radius-medium);display:flex;gap:.6rem;padding:.8rem;background:var(--background);color:var(--text-color)}.preview span{border-radius:.4rem;padding:.5rem .7rem;background:var(--secondary-color)}.preview strong{border-radius:.4rem;padding:.5rem .7rem;background:var(--primary-color);color:var(--button-text-color)}@media(max-width:36rem){.identity{grid-template-columns:1fr}}
|
|
4
|
+
</style>
|
|
5
|
+
<form class="arcane-card editor">
|
|
6
|
+
<header class="arcane-card__header intro"><h2 class="arcane-section-heading">Create your own skin</h2><p class="arcane-help">Choose a small set of safe design tokens. Compatible Arcane apps use the same saved skin.</p></header>
|
|
7
|
+
<div class="arcane-card__body editor">
|
|
8
|
+
<div class="identity">
|
|
9
|
+
<div class="arcane-field"><label for="themeName">Skin name</label><input id="themeName" name="name" maxlength="40" required></div>
|
|
10
|
+
<div class="arcane-field"><label for="themeScheme">Starting contrast</label><select id="themeScheme" name="scheme"><option value="light">Light</option><option value="dark">Dark</option></select></div>
|
|
11
|
+
</div>
|
|
12
|
+
<div id="colors" class="colors"></div>
|
|
13
|
+
<div class="preview" aria-label="Skin preview"><span>Surface</span><strong>Primary action</strong></div>
|
|
14
|
+
</div>
|
|
15
|
+
<footer class="arcane-card__footer"><p id="status" class="status arcane-help" role="status" aria-live="polite"></p><button id="reset" class="arcane-button arcane-button--tertiary" type="button">Remove custom skin</button><button class="arcane-button" type="submit">Save and use skin</button></footer>
|
|
16
|
+
</form>
|
|
17
|
+
<script type="module">
|
|
18
|
+
const host=this;
|
|
19
|
+
const root=host.shadowRoot;
|
|
20
|
+
const form=root.querySelector('form');
|
|
21
|
+
const colors=root.querySelector('#colors');
|
|
22
|
+
const status=root.querySelector('#status');
|
|
23
|
+
const {default:Theme,themeColorToHex,themeTokens}=await import('../entities/Theme.js');
|
|
24
|
+
|
|
25
|
+
host.configure=configure;host.getTheme=getTheme;host.setTheme=setTheme;host.setBusy=setBusy;host.setStatus=setStatus;
|
|
26
|
+
colors.replaceChildren(...themeTokens.map(token=>{
|
|
27
|
+
const field=document.createElement('div'),input=document.createElement('input'),label=document.createElement('label');
|
|
28
|
+
field.className='color-field';input.type='color';input.id=`theme-${token.key}`;input.name=token.key;input.value=themeColorToHex(token.defaultValue);label.htmlFor=input.id;label.textContent=token.label;field.append(input,label);return field;
|
|
29
|
+
}));
|
|
30
|
+
form.addEventListener('input',()=>emit('theme-preview',{theme:getTheme()}));
|
|
31
|
+
form.addEventListener('submit',event=>{event.preventDefault();emit('theme-save',{theme:getTheme()});});
|
|
32
|
+
root.querySelector('#reset').addEventListener('click',()=>emit('theme-reset'));
|
|
33
|
+
|
|
34
|
+
function emit(name,detail={}){host.dispatchEvent(new CustomEvent(name,{bubbles:true,composed:true,detail}));}
|
|
35
|
+
function getTheme(){return new Theme({name:form.elements.name.value,scheme:form.elements.scheme.value,tokens:Object.fromEntries(themeTokens.map(token=>[token.key,form.elements[token.key].value]))});}
|
|
36
|
+
function setTheme(input={}){const theme=Theme.fromJSON(input);form.elements.name.value=theme.name;form.elements.scheme.value=theme.scheme;for(const token of themeTokens) form.elements[token.key].value=themeColorToHex(theme.tokens[token.key]);return theme;}
|
|
37
|
+
function configure(options={}){return setTheme(options.theme||{name:'My Arcane skin'});}
|
|
38
|
+
function setBusy(value=true){for(const control of form.elements) control.disabled=Boolean(value);form.setAttribute('aria-busy',String(Boolean(value)));}
|
|
39
|
+
function setStatus(value=''){status.textContent=String(value||'');return status.textContent;}
|
|
40
|
+
configure();host.ready=true;emit('theme-editor-ready');
|
|
41
|
+
</script>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
:host{display:inline-block;color:inherit}
|
|
3
|
+
.switcher{align-items:center;display:flex;gap:.15rem;padding:.2rem;border:1px solid var(--app-bar-border,var(--border-color,rgba(255,255,255,.25)));border-radius:999px;background:color-mix(in srgb,currentColor 8%,transparent)}
|
|
4
|
+
button{appearance:none;border:0;border-radius:999px;background:transparent;color:inherit;cursor:pointer;font:700 .68rem/1 system-ui,sans-serif;min-height:1.85rem;padding:.42rem .58rem}
|
|
5
|
+
button:hover{background:color-mix(in srgb,currentColor 14%,transparent)}
|
|
6
|
+
button[aria-pressed="true"]{background:var(--app-bar-accent,var(--secondary-color,#c49a52));color:var(--app-bar-active-text,var(--primary-color,#172238))}
|
|
7
|
+
button:focus-visible{outline:.18rem solid var(--focus-color,currentColor);outline-offset:.12rem}
|
|
8
|
+
@media(max-width:32rem){button{font-size:0;padding-inline:.5rem}button::first-letter{font-size:.72rem}}
|
|
9
|
+
</style>
|
|
10
|
+
<div class="switcher" role="group" aria-label="Color scheme">
|
|
11
|
+
<button type="button" data-scheme="system" aria-pressed="false" title="Use device theme">Auto</button>
|
|
12
|
+
<button type="button" data-scheme="light" aria-pressed="false" title="Use light theme">Light</button>
|
|
13
|
+
<button type="button" data-scheme="dark" aria-pressed="false" title="Use dark theme">Dark</button>
|
|
14
|
+
<button type="button" data-scheme="custom" aria-pressed="false" title="Use my custom skin" disabled>Skin</button>
|
|
15
|
+
</div>
|
|
16
|
+
<script type="module">
|
|
17
|
+
const host=this;
|
|
18
|
+
const root=host.shadowRoot;
|
|
19
|
+
const buttons=Array.from(root.querySelectorAll('[data-scheme]'));
|
|
20
|
+
const {default:ThemeManager}=await import('../modules/ThemeManager.js');
|
|
21
|
+
const manager=new ThemeManager();
|
|
22
|
+
|
|
23
|
+
host.setMode=setMode;
|
|
24
|
+
host.refresh=refresh;
|
|
25
|
+
root.querySelector('.switcher').addEventListener('click',async event=>{
|
|
26
|
+
const button=event.target.closest('[data-scheme]');
|
|
27
|
+
if(!button) return;
|
|
28
|
+
setBusy(true);
|
|
29
|
+
try{
|
|
30
|
+
const state=button.dataset.scheme==='custom'?await manager.activateCustom():await manager.setScheme(button.dataset.scheme);
|
|
31
|
+
setMode(state.mode);setCustomAvailable(Boolean(state.theme));
|
|
32
|
+
}finally{setBusy(false);setCustomAvailable(Boolean(manager.customTheme));}
|
|
33
|
+
});
|
|
34
|
+
globalThis.addEventListener('arcane-theme-change',()=>refresh().catch(()=>{}));
|
|
35
|
+
|
|
36
|
+
function setMode(mode='system'){
|
|
37
|
+
for(const button of buttons) button.setAttribute('aria-pressed',String(button.dataset.scheme===mode));
|
|
38
|
+
host.dataset.mode=mode;
|
|
39
|
+
return mode;
|
|
40
|
+
}
|
|
41
|
+
function setBusy(value){for(const button of buttons) button.disabled=Boolean(value);}
|
|
42
|
+
function setCustomAvailable(value){root.querySelector('[data-scheme="custom"]').disabled=!value;}
|
|
43
|
+
async function refresh(){const state=await manager.load();setMode(state.mode);setCustomAvailable(Boolean(state.theme));return state;}
|
|
44
|
+
|
|
45
|
+
refresh().catch(()=>setMode('system'));
|
|
46
|
+
</script>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
|
|
2
|
+
<style>
|
|
3
|
+
:host{display:block;min-width:0}.toolbar{display:grid;gap:.75rem;padding:1rem;border-bottom:1px solid var(--border-color,#ccd4df)}.toolbar-row{display:flex;gap:.5rem}.toolbar input{min-width:0;width:100%}.filters{display:flex;gap:.4rem;overflow:auto}.filters button{white-space:nowrap}.list{display:grid;max-height:calc(100vh - 17rem);overflow:auto}.thread{display:grid;gap:.35rem;width:100%;padding:1rem;border:0;border-bottom:1px solid var(--border-color,#ccd4df);color:inherit;background:transparent;text-align:left}.thread:hover,.thread[aria-current="true"]{background:var(--secondary-color,#e8edf4)}.thread:focus-visible{outline:.18rem solid var(--focus-color,#7657d5);outline-offset:-.2rem}.line{align-items:center;display:flex;gap:.5rem;justify-content:space-between}.title{overflow:hidden;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.preview{color:var(--muted-text-color,#607080);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meta{align-items:center;display:flex;gap:.4rem;font-size:.75rem}.badge{border:1px solid var(--border-color,#ccd4df);border-radius:999px;padding:.15rem .45rem;text-transform:uppercase}.unread{display:grid;place-items:center;min-width:1.4rem;height:1.4rem;border-radius:999px;color:var(--button-text-color,#fff);background:var(--focus-color,#7657d5);font-weight:800}.empty{padding:2rem;text-align:center}.empty p{color:var(--muted-text-color,#607080)}
|
|
4
|
+
</style>
|
|
5
|
+
<section aria-label="Unified inbox">
|
|
6
|
+
<div class="toolbar"><div class="toolbar-row"><input id="search" type="search" placeholder="Search conversations" aria-label="Search conversations"><button id="refresh" class="arcane-button arcane-button--tertiary" type="button">Refresh</button></div><div id="filters" class="filters" aria-label="Channel filters"></div></div>
|
|
7
|
+
<div id="list" class="list"></div>
|
|
8
|
+
<div id="empty" class="empty"><strong>No conversations yet</strong><p>Connect a service in settings to bring conversations into this inbox.</p></div>
|
|
9
|
+
</section>
|
|
10
|
+
<script type="module">
|
|
11
|
+
const host=this,root=host.shadowRoot,list=root.querySelector('#list'),empty=root.querySelector('#empty'),search=root.querySelector('#search'),filters=root.querySelector('#filters');let threads=[],activeId='',channel='all',providers={};
|
|
12
|
+
host.configure=options=>{providers=Object.fromEntries(Array.from(options?.providers||[],item=>[item.id,item]));renderFilters(options?.channels);return host.setThreads(options?.threads||[])};
|
|
13
|
+
host.setThreads=values=>{threads=Array.from(values||[]);render();return threads};host.setActive=id=>{activeId=String(id||'');render();};host.setLoading=value=>{root.querySelector('#refresh').disabled=Boolean(value)};
|
|
14
|
+
root.querySelector('#refresh').addEventListener('click',()=>host.dispatchEvent(new CustomEvent('inbox-refresh',{bubbles:true,composed:true})));
|
|
15
|
+
search.addEventListener('input',render);
|
|
16
|
+
function renderFilters(values){const names=['all',...new Set(values||[])];filters.replaceChildren(...names.map(name=>{const button=document.createElement('button');button.type='button';button.className='arcane-button arcane-button--tertiary';button.textContent=name==='all'?'All':name.toUpperCase();button.setAttribute('aria-pressed',String(channel===name));button.addEventListener('click',()=>{channel=name;renderFilters(values);render()});return button}))}
|
|
17
|
+
function render(){const term=search.value.trim().toLowerCase();const visible=threads.filter(item=>(channel==='all'||item.channel===channel)&&(!term||`${item.title} ${item.preview} ${(item.participants||[]).join(' ')}`.toLowerCase().includes(term)));empty.hidden=visible.length>0;list.replaceChildren(...visible.map(makeThread));}
|
|
18
|
+
function makeThread(item){const button=document.createElement('button');button.type='button';button.className='thread';button.setAttribute('aria-current',String(item.id===activeId));const top=document.createElement('span'),title=document.createElement('span'),time=document.createElement('time'),preview=document.createElement('span'),meta=document.createElement('span'),badge=document.createElement('span'),provider=document.createElement('span');top.className='line';title.className='title';title.textContent=item.title;time.dateTime=item.updatedAt;time.textContent=new Intl.DateTimeFormat(undefined,{month:'short',day:'numeric'}).format(new Date(item.updatedAt));preview.className='preview';preview.textContent=item.preview||'No preview';meta.className='meta';badge.className='badge';badge.textContent=item.channel;provider.textContent=providers[item.providerId]?.label||item.providerId;top.append(title,time);meta.append(badge,provider);if(item.unreadCount){const unread=document.createElement('span');unread.className='unread';unread.textContent=String(item.unreadCount);meta.append(unread)}button.append(top,preview,meta);button.addEventListener('click',()=>host.dispatchEvent(new CustomEvent('thread-select',{bubbles:true,composed:true,detail:{thread:item}})));return button;}
|
|
19
|
+
host.ready=true;host.dispatchEvent(new CustomEvent('unified-inbox-ready',{bubbles:true,composed:true}));
|
|
20
|
+
</script>
|