Spaces:
Running
Running
Commit ·
43d70fc
1
Parent(s): 2a7be55
added share link.
Browse files- app.py +69 -0
- web/lyl-editor.css +24 -0
app.py
CHANGED
|
@@ -450,6 +450,66 @@ function (value) {
|
|
| 450 |
}
|
| 451 |
'''
|
| 452 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
# Render the editor text to SVG. The text is passed in by Gradio as the event's
|
| 454 |
# input value (the hidden #ls-editor-state textbox, kept in sync with the embedded
|
| 455 |
# CodeMirror editor) — taking the value as an argument gives the full text directly,
|
|
@@ -687,6 +747,12 @@ def build_ui ():
|
|
| 687 |
with gr.Column(scale=5, elem_id='editor-col'):
|
| 688 |
with gr.Group():
|
| 689 |
gr.Markdown('## Lilylet editor')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
# Our own CodeMirror 6 editor (lyl-editor.bundle.js) mounts into
|
| 691 |
# this div and bridges to the hidden textbox below — Gradio's
|
| 692 |
# gr.Code can't be syntax-highlighted (its CM is sealed), so we
|
|
@@ -744,6 +810,9 @@ def build_ui ():
|
|
| 744 |
file_list.select(load_file, inputs=[file_list, store], outputs=[editor])
|
| 745 |
# separate js-only listener: mirror the selected file into location.hash for deep-linking
|
| 746 |
file_list.select(None, inputs=[file_list], outputs=None, js=_JS_SELECT_HASH)
|
|
|
|
|
|
|
|
|
|
| 747 |
|
| 748 |
return demo
|
| 749 |
|
|
|
|
| 450 |
}
|
| 451 |
'''
|
| 452 |
|
| 453 |
+
# Share button: copy the current deep-link URL to the clipboard and flash a hint.
|
| 454 |
+
#
|
| 455 |
+
# iframe notes (this app is embedded in a cross-origin iframe on HF Spaces):
|
| 456 |
+
# - location.href ALWAYS reads THIS frame's own document URL — the Gradio app's
|
| 457 |
+
# direct URL incl. the #score=<file> hash — even inside a cross-origin iframe.
|
| 458 |
+
# The same-origin policy only blocks reading window.parent/window.top.location,
|
| 459 |
+
# not your own frame's. So this is exactly the shareable deep-link; the outer
|
| 460 |
+
# huggingface.co/spaces URL is both unreadable AND wrong (it has no hash).
|
| 461 |
+
# - navigator.clipboard.writeText may be blocked by Permissions-Policy in a
|
| 462 |
+
# cross-origin iframe (needs allow="clipboard-write", which the HF embed may not
|
| 463 |
+
# grant). So we try the async Clipboard API first and fall back to a hidden
|
| 464 |
+
# <textarea> + document.execCommand('copy'), which works from a user gesture.
|
| 465 |
+
_JS_SHARE = '''
|
| 466 |
+
function () {
|
| 467 |
+
const url = location.href;
|
| 468 |
+
const flash = (msg, ok) => {
|
| 469 |
+
const btn = document.getElementById('ls-share-btn');
|
| 470 |
+
if (!btn) return;
|
| 471 |
+
let hint = document.getElementById('ls-share-hint');
|
| 472 |
+
if (!hint) {
|
| 473 |
+
hint = document.createElement('span');
|
| 474 |
+
hint.id = 'ls-share-hint';
|
| 475 |
+
hint.className = 'ls-share-hint';
|
| 476 |
+
btn.insertAdjacentElement('afterend', hint);
|
| 477 |
+
}
|
| 478 |
+
hint.textContent = msg;
|
| 479 |
+
hint.classList.toggle('ls-share-err', !ok);
|
| 480 |
+
hint.classList.add('show');
|
| 481 |
+
clearTimeout(hint._t);
|
| 482 |
+
hint._t = setTimeout(() => hint.classList.remove('show'), 2200);
|
| 483 |
+
};
|
| 484 |
+
// No score selected yet -> no #score= hash; the bare URL isn't a useful share link.
|
| 485 |
+
if (!/(?:^#|&|#)score=/.test(location.hash || '')) {
|
| 486 |
+
flash('Select a score first', false);
|
| 487 |
+
return [];
|
| 488 |
+
}
|
| 489 |
+
const fallback = () => {
|
| 490 |
+
try {
|
| 491 |
+
const ta = document.createElement('textarea');
|
| 492 |
+
ta.value = url; ta.style.position = 'fixed'; ta.style.opacity = '0';
|
| 493 |
+
document.body.appendChild(ta); ta.focus(); ta.select();
|
| 494 |
+
const ok = document.execCommand('copy');
|
| 495 |
+
document.body.removeChild(ta);
|
| 496 |
+
flash(ok ? 'Link copied!' : 'Copy failed — select & copy manually', ok);
|
| 497 |
+
} catch (e) {
|
| 498 |
+
flash('Copy failed — select & copy manually', false);
|
| 499 |
+
}
|
| 500 |
+
};
|
| 501 |
+
try {
|
| 502 |
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
| 503 |
+
navigator.clipboard.writeText(url).then(
|
| 504 |
+
() => flash('Link copied!', true),
|
| 505 |
+
() => fallback()
|
| 506 |
+
);
|
| 507 |
+
} else { fallback(); }
|
| 508 |
+
} catch (e) { fallback(); }
|
| 509 |
+
return [];
|
| 510 |
+
}
|
| 511 |
+
'''
|
| 512 |
+
|
| 513 |
# Render the editor text to SVG. The text is passed in by Gradio as the event's
|
| 514 |
# input value (the hidden #ls-editor-state textbox, kept in sync with the embedded
|
| 515 |
# CodeMirror editor) — taking the value as an argument gives the full text directly,
|
|
|
|
| 747 |
with gr.Column(scale=5, elem_id='editor-col'):
|
| 748 |
with gr.Group():
|
| 749 |
gr.Markdown('## Lilylet editor')
|
| 750 |
+
# Share button: copies the current deep-link URL (#score=<file>)
|
| 751 |
+
# to the clipboard. js-only handler (_JS_SHARE) — see its comment
|
| 752 |
+
# for the iframe URL/clipboard caveats. A transient hint span next
|
| 753 |
+
# to it shows the copy result.
|
| 754 |
+
share_btn = gr.Button('🔗 Share link', elem_id='ls-share-btn',
|
| 755 |
+
size='sm', scale=0, min_width=110)
|
| 756 |
# Our own CodeMirror 6 editor (lyl-editor.bundle.js) mounts into
|
| 757 |
# this div and bridges to the hidden textbox below — Gradio's
|
| 758 |
# gr.Code can't be syntax-highlighted (its CM is sealed), so we
|
|
|
|
| 810 |
file_list.select(load_file, inputs=[file_list, store], outputs=[editor])
|
| 811 |
# separate js-only listener: mirror the selected file into location.hash for deep-linking
|
| 812 |
file_list.select(None, inputs=[file_list], outputs=None, js=_JS_SELECT_HASH)
|
| 813 |
+
# Share: copy the current deep-link URL (the #score hash kept current by the
|
| 814 |
+
# select listener above) to the clipboard. js-only — see _JS_SHARE.
|
| 815 |
+
share_btn.click(None, inputs=None, outputs=None, js=_JS_SHARE)
|
| 816 |
|
| 817 |
return demo
|
| 818 |
|
web/lyl-editor.css
CHANGED
|
@@ -30,3 +30,27 @@
|
|
| 30 |
#ls-editor-mount .cm-editor.cm-focused {
|
| 31 |
outline: none;
|
| 32 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
#ls-editor-mount .cm-editor.cm-focused {
|
| 31 |
outline: none;
|
| 32 |
}
|
| 33 |
+
|
| 34 |
+
/* Share button: a compact, unobtrusive button under the "Lilylet editor" title. */
|
| 35 |
+
#ls-share-btn {
|
| 36 |
+
flex: 0 0 auto;
|
| 37 |
+
width: auto;
|
| 38 |
+
min-width: 0;
|
| 39 |
+
align-self: flex-start;
|
| 40 |
+
margin: 0 0 6px 2px;
|
| 41 |
+
font-size: 12px;
|
| 42 |
+
padding: 3px 10px;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* Transient "Link copied!" hint shown beside the share button. Fades in on .show. */
|
| 46 |
+
.ls-share-hint {
|
| 47 |
+
display: inline-block;
|
| 48 |
+
margin-left: 8px;
|
| 49 |
+
font-size: 12px;
|
| 50 |
+
color: #2b7;
|
| 51 |
+
opacity: 0;
|
| 52 |
+
transition: opacity 0.2s ease;
|
| 53 |
+
vertical-align: middle;
|
| 54 |
+
}
|
| 55 |
+
.ls-share-hint.show { opacity: 1; }
|
| 56 |
+
.ls-share-hint.ls-share-err { color: #c0392b; }
|