Spaces:
Running
Running
Commit ·
e5a3bec
1
Parent(s): 43d70fc
added live-editor link.
Browse files- app.py +70 -7
- web/lyl-editor.css +15 -5
- web/vendor/js-base64.js +3 -0
- web/vendor/pako_deflate.min.js +3 -0
app.py
CHANGED
|
@@ -350,6 +350,11 @@ def build_head ():
|
|
| 350 |
# the mount/bridge script that wires it to the hidden #ls-editor-state textbox.
|
| 351 |
os.path.join(vendor, 'lyl-editor.bundle.js'),
|
| 352 |
os.path.join(WEB_DIR, 'lyl-editor-mount.js'),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
# computes --ls-fill-h so the bottom Score List | editor row fills the height
|
| 354 |
# left under Compose + Logs (robust against Gradio's flex-nesting quirks).
|
| 355 |
os.path.join(WEB_DIR, 'layout-fit.js'),
|
|
@@ -510,13 +515,62 @@ function () {
|
|
| 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,
|
| 516 |
-
# no DOM scraping needed.
|
|
|
|
| 517 |
_JS_RENDER = '''
|
| 518 |
function (text) {
|
| 519 |
if (window.LilyScore) window.LilyScore.render(text || '');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 520 |
return [];
|
| 521 |
}
|
| 522 |
'''
|
|
@@ -747,12 +801,19 @@ def build_ui ():
|
|
| 747 |
with gr.Column(scale=5, elem_id='editor-col'):
|
| 748 |
with gr.Group():
|
| 749 |
gr.Markdown('## Lilylet editor')
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
@@ -813,6 +874,8 @@ def build_ui ():
|
|
| 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 |
|
|
|
|
| 350 |
# the mount/bridge script that wires it to the hidden #ls-editor-state textbox.
|
| 351 |
os.path.join(vendor, 'lyl-editor.bundle.js'),
|
| 352 |
os.path.join(WEB_DIR, 'lyl-editor-mount.js'),
|
| 353 |
+
# pako (deflate) + js-base64: used by the "Open in live-editor" share link to
|
| 354 |
+
# compress+url-safe-base64 the editor text into a ?code= param, matching
|
| 355 |
+
# lilylet-live-editor's share.ts encoding exactly. Expose window.pako/Base64.
|
| 356 |
+
os.path.join(vendor, 'pako_deflate.min.js'),
|
| 357 |
+
os.path.join(vendor, 'js-base64.js'),
|
| 358 |
# computes --ls-fill-h so the bottom Score List | editor row fills the height
|
| 359 |
# left under Compose + Logs (robust against Gradio's flex-nesting quirks).
|
| 360 |
os.path.join(WEB_DIR, 'layout-fit.js'),
|
|
|
|
| 515 |
}
|
| 516 |
'''
|
| 517 |
|
| 518 |
+
# Open the current editor text in lilylet-live-editor. We compress the text exactly
|
| 519 |
+
# as the editor's share.ts does — JSON.stringify({code}) -> pako.deflate(level 9) ->
|
| 520 |
+
# URL-safe base64 — and put it in ?code=<...>, then open the deployed editor in a new
|
| 521 |
+
# tab. window.pako / window.Base64 come from the vendored UMD bundles (build_head).
|
| 522 |
+
# Reads the text from the hidden #ls-editor-state <textarea> (the canonical editor
|
| 523 |
+
# value). No-op with a hint if empty or the libs failed to load.
|
| 524 |
+
_LIVE_EDITOR_URL = 'https://k-l-lambda.github.io/lilylet-live-editor/'
|
| 525 |
+
_JS_LIVE_EDITOR = '''
|
| 526 |
+
function () {
|
| 527 |
+
const flash = (msg, ok) => {
|
| 528 |
+
const btn = document.getElementById('ls-live-btn');
|
| 529 |
+
if (!btn) return;
|
| 530 |
+
let hint = document.getElementById('ls-share-hint');
|
| 531 |
+
if (!hint) {
|
| 532 |
+
hint = document.createElement('span');
|
| 533 |
+
hint.id = 'ls-share-hint';
|
| 534 |
+
hint.className = 'ls-share-hint';
|
| 535 |
+
const sb = document.getElementById('ls-share-btn');
|
| 536 |
+
(sb || btn).insertAdjacentElement('afterend', hint);
|
| 537 |
+
}
|
| 538 |
+
hint.textContent = msg;
|
| 539 |
+
hint.classList.toggle('ls-share-err', !ok);
|
| 540 |
+
hint.classList.add('show');
|
| 541 |
+
clearTimeout(hint._t);
|
| 542 |
+
hint._t = setTimeout(() => hint.classList.remove('show'), 2200);
|
| 543 |
+
};
|
| 544 |
+
try {
|
| 545 |
+
const ta = document.querySelector('#ls-editor-state textarea');
|
| 546 |
+
const code = ta && ta.value;
|
| 547 |
+
if (!code || !code.trim()) { flash('Editor is empty', false); return []; }
|
| 548 |
+
if (!window.pako || !window.Base64) { flash('Encoder not loaded', false); return []; }
|
| 549 |
+
const json = JSON.stringify({ code: code });
|
| 550 |
+
const compressed = window.pako.deflate(json, { level: 9 });
|
| 551 |
+
const encoded = window.Base64.fromUint8Array(compressed, true); // URL-safe
|
| 552 |
+
const url = '%s' + '?code=' + encoded;
|
| 553 |
+
window.open(url, '_blank', 'noopener');
|
| 554 |
+
} catch (e) {
|
| 555 |
+
flash('Could not build link', false);
|
| 556 |
+
}
|
| 557 |
+
return [];
|
| 558 |
+
}
|
| 559 |
+
''' % _LIVE_EDITOR_URL
|
| 560 |
+
|
| 561 |
+
|
| 562 |
# Render the editor text to SVG. The text is passed in by Gradio as the event's
|
| 563 |
# input value (the hidden #ls-editor-state textbox, kept in sync with the embedded
|
| 564 |
# CodeMirror editor) — taking the value as an argument gives the full text directly,
|
| 565 |
+
# no DOM scraping needed. Also toggles the "Open in live-editor" button: shown only
|
| 566 |
+
# when the editor has non-empty text (the link needs content to encode).
|
| 567 |
_JS_RENDER = '''
|
| 568 |
function (text) {
|
| 569 |
if (window.LilyScore) window.LilyScore.render(text || '');
|
| 570 |
+
try {
|
| 571 |
+
const live = document.getElementById('ls-live-btn');
|
| 572 |
+
if (live) live.classList.toggle('ls-hidden', !(text && text.trim()));
|
| 573 |
+
} catch (e) {}
|
| 574 |
return [];
|
| 575 |
}
|
| 576 |
'''
|
|
|
|
| 801 |
with gr.Column(scale=5, elem_id='editor-col'):
|
| 802 |
with gr.Group():
|
| 803 |
gr.Markdown('## Lilylet editor')
|
| 804 |
+
with gr.Row(elem_id='ls-editor-actions'):
|
| 805 |
+
# Share button: copies the current deep-link URL (#score=<file>)
|
| 806 |
+
# to the clipboard. js-only handler (_JS_SHARE) — see its comment
|
| 807 |
+
# for the iframe URL/clipboard caveats. A transient hint span next
|
| 808 |
+
# to it shows the copy result.
|
| 809 |
+
share_btn = gr.Button('🔗 Share link', elem_id='ls-share-btn',
|
| 810 |
+
size='sm', scale=0, min_width=110)
|
| 811 |
+
# Open the current editor text in lilylet-live-editor: builds a
|
| 812 |
+
# ?code=<pako+base64> URL (matching the editor's share.ts) and
|
| 813 |
+
# opens it in a new tab. Hidden via CSS until the editor has text
|
| 814 |
+
# (toggled by _JS_RENDER, which receives the full text each change).
|
| 815 |
+
live_btn = gr.Button('🎹 Open in live-editor', elem_id='ls-live-btn',
|
| 816 |
+
size='sm', scale=0, min_width=150, elem_classes=['ls-hidden'])
|
| 817 |
# Our own CodeMirror 6 editor (lyl-editor.bundle.js) mounts into
|
| 818 |
# this div and bridges to the hidden textbox below — Gradio's
|
| 819 |
# gr.Code can't be syntax-highlighted (its CM is sealed), so we
|
|
|
|
| 874 |
# Share: copy the current deep-link URL (the #score hash kept current by the
|
| 875 |
# select listener above) to the clipboard. js-only — see _JS_SHARE.
|
| 876 |
share_btn.click(None, inputs=None, outputs=None, js=_JS_SHARE)
|
| 877 |
+
# Open in live-editor: encode the editor text into a ?code= URL and open it.
|
| 878 |
+
live_btn.click(None, inputs=None, outputs=None, js=_JS_LIVE_EDITOR)
|
| 879 |
|
| 880 |
return demo
|
| 881 |
|
web/lyl-editor.css
CHANGED
|
@@ -31,18 +31,28 @@
|
|
| 31 |
outline: none;
|
| 32 |
}
|
| 33 |
|
| 34 |
-
/*
|
| 35 |
-
#ls-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
/*
|
|
|
|
|
|
|
|
|
|
| 46 |
.ls-share-hint {
|
| 47 |
display: inline-block;
|
| 48 |
margin-left: 8px;
|
|
|
|
| 31 |
outline: none;
|
| 32 |
}
|
| 33 |
|
| 34 |
+
/* Editor action buttons row (Share link + Open in live-editor) under the title. */
|
| 35 |
+
#ls-editor-actions {
|
| 36 |
+
flex: 0 0 auto;
|
| 37 |
+
gap: 6px;
|
| 38 |
+
align-items: center;
|
| 39 |
+
margin: 0 0 6px 2px;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/* Share / live-editor buttons: compact and unobtrusive. */
|
| 43 |
+
#ls-share-btn,
|
| 44 |
+
#ls-live-btn {
|
| 45 |
flex: 0 0 auto;
|
| 46 |
width: auto;
|
| 47 |
min-width: 0;
|
|
|
|
|
|
|
| 48 |
font-size: 12px;
|
| 49 |
padding: 3px 10px;
|
| 50 |
}
|
| 51 |
|
| 52 |
+
/* "Open in live-editor" is hidden until the editor has content (toggled in JS). */
|
| 53 |
+
#ls-live-btn.ls-hidden { display: none !important; }
|
| 54 |
+
|
| 55 |
+
/* Transient "Link copied!" hint shown beside the buttons. Fades in on .show. */
|
| 56 |
.ls-share-hint {
|
| 57 |
display: inline-block;
|
| 58 |
margin-left: 8px;
|
web/vendor/js-base64.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cc4803fc4f373cbbeecac84bad8ccfefe791062191a6675141f7df870652989f
|
| 3 |
+
size 13006
|
web/vendor/pako_deflate.min.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9429785d1e7f805d46640c4155957fd0f286144dec05cea09f6f2eaa646adb21
|
| 3 |
+
size 27876
|