Setting the file. One moment.
App · Wix Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page — line 43
This file
Number 45.44
Position 44 of 182
Type JavaScript
Size 23 KB
Lines 117 console/ app.js
JavaScript · 117 lines · 23 KB
{})
=>
{
const
join
=
path.
includes
(
'?'
)
?
'&'
:
'?'
;
const
res
=
await
fetch
(
`${
path
}${
join
}token=${
encodeURIComponent
(
token
)
}`
, {
...
options, headers: {
'content-type'
:
'application/json'
,
...
(options.headers
||
{}) } });
const
data
=
await
res.
json
();
if
(
!
res.ok)
throw
new
Error
(data.error
||
'Request failed'
);
return
data; };
4 const esc = ( value ) => String (value ?? '' ). replace ( / [&<>"] / g , ( c ) => ({ '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' }[c]));
5 function routeState () { const params = new URLSearchParams (location.search); return { project: params. get ( 'project' ), request: params. get ( 'request' ), artifact: params. get ( 'artifact' ), cursor: params. get ( 'cursor' ) }; }
6 function updateRoute ( values ) { const url = new URL (location.href); for ( const [ key , value ] of Object. entries (values)) { if (value === null || value === undefined || value === '' ) url.searchParams. delete (key); else url.searchParams. set (key, value); } history. replaceState ( null , '' , `${ url . pathname }?${ url . searchParams . toString () }${ url . hash }` ); }
7 function statusName ( s ) { if ( /_running $ / . test (s || '' )) return 'Ready to continue' ; return ({ running: 'Running' , waiting_for_user: 'Waiting for you' , awaiting_user_action: 'Waiting for you' , blocked: 'Blocked' , canceled: 'Canceled' , interrupted: 'Interrupted' , timed_out: 'Timed out' , completed: 'Completed' , failed: 'Failed' })[s] || 'Ready to start' ; }
8 function readable ( value ) { return String (value || '' ). replaceAll ( '_' , ' ' ). replace ( / \b \w / g , ( c ) => c. toUpperCase ()); }
9 function structuredArtifact ( file ) {
10 const structured = /(?:application \/ (?:json | x-ndjson) | (?:json | jsonl | ndjson))/ . test ( `${ file . mediaType } ${ file . path }` . toLowerCase ());
11 if ( ! structured) return null ;
12 try {
13 const isLines = /(?:jsonl | ndjson)/ . test ( `${ file . mediaType } ${ file . path }` . toLowerCase ());
14 return isLines ? file.content. split ( ' \n ' ). filter (Boolean). map (( line ) => JSON . parse (line)) : JSON . parse (file.content);
15 } catch { return null ; }
16 }
17 function jsonMarkup ( value ) {
18 const raw = JSON . stringify (value, null , 2 );
19 return raw. replace ( /("(?: \\ . | [ ^ " \\ ] ) * ")( \s * :) ?|\b (true | false | null) \b| - ? \d + (?: \. \d + ) ? (?: [eE][+-] ? \d + ) ? / g , ( match , string , colon , literal ) => {
20 const kind = string ? (colon ? 'json-key' : 'json-string' ) : literal ? 'json-literal' : 'json-number' ;
21 return `<span class="${ kind }">${ esc ( string || literal || match ) }</span>${ colon || ''}` ;
22 });
23 }
24 function fileLanguage ( file ) {
25 const source = `${ file . mediaType || ''} ${ file . path || ''}` . toLowerCase ();
26 if ( /(?:application \/ (?:json | x-ndjson) | (?:json | jsonl | ndjson))/ . test (source)) return 'json' ;
27 if ( /(?:javascript | ecmascript | \. m ? js \b| \. cjs \b )/ . test (source)) return 'javascript' ;
28 if ( /(?:typescript | \. tsx ?\b )/ . test (source)) return 'typescript' ;
29 if ( /(?:text \/ css | \. css \b )/ . test (source)) return 'css' ;
30 if ( /(?:text \/ html | \. html ?\b )/ . test (source)) return 'html' ;
31 if ( /(?:text \/ markdown | \. md \b )/ . test (source)) return 'markdown' ;
32 return 'text' ;
33 }
34 function codeMarkup ( content , language ) {
35 const escaped = esc (content);
36 if (language === 'json' ) return escaped;
37 if (language === 'html' ) return escaped. replace ( /(< \/ ? )( [A-Za-z][\w-] * )( [ ^ &] *? )( \/ ?? >)/ g , '$1<span class="syntax-tag">$2</span>$3$4' );
38 if (language === 'markdown' ) return escaped. replace ( / ^ (# {1,6} \s. * ) $ / gm , '<span class="syntax-heading">$1</span>' ). replace ( /(` [ ^ `] + `)/ g , '<span class="syntax-code">$1</span>' );
39 if (language === 'javascript' || language === 'typescript' ) return escaped. replace ( /( \/\/ [ ^ \n] *| \/\* [\s\S] *? \*\/ )/ g , '<span class="syntax-comment">$1</span>' ). replace ( / \b (const | let | var | function | async | await | return | if | else | for | while | class | new | import | from | export | throw | try | catch) \b / g , '<span class="syntax-keyword">$1</span>' );
40 if (language === 'css' ) return escaped. replace ( /( \/\* [\s\S] *? \*\/ )/ g , '<span class="syntax-comment">$1</span>' ). replace ( /( ^| \n )( [ ^ {}\n] + )(?= \s * \{ )/ g , '$1<span class="syntax-selector">$2</span>' );
41 return escaped;
42 }
43 async function projects () { try { const data = await api ( '/api/projects' ); q ( '#projects' ).innerHTML = data.projects. map (( p ) => `<button class="project ${ p . project === selected ? 'active' : ''}" data-project="${ esc ( p . project ) }">${ esc ( p . project ) }<small>${ esc ( statusName ( p . router ?. state || p . activeRun ?. status || p . orchestration ?. status )) }</small></button>` ). join ( '' ) || '<p class="muted">No migration projects yet.</p>' ; document. querySelectorAll ( '[data-project]' ). forEach (( b ) => b. onclick = () => openProject (b.dataset.project)); } catch (e) { q ( '#projects' ).textContent = e.message; } }
44 function eventMarkup ( event ) { return `<details class="event ${ event . type === 'heartbeat' ? 'heartbeat' : ''}" data-event-id="${ esc ( event . eventId || '' ) }"><summary><strong>${ esc ( readable ( event . type )) }</strong><small>${ esc ( event . message || '' ) } · ${ esc ( event . timestamp ? new Date ( event . timestamp ). toLocaleString () : 'just now' ) }</small></summary><pre>${ esc ( JSON . stringify ( event , null , 2 )) }</pre></details>` ; }
45 function appendActivity ( event ) { const feed = q ( '.activity-feed' ); if ( ! feed) return ; feed. querySelector ( '.empty-activity' )?. remove (); feed. insertAdjacentHTML ( 'afterbegin' , eventMarkup (event)); }
46 function taskIcon ( state ) { return [ 'starting' , 'running' ]. includes (state) ? '<span class="spinner" aria-hidden="true"></span>' : state === 'completed' ? '<span class="task-check" aria-hidden="true">✓</span>' : '<span class="task-state-icon" aria-hidden="true">•</span>' ; }
47 function workMarkup ( data ) {
48 const tasks = data?.tasks || []; const agents = data?.agents || []; const runningTasks = tasks. filter (( task ) => task.state === 'running' ). length ; const runningAgents = agents. filter (( agent ) => [ 'starting' , 'running' ]. includes (agent.state)). length ;
49 if ( ! tasks. length ) return `<div class="work-heading"><div><h3>Work</h3><p class="muted">Persisted work state</p></div><p class="muted">No tasks recorded</p></div><p class="muted">Work details will appear after the supervisor records a route. Observed activity below is not treated as task state.</p>` ;
50 const rows = tasks. map (( task ) => {
51 const assigned = agents. filter (( agent ) => agent.taskId === task.taskId); const active = assigned. filter (( agent ) => [ 'starting' , 'running' ]. includes (agent.state));
52 const agentRows = assigned. length ? `<ul class="agent-list">${ assigned . map (( agent ) => `<li class="agent-row agent-${ esc ( agent . state ) } ${ agent . parentAgentId ? 'agent-child' : ''}">${ [ 'starting' , 'running' ]. includes ( agent . state ) ? '<span class="spinner small" aria-hidden="true"></span>' : agent . state === 'completed' ? '<span class="task-check" aria-hidden="true">✓</span>' : '<span class="task-state-icon" aria-hidden="true">•</span>'}<span>${ esc ( agent . label ) }</span><span class="muted">${ esc ( readable ( agent . state )) }</span></li>` ). join ( '' ) }</ul>` : '<p class="agent-empty muted">No agent has been recorded for this task.</p>' ;
53 const topology = active. length && ! active. some (( agent ) => agent.parentAgentId) ? '<p class="agent-empty muted">No additional agent detail reported.</p>' : '' ;
54 return `<li class="task-row task-${ esc ( task . state ) }" data-task-id="${ esc ( task . taskId ) }"><div class="task-main">${ taskIcon ( task . state ) }<div><div class="task-title ${ task . state === 'completed' ? 'task-completed' : ''}">${ esc ( task . title ) }</div><div class="muted">${ esc ( readable ( task . state )) }${ task . route ?. reason ? ` · ${ esc ( readable ( task . route . reason )) }` : ''}</div></div></div>${ agentRows }${ topology }</li>` ;
55 }). join ( '' );
56 return `<div class="work-heading"><div><h3>Work</h3><p class="muted">Persisted work state</p></div><p class="work-summary">${ runningTasks ? `${ runningTasks } running task${ runningTasks === 1 ? '' : 's'}` : 'No running tasks'} · ${ runningAgents } active agent${ runningAgents === 1 ? '' : 's'}</p></div><ul class="task-list">${ rows }</ul>` ;
57 }
58 function renderWork ( tasks ) { const panel = q ( '#work-panel' ); if (panel) panel.innerHTML = workMarkup (tasks); }
59 function panelMarkup ( status , requests ) {
60 const state = status.activeRun?.status || status.latestResult?.outcome || status.orchestration?.status || 'ready' ; const pending = requests. find (( r ) => r.status === 'pending' );
61 const attention = pending ? `<div class="attention"><div><strong>${ esc ( pending . title ) }</strong><p>${ esc ( pending . prompt ) }</p></div><button class="primary" id="open-request">Review decision</button></div>` : '' ;
62 const rawState = status.router?.reason || status.latestResult?.nextAction || 'ready' ;
63 return { state, pending, attention, current: `<div class="card"><h3>Current state</h3><p>${ esc ( readable ( rawState )) }</p><p class="muted">Technical state: ${ esc ( rawState ) }</p><div class="actions">${ ! status . activeRun && state !== 'completed' ? '<button class="primary" id="resume">Resume</button>' : ''}${ status . activeRun ? '<button class="danger" id="cancel">Stop migration</button>' : ''}</div></div>` , requests: `<div class="card" style="margin-top:16px"><h3>Requests</h3><p class="muted">${ requests . length } total · ${ requests . filter (( r ) => r . status === 'pending' ). length } pending</p></div>` };
64 }
65 function bindPanels ( pending ) {
66 if (pending) q ( '#open-request' ). onclick = () => requestView (pending); if ( q ( '#resume' )) q ( '#resume' ). onclick = () => start ( true ); if ( q ( '#cancel' )) q ( '#cancel' ).onclick = cancelRun;
67 }
68 function renderDurablePanels ( status , requests ) {
69 const panel = panelMarkup (status, requests); const statusLine = q ( '#status-line' ); if ( ! statusLine) return ;
70 statusLine.innerHTML = `<span class="pill">${ esc ( statusName ( panel . state )) }</span><span>${ status . eventStream ?. lastEventAt ? `Last observed ${ esc ( new Date ( status . eventStream . lastEventAt ). toLocaleString ()) }` : 'No recent observation'}</span>` ;
71 q ( '#attention-area' ).innerHTML = panel.attention; q ( '#current-state-card' ).innerHTML = panel.current; q ( '#requests-card' ).innerHTML = panel.requests; bindPanels (panel.pending);
72 }
73 function render ( status , requests , tasks ) {
74 const rows = events. slice ( - 200 ). reverse (). map (eventMarkup). join ( '' ) || '<p class="muted empty-activity">No observed activity yet.</p>' ;
75 q ( '#content' ).innerHTML = `<h1>${ esc ( selected ) }</h1><div class="status" id="status-line"></div><div id="attention-area"></div><section class="card work-card" id="work-panel">${ workMarkup ( tasks ) }</section><div class="grid"><div class="card activity-card"><h3>Observed activity</h3><div class="activity-feed" aria-label="Observed migration activity">${ rows }</div></div><div><div id="current-state-card"></div><div id="requests-card"></div></div></div>` ;
76 renderDurablePanels (status, requests);
77 }
78 async function refreshTasks () { if ( ! selected) return null ; const tasks = await api ( `/api/tasks?project=${ encodeURIComponent ( selected ) }` ); renderWork (tasks); return tasks; }
79 async function refresh () { if ( ! selected) return null ; const [ status , requests , tasks ] = await Promise . all ([ api ( `/api/status?project=${ encodeURIComponent ( selected ) }` ), api ( `/api/requests?project=${ encodeURIComponent ( selected ) }` ), api ( `/api/tasks?project=${ encodeURIComponent ( selected ) }` )]); render (status, requests, tasks); return { status, requests, tasks }; }
80 let durableRefreshTimer;
81 function scheduleDurableRefresh () { if (durableRefreshTimer || ! selected) return ; durableRefreshTimer = setTimeout ( async () => { durableRefreshTimer = null ; try { const [ status , requests , tasks ] = await Promise . all ([ api ( `/api/status?project=${ encodeURIComponent ( selected ) }` ), api ( `/api/requests?project=${ encodeURIComponent ( selected ) }` ), api ( `/api/tasks?project=${ encodeURIComponent ( selected ) }` )]); renderDurablePanels (status, requests); renderWork (tasks); projects (). catch (() => {}); } catch {} }, 100 ); }
82 function connect () { stream?. close (); if ( ! selected) return ; stream = new EventSource ( `/api/events?project=${ encodeURIComponent ( selected ) }&token=${ encodeURIComponent ( token ) }` ); stream. onopen = () => { q ( '#connection' ).textContent = 'Live' ; scheduleDurableRefresh (); }; stream. onerror = () => q ( '#connection' ).textContent = 'Reconnecting…' ; stream. addEventListener ( 'migration' , ( message ) => { const event = JSON . parse (message.data); if (events. some (( x ) => x.eventId && x.eventId === event.eventId)) return ; events. push (event); appendActivity (event); scheduleDurableRefresh (); }); }
83 async function openProject ( project , { updateUrl = true } = {}) { if (updateUrl) updateRoute ({ project, request: null , artifact: null , cursor: null }); selected = project; events = []; await projects (); const state = await refresh (); connect (); return state; }
84 function cancelRun () { const d = modal ( `<form class="dialog-body" id="cancel-form"><h1>Stop migration?</h1><p>This asks the supervisor to stop gracefully. Recovery artifacts are preserved, and you can resume later.</p><label>Reason<input id="cancel-reason" value="canceled by operator"></label><p class="error" id="cancel-error"></p><div class="footer"><button type="button" data-close>Keep running</button><button class="danger" type="submit">Stop migration</button></div></form>` ); q ( '#cancel-form' ). onsubmit = async ( event ) => { event. preventDefault (); try { await api ( '/api/cancel' , { method: 'POST' , body: JSON . stringify ({ project:selected, reason: q ( '#cancel-reason' ).value }) }); d. close (); await refresh (); } catch (e) { q ( '#cancel-error' ).textContent = e.message; } }; }
85 function modal ( html , { onClose = null } = {}) { const d = q ( '#dialog' ); d.innerHTML = html; if ( ! d.open) d. showModal (); d. querySelector ( '[data-close]' )?. addEventListener ( 'click' , () => d. close ()); d.onclose = onClose; return d; }
86 async function showArtifact ( item , cursor = 0 , opener = null , { updateUrl = true } = {}) { const d = q ( '#file-dialog' ); if (opener) d.returnFocus = opener; if (updateUrl) updateRoute ({ artifact: item, cursor: String (cursor) }); try { const file = await api ( `/api/show?project=${ encodeURIComponent ( selected ) }&item=${ encodeURIComponent ( item ) }&cursor=${ cursor }` ); const language = fileLanguage (file); const parsed = structuredArtifact (file); const content = parsed === null ? codeMarkup (file.content, language) : jsonMarkup (parsed); d.innerHTML = `<div class="dialog-body"><h1>${ esc ( file . path ) }</h1><p class="muted">${ esc ( file . mediaType ) } · showing ${ file . emittedBytes } of ${ file . totalBytes } bytes</p><pre class="file-preview language-${ language }${ parsed === null ? '' : ' structured-json'}" aria-label="${ esc ( file . path ) } preview">${ content }</pre><div class="footer">${ file . nextCursor !== null ? '<button id="more">Show more</button>' : '<span></span>'}<button data-close>Close</button></div></div>` ; if ( ! d.open) d. showModal (); d. querySelector ( '[data-close]' ). onclick = () => d. close (); d. onclose = () => { updateRoute ({ artifact: null , cursor: null }); d.returnFocus?. focus (); d.returnFocus = null ; }; if (file.nextCursor !== null ) d. querySelector ( '#more' ). onclick = () => showArtifact (item, file.nextCursor); } catch (e) { alert (e.message); } }
87 async function loadWixAccount () {
88 const result = q ( '#wix-account-result' ); const acknowledgement = q ( '#ack' );
89 if ( ! result) return ;
90 result.textContent = 'Checking the local Wix CLI account…' ;
91 try {
92 const account = await api ( '/api/account' );
93 if (account.authState === 'authenticated' && account.identity) {
94 result.innerHTML = `<strong>Authenticated Wix CLI identity</strong><code>${ esc ( account . identity ) }</code><span class="muted">Reported directly by <code>wix whoami</code>. Confirm that this account is authorized before continuing.</span>` ;
95 acknowledgement?. removeAttribute ( 'disabled' );
96 } else result.innerHTML = `<strong>Wix CLI identity could not be confirmed</strong><span class="error">${ esc ( account . reason || 'Run wix login, then refresh this check.' ) }</span>` ;
97 } catch (error) { result.innerHTML = `<strong>Wix CLI identity could not be confirmed</strong><span class="error">${ esc ( error . message ) }</span>` ; }
98 }
99 async function requestView ( row , { updateUrl = true } = {}) {
100 const request = await api ( `/api/show?project=${ encodeURIComponent ( selected ) }&item=${ encodeURIComponent ( row . requestId ) }` );
101 if (updateUrl) updateRoute ({ project: selected, request: request.requestId, artifact: null , cursor: null });
102 const artifacts = request.artifactRefs. map (( a ) => `<div class="artifact"><span><strong>${ esc ( a . path ) }</strong><br><span class="muted">${ esc ( a . mediaType ) }</span></span><button data-artifact="${ esc ( a . path ) }">Open</button></div>` ). join ( '' );
103 const operatorInstructions = request.operatorInstructions ? `<section class="operator-instructions"><p><strong>Open Terminal and run this:</strong></p><pre class="file-preview" aria-label="Terminal command">${ esc ( request . operatorInstructions . command ) }</pre><button type="button" id="copy-terminal-command">Copy command</button><p class="muted">${ esc ( request . operatorInstructions . confirmation ) }</p></section>` : '' ;
104 const destinationAccountCheck = request.kind === 'manual_action' && request.stage === 'destination' ; let response = '' ;
105 if (request.response.mode === 'choice' ) response = `<fieldset><legend>Your decision</legend>${ request . choices . map (( c ) => `<label class="choice"><input required type="radio" name="answer" value="${ esc ( c . id ) }">${ esc ( c . label ) }${ c . description ? `<small>${ esc ( c . description ) }</small>` : ''}</label>` ). join ( '' ) }</fieldset><label>Review note <span class="muted">(optional)</span><textarea id="answer-note" maxlength="10000" placeholder="Add context for this decision"></textarea></label>` ;
106 else if (request.response.mode === 'text' ) response = '<label>Response<textarea id="answer" maxlength="10000" required></textarea></label>' ;
107 else response = destinationAccountCheck ? '<label>Confirmation<textarea id="ack-note" maxlength="10000" required placeholder="I confirm the CLI identity is authorized to create this destination."></textarea></label><p class="muted">Your confirmation will be recorded with the required acknowledgement.</p>' : '<label class="choice"><input type="checkbox" id="ack" required>I completed the required manual step.</label>' ;
108 const accountCheck = destinationAccountCheck ? '<section class="account-check" aria-live="polite"><div><p class="account-heading">Current Wix CLI account</p><div id="wix-account-result">Checking the local Wix CLI account…</div></div><button type="button" id="refresh-wix-account">Refresh</button></section>' : '' ;
109 const d = modal ( `<form class="dialog-body" id="answer-form"><div class="pill">${ esc ( request . kind ) }</div><h1>${ esc ( request . title ) }</h1><p>${ esc ( request . prompt ) }</p>${ operatorInstructions }${ accountCheck }${ artifacts }${ response }<p class="error" id="form-error"></p><div class="footer"><button type="button" data-close>Cancel</button><span><button type="submit" name="mode" value="save">Save answer only</button> <button class="primary" type="submit" name="mode" value="continue">Submit and resume</button></span></div></form>` , { onClose : () => updateRoute ({ request: null , artifact: null , cursor: null }) });
110 d. querySelectorAll ( '[data-artifact]' ). forEach (( b ) => b. onclick = ( event ) => showArtifact (b.dataset.artifact, 0 , event.currentTarget));
111 d. querySelector ( '#copy-terminal-command' )?. addEventListener ( 'click' , async () => { try { await navigator.clipboard. writeText (request.operatorInstructions.command); d. querySelector ( '#copy-terminal-command' ).textContent = 'Copied' ; } catch { d. querySelector ( '#copy-terminal-command' ).textContent = 'Copy unavailable' ; } });
112 if (destinationAccountCheck) { q ( '#refresh-wix-account' ).onclick = loadWixAccount; loadWixAccount (); }
113 q ( '#answer-form' ). onsubmit = async ( event ) => { event. preventDefault (); const submit = event.submitter.value; const payload = { project:selected, requestId:request.requestId, continue:submit === 'continue' }; if (request.response.mode === 'choice' ) { payload.choice = new FormData (event.currentTarget). get ( 'answer' ); const note = q ( '#answer-note' ).value. trim (); if (note) payload.note = note; } else if (request.response.mode === 'text' ) payload.text = q ( '#answer' ).value; else if (destinationAccountCheck) { payload.acknowledged = true ; payload.note = q ( '#ack-note' ).value. trim (); } else payload.acknowledged = q ( '#ack' ).checked; try { await api ( '/api/answer' , { method: 'POST' , body: JSON . stringify (payload) }); d. close (); await refresh (); } catch (e) { q ( '#form-error' ).textContent = e.message; } };
114 }
115 function start ( resume = false ) { if (resume) return api ( '/api/run' , {method: 'POST' ,body: JSON . stringify ({project:selected,resume: true })}). then (refresh). catch (( e ) => alert (e.message)); const d = modal ( `<form class="dialog-body" id="new-form"><h1>New migration</h1><div class="form-grid"><label>Project name<input name="project" required pattern="[A-Za-z0-9][A-Za-z0-9_-]{0,99}"></label><label>Source URL<input name="sourceUrl" type="url"></label><label>Source mode<select name="sourceMode"><option value="public_content">Public content</option><option value="authenticated_api">Authenticated API</option><option value="private_data">Private data</option><option value="files_only">Files only</option></select></label><label>Delivery mode<select name="deliveryMode"><option value="management">Management</option><option value="website">Website</option><option value="management_and_website">Management + website</option></select></label><label>Destination<input name="destination" placeholder="new or existing:site-id" value="new"></label><label>Agent<select name="agent"><option value="auto">Automatic</option><option value="codex">Codex</option><option value="claude">Claude</option></select></label><label>Import mode<select name="managementImportMode"><option value="standard">Standard</option><option value="quick">Quick</option></select></label></div><label>Files (optional)<input id="files" type="file" multiple></label><p class="error" id="new-error"></p><div class="footer"><button type="button" data-close>Cancel</button><button class="primary">Check and start migration</button></div></form>` ); q ( '#new-form' ). onsubmit = async ( e ) => { e. preventDefault (); const data = Object. fromEntries ( new FormData (e.currentTarget)); try { const doctor = await api ( `/api/doctor?agent=${ encodeURIComponent ( data . agent ) }` ); if ( ! doctor.adapters. some (( adapter ) => adapter.ok)) throw new Error ( 'No selected agent is ready. Complete the local authentication/prerequisite shown by replatform doctor, then retry.' ); const files = []; for ( const file of q ( '#files' ).files) { const base64 = await new Promise (( resolve , reject ) => { const r =new FileReader ();r. onload = () => resolve ( String (r.result). split ( ',' )[ 1 ]);r.onerror = reject;r. readAsDataURL (file);}); const result =await api ( '/api/upload' ,{method: 'POST' ,body: JSON . stringify ({project:data.project,name:file.name,contentBase64:base64})});files. push (result.name); } await api ( '/api/run' ,{method: 'POST' ,body: JSON . stringify ({project:data.project,options:{ ... data,files}})}); d. close (); await openProject (data.project); } catch (err) { q ( '#new-error' ).textContent = err.message; } }; }
116 async function restoreRoute () { const route = routeState (); if ( ! route.project) return ; const state = await openProject (route.project, { updateUrl: false }); if ( ! route.request) return ; const row = state?.requests. find (( item ) => item.requestId === route.request && item.status === 'pending' ); if ( ! row) { updateRoute ({ request: null , artifact: null , cursor: null }); return ; } await requestView (row, { updateUrl: false }); if (route.artifact) await showArtifact (route.artifact, Number (route.cursor) || 0 , null , { updateUrl: false }); }
117 q ( '#new-project' ). onclick = () => start ( false ); projects (). then (restoreRoute). catch (( error ) => { q ( '#content' ).textContent = error.message; });