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 message = document.querySelector('#program-source-status');
|
||||
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';
|
||||
message.hidden = false;
|
||||
message.className = '';
|
||||
|
|
@ -276,12 +280,49 @@ async function loadInlineSource(program) {
|
|||
highlightSource(listing.querySelector('code'), program, await response.text());
|
||||
message.hidden = true;
|
||||
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) {
|
||||
message.className = 'error';
|
||||
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) {
|
||||
library.hidden = true;
|
||||
player.hidden = false;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,14 @@
|
|||
<div id="program-controls" class="control-list"></div>
|
||||
</section>
|
||||
<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>
|
||||
<pre id="program-source-code" tabindex="0" aria-label="Program source code" hidden><code></code></pre>
|
||||
</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; }
|
||||
.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; }
|
||||
.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; }
|
||||
.token.comment { color: #aaa5b8; font-style: italic; }
|
||||
.token.keyword { color: #c6b4ff; }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue