Add copy and download actions to program source panels
This commit is contained in:
parent
489918fbf8
commit
0d44c8a709
3 changed files with 56 additions and 1 deletions
|
|
@ -264,6 +264,10 @@ async function loadInlineSource(program) {
|
||||||
const heading = document.querySelector('#program-source-title');
|
const heading = document.querySelector('#program-source-title');
|
||||||
const message = document.querySelector('#program-source-status');
|
const message = document.querySelector('#program-source-status');
|
||||||
const listing = document.querySelector('#program-source-code');
|
const listing = document.querySelector('#program-source-code');
|
||||||
|
const copyButton = document.querySelector('#copy-program-source');
|
||||||
|
const downloadLink = document.querySelector('#download-program-source');
|
||||||
|
copyButton.disabled = true;
|
||||||
|
downloadLink.hidden = true;
|
||||||
heading.textContent = program.language === 'machine-code' ? 'Assembly source' : 'BASIC V2 source';
|
heading.textContent = program.language === 'machine-code' ? 'Assembly source' : 'BASIC V2 source';
|
||||||
message.hidden = false;
|
message.hidden = false;
|
||||||
message.className = '';
|
message.className = '';
|
||||||
|
|
@ -276,12 +280,49 @@ async function loadInlineSource(program) {
|
||||||
highlightSource(listing.querySelector('code'), program, await response.text());
|
highlightSource(listing.querySelector('code'), program, await response.text());
|
||||||
message.hidden = true;
|
message.hidden = true;
|
||||||
listing.hidden = false;
|
listing.hidden = false;
|
||||||
|
copyButton.disabled = false;
|
||||||
|
downloadLink.href = `/api/source?path=${encodeURIComponent(program.path)}`;
|
||||||
|
downloadLink.download = program.source.split('/').at(-1);
|
||||||
|
downloadLink.hidden = false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.className = 'error';
|
message.className = 'error';
|
||||||
message.textContent = error.message;
|
message.textContent = error.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sourceCopyTimer;
|
||||||
|
document.querySelector('#copy-program-source').addEventListener('click', async event => {
|
||||||
|
const button = event.currentTarget;
|
||||||
|
const feedback = document.querySelector('#source-copy-feedback');
|
||||||
|
const text = document.querySelector('#program-source-code code').textContent;
|
||||||
|
clearTimeout(sourceCopyTimer);
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable');
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} catch {
|
||||||
|
// Support plain HTTP deployments as well as HTTPS and localhost.
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.cssText = 'position:fixed;left:-9999px;top:0';
|
||||||
|
document.body.append(textarea);
|
||||||
|
try {
|
||||||
|
textarea.select();
|
||||||
|
if (!document.execCommand('copy')) throw new Error('Copy failed');
|
||||||
|
} finally {
|
||||||
|
textarea.remove();
|
||||||
|
button.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button.textContent = 'Copied';
|
||||||
|
feedback.textContent = 'Source code copied to clipboard.';
|
||||||
|
} catch {
|
||||||
|
button.textContent = 'Try again';
|
||||||
|
feedback.textContent = 'Could not copy the source. Select the code or use Download instead.';
|
||||||
|
}
|
||||||
|
sourceCopyTimer = setTimeout(() => { button.textContent = 'Copy'; }, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
function startEmulator(program) {
|
function startEmulator(program) {
|
||||||
library.hidden = true;
|
library.hidden = true;
|
||||||
player.hidden = false;
|
player.hidden = false;
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,14 @@
|
||||||
<div id="program-controls" class="control-list"></div>
|
<div id="program-controls" class="control-list"></div>
|
||||||
</section>
|
</section>
|
||||||
<section class="program-source-panel" aria-labelledby="program-source-title">
|
<section class="program-source-panel" aria-labelledby="program-source-title">
|
||||||
<h2 id="program-source-title">Source code</h2>
|
<div class="source-panel-heading">
|
||||||
|
<h2 id="program-source-title">Source code</h2>
|
||||||
|
<div class="source-panel-actions">
|
||||||
|
<button id="copy-program-source" class="source-action" type="button" disabled>Copy</button>
|
||||||
|
<a id="download-program-source" class="source-action" hidden>Download</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span id="source-copy-feedback" class="sr-only" role="status"></span>
|
||||||
<p id="program-source-status" role="status">Loading source…</p>
|
<p id="program-source-status" role="status">Loading source…</p>
|
||||||
<pre id="program-source-code" tabindex="0" aria-label="Program source code" hidden><code></code></pre>
|
<pre id="program-source-code" tabindex="0" aria-label="Program source code" hidden><code></code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,13 @@ button:disabled { opacity: .5; cursor: wait; }
|
||||||
.instructions-panel h2, .program-source-panel h2 { margin: 0 0 .85rem; font-size: 1rem; letter-spacing: .08em; text-transform: uppercase; }
|
.instructions-panel h2, .program-source-panel h2 { margin: 0 0 .85rem; font-size: 1rem; letter-spacing: .08em; text-transform: uppercase; }
|
||||||
.program-source-panel pre { max-height: 600px; margin: 0; padding: 1rem; overflow: auto; border-radius: 6px; background: #0d0928; color: #d8d3f4; font: .85rem/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre; tab-size: 4; }
|
.program-source-panel pre { max-height: 600px; margin: 0; padding: 1rem; overflow: auto; border-radius: 6px; background: #0d0928; color: #d8d3f4; font: .85rem/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre; tab-size: 4; }
|
||||||
.program-source-panel pre:focus-visible { outline: 2px solid var(--c64-light-blue); outline-offset: 2px; }
|
.program-source-panel pre:focus-visible { outline: 2px solid var(--c64-light-blue); outline-offset: 2px; }
|
||||||
|
.source-panel-heading { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: .85rem; }
|
||||||
|
.source-panel-heading h2 { margin: 0; }
|
||||||
|
.source-panel-actions { display: flex; gap: .5rem; }
|
||||||
|
.source-action { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: .4rem .7rem; border: 1px solid #b8b1e755; border-radius: 6px; background: var(--c64-blue); color: #fff; font: inherit; font-size: .8rem; line-height: 1.25; text-decoration: none; cursor: pointer; }
|
||||||
|
.source-action:hover { background: var(--c64-light-blue); }
|
||||||
|
.source-action:focus-visible { outline: 2px solid var(--c64-lightest-blue); outline-offset: 2px; }
|
||||||
|
.source-action[hidden] { display: none; }
|
||||||
.program-source-panel code, .source-modal code { font: inherit; }
|
.program-source-panel code, .source-modal code { font: inherit; }
|
||||||
.token.comment { color: #aaa5b8; font-style: italic; }
|
.token.comment { color: #aaa5b8; font-style: italic; }
|
||||||
.token.keyword { color: #c6b4ff; }
|
.token.keyword { color: #c6b4ff; }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue