diff --git a/src/css/button.css b/src/css/button.css index 8cfbfb7..ecfc698 100644 --- a/src/css/button.css +++ b/src/css/button.css @@ -19,6 +19,7 @@ } .btn-primary { + position: relative; display: flex; align-items: center; justify-content: center; @@ -104,3 +105,42 @@ .btn-primary > .btn-icon { filter: var(--btn-primary-icon-filter); } + +.btn-primary:hover > .compile-tooltip { + visibility: visible; +} + +.btn-primary:hover > .compile-arrow { + visibility: visible; +} + +.compile-tooltip { + position: absolute; + top: -41px; + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + background: var(--steel-900); + border-radius: 2px; + padding: 6px 8px 6px 8px; + visibility: hidden; +} + +.compile-tooltip > span { + color: var(--steel-000); + font-size: 12px; + text-align: center; +} + +.compile-arrow { + visibility: hidden; + position: absolute; + top: -35px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid var(--steel-900); +} diff --git a/src/css/main.css b/src/css/main.css index 6838e60..1d73864 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -67,8 +67,6 @@ --clipboard_icon_filter_light: var(--media-prefers-light) none; --btn_primary_fg_light: var(--media-prefers-light) var(--blue-600); --btn_primary_icon_filter_light: var(--media-prefers-light) none; - --sketch_mobile_bg_light: var(--media-prefers-light) var(--blue-050); - --sketch_mobile_fg_filter_light: var(--media-prefers-light) var(--blue-600-filter); --clickable_icon_filter_light: var(--media-prefers-light) none; --modal_close_icon_filter_light: var(--media-prefers-light) none; --theme_scheme: var(--media-prefers-light) light; @@ -119,12 +117,6 @@ invert(67%) sepia(12%) saturate(1615%) hue-rotate(198deg) brightness(103%) contrast(101%) ); - --sketch-mobile-bg: var(--sketch_mobile_bg_light, initial); - --sketch-mobile-fg-filter: var( - --sketch_mobile_fg_filter_light, - invert(67%) sepia(15%) saturate(1391%) hue-rotate(198deg) brightness(103%) - contrast(96%) - ); --clickable-icon-filter: var( --clickable_icon_filter_light, invert(71%) sepia(13%) saturate(274%) hue-rotate(191deg) brightness(89%) contrast(87%) diff --git a/src/css/responsive.css b/src/css/responsive.css index 5539525..43af563 100644 --- a/src/css/responsive.css +++ b/src/css/responsive.css @@ -23,22 +23,6 @@ #sketch { display: none; } - #sketch-mobile { - display: flex !important; - flex-direction: column; - align-items: center; - padding: 0.4rem; - border-radius: 4px; - } - #sketch-mobile-icon { - margin-top: 2px; - } - .sketch-mobile-activated { - background: var(--sketch-mobile-bg); - #sketch-mobile-icon { - filter: var(--sketch-mobile-fg-filter); - } - } #download-cli { display: none; } diff --git a/src/css/workstation.css b/src/css/workstation.css index 8a7a46a..55879bc 100644 --- a/src/css/workstation.css +++ b/src/css/workstation.css @@ -140,11 +140,6 @@ color: var(--steel-800); } -#sketch-mobile-icon { - filter: invert(46%) sepia(11%) saturate(523%) hue-rotate(192deg) brightness(94%) - contrast(88%); -} - #key { width: 20px; } diff --git a/src/index.html b/src/index.html index 667b89b..d2f34af 100644 --- a/src/index.html +++ b/src/index.html @@ -201,8 +201,12 @@ @@ -230,34 +234,16 @@ -
- Sketch -
- - +
+ +
-
diff --git a/src/js/modules/editor.js b/src/js/modules/editor.js index 231af8a..14c0b68 100644 --- a/src/js/modules/editor.js +++ b/src/js/modules/editor.js @@ -8,6 +8,7 @@ import WebTheme from "./web_theme.js"; import Theme from "./theme.js"; import Layout from "./layout.js"; import Sketch from "./sketch.js"; +import Export from "./export.js"; import Zoom from "./zoom.js"; import Alert from "./alert.js"; @@ -166,6 +167,11 @@ function initTextArea() { async function attachListeners() { document.getElementById("compile-btn").addEventListener("click", compile); + + // Set up tooltip text based on OS + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + const shortcutText = isMac ? "Cmd+S" : "Ctrl+S"; + document.querySelector(".compile-tooltip-text").textContent = shortcutText; } function displayCompileErrors(errs) { @@ -278,6 +284,7 @@ async function compile() { QueryParams.set("script", encoded); const sketch = Sketch.getValue() === "1" ? true : false; + const ascii = Sketch.getASCII(); const layout = Layout.getLayout(); let svg; @@ -294,13 +301,17 @@ async function compile() { } let response; try { - response = await fetch( - `https://api.d2lang.com/render/svg?script=${encoded}&layout=${layout}&theme=${Theme.getThemeID()}&sketch=${Sketch.getValue()}`, - { - headers, - method: "GET", - } - ); + let apiUrl = `https://api.d2lang.com/render/svg?script=${encoded}&layout=${layout}&theme=${Theme.getThemeID()}`; + if (ascii) { + apiUrl += `&ascii=1`; + } + if (sketch) { + apiUrl += `&sketch=1`; + } + response = await fetch(apiUrl, { + headers, + method: "GET", + }); } catch (e) { // 4-500s do not throw Alert.show( @@ -340,7 +351,8 @@ async function compile() { fs: { index: script }, options: { layout, - sketch, + sketch: ascii ? false : sketch, + ascii, forceAppendix: false, target: "", animateInterval: 0, @@ -377,7 +389,8 @@ async function compile() { } const renderOptions = { layout: layout, - sketch, + sketch: ascii ? false : sketch, + ascii, themeID: Theme.getThemeID(), center: true, }; @@ -399,17 +412,23 @@ async function compile() { const containerHeight = renderEl.getBoundingClientRect().height; diagramSVG = svg; - renderEl.innerHTML = svg; - // skip over the xml version tag - const svgEl = renderEl.lastChild; + if (ascii) { + renderEl.innerHTML = `
${svg}
`; + } else { + renderEl.innerHTML = svg; + + // skip over the xml version tag + const svgEl = renderEl.lastChild; - svgEl.id = "diagram"; - Zoom.attach(); + svgEl.id = "diagram"; + Zoom.attach(); - svgEl.setAttribute("width", `${containerWidth}px`); - svgEl.setAttribute("height", `${containerHeight}px`); + svgEl.setAttribute("width", `${containerWidth}px`); + svgEl.setAttribute("height", `${containerHeight}px`); + } unlockCompileBtn(); + Export.updateExportButton(); } function parseRange(rs) { diff --git a/src/js/modules/export.js b/src/js/modules/export.js index 9223778..72da781 100644 --- a/src/js/modules/export.js +++ b/src/js/modules/export.js @@ -1,9 +1,10 @@ import Alert from "./alert.js"; import Editor from "./editor.js"; +import Sketch from "./sketch.js"; import Mobile from "../lib/mobile.js"; function init() { - document.getElementById("export-btn").addEventListener("click", toggleMenu); + document.getElementById("export-btn").addEventListener("click", handleExportClick); document.getElementById("export-menu").addEventListener("mouseleave", hideMenu); document.getElementById("export-menu-png").addEventListener("click", exportPNG); document.getElementById("export-menu-svg").addEventListener("click", exportSVG); @@ -22,6 +23,30 @@ function init() { } } +function handleExportClick() { + if (Sketch.getASCII()) { + copyASCII(); + } else { + toggleMenu(); + } +} + +function updateExportButton() { + const exportBtn = document.getElementById("export-btn"); + const exportMenu = document.getElementById("export-menu"); + + if (!exportBtn || !exportMenu) { + return; + } + + if (Sketch.getASCII()) { + exportBtn.textContent = "Copy"; + exportMenu.style.display = "none"; + } else { + exportBtn.innerHTML = 'Export'; + } +} + function toggleMenu() { const menu = document.getElementById("export-menu"); if (menu.style.display == "none") { @@ -201,6 +226,22 @@ async function exportPNGClipboard() { }, "image/png"); } +async function copyASCII() { + const ascii = Editor.getDiagramSVG(); + if (ascii == "") { + Alert.show(`Compile a diagram to copy`, 4000); + return; + } + + try { + await navigator.clipboard.writeText(ascii); + Alert.show(`Copied to clipboard`, 2000, "success"); + } catch (e) { + Alert.show(`Failed to copy to clipboard: ${e}`, 4000); + } +} + export default { init, + updateExportButton, }; diff --git a/src/js/modules/sketch.js b/src/js/modules/sketch.js index 6f6ac4c..4236708 100644 --- a/src/js/modules/sketch.js +++ b/src/js/modules/sketch.js @@ -1,66 +1,107 @@ import Editor from "./editor.js"; +import Export from "./export.js"; import QueryParams from "../lib/queryparams"; +const rendererOptions = ["SVG", "Sketch", "ASCII"]; + function init() { - readQueryParam(); - document - .getElementById("sketch-checkbox") - .addEventListener("change", (e) => toggleSketch(e.target.checked)); - document - .getElementById("sketch-mobile-icon") - .addEventListener("click", () => toggleSketch(QueryParams.get("sketch") === "0")); - updateMobileIcon(QueryParams.get("sketch") === "1"); + readQueryParams(); + hydrateRendererOptions(); + addListeners(); + + document.getElementById("renderer-btn").addEventListener("click", toggleMenu); + document.getElementById("renderer-menu").addEventListener("mouseleave", hideMenu); + + Export.updateExportButton(); } -function readQueryParam() { - const param = QueryParams.get("sketch"); - if (param === "") { - return; +function readQueryParams() { + const sketchParam = QueryParams.get("sketch"); + const asciiParam = QueryParams.get("ascii"); + + let current = "SVG"; + + if (asciiParam === "1") { + current = "ASCII"; + } else if (sketchParam === "1") { + current = "Sketch"; } - if (param === "1") { - document.getElementById("sketch-checkbox").checked = true; - return; - } else if (param === "0") { - document.getElementById("sketch-checkbox").checked = false; - return; - } else { - QueryParams.del("sketch"); + const currentRendererEl = document.getElementById("current-renderer"); + if (currentRendererEl) { + currentRendererEl.textContent = current; + } +} + +function hydrateRendererOptions() { + let itemEls = ""; + + for (const option of rendererOptions) { + itemEls += `
${option}
`; + } + + document.getElementById("renderer-menu").innerHTML = itemEls; +} + +function addListeners() { + for (const el of document.getElementsByClassName("renderer-option")) { + el.addEventListener("click", () => changeRenderer(el.textContent)); } } -function toggleSketch(on) { - const icon = document.getElementById("sketch-toggle-icon"); - if (on) { - icon.src = "assets/icons/toggle_check.svg"; +function changeRenderer(name) { + document.getElementById("current-renderer").textContent = name; + hideMenu(); + + // Clear both parameters first + QueryParams.del("sketch"); + QueryParams.del("ascii"); + + // Set the appropriate parameter + if (name === "Sketch") { QueryParams.set("sketch", "1"); - } else { - icon.src = "assets/icons/toggle_x.svg"; - QueryParams.set("sketch", "0"); + } else if (name === "ASCII") { + QueryParams.set("ascii", "1"); } + if (Editor.getDiagramSVG()) { Editor.compile(); } - - updateMobileIcon(on); + Export.updateExportButton(); } -function updateMobileIcon(on) { - const icon = document.getElementById("sketch-mobile"); - - if (on) { - icon.classList.add("sketch-mobile-activated"); +function toggleMenu() { + const menu = document.getElementById("renderer-menu"); + if (menu.style.display == "none") { + menu.style.display = "block"; } else { - icon.classList.remove("sketch-mobile-activated"); + menu.style.display = "none"; } } +function hideMenu() { + document.getElementById("renderer-menu").style.display = "none"; +} + +function getCurrentRenderer() { + return document.getElementById("current-renderer").textContent; +} + function getValue() { - return document.getElementById("sketch-checkbox").checked ? "1" : "0"; + const current = getCurrentRenderer(); + if (current === "Sketch") { + return "1"; + } + return "0"; +} + +function getASCII() { + return getCurrentRenderer() === "ASCII"; } export default { init, getValue, + getASCII, }; diff --git a/src/js/package.json b/src/js/package.json index 356c73d..7fe736e 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -6,7 +6,7 @@ "author": "Terrastruct, Inc.", "license": "MPL 2.0", "dependencies": { - "@terrastruct/d2": "^0.1.31", + "@terrastruct/d2": "^0.1.33", "monaco-editor": "^0.34.1", "shiki": "^0.11.1", "vscode-oniguruma": "^1.7.0", diff --git a/src/js/yarn.lock b/src/js/yarn.lock index 9290010..b4f62ee 100644 --- a/src/js/yarn.lock +++ b/src/js/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@terrastruct/d2@^0.1.31": - version "0.1.31" - resolved "https://registry.yarnpkg.com/@terrastruct/d2/-/d2-0.1.31.tgz#4eec04f46078debd72e619871aa7cadd03118a8a" - integrity sha512-+WrfaOgG9C4TnfJX/vpk2v0RwKj4FHXiFamGPW+3zeVgTZ1YKJAQYEdgyH5i7EFP4kWgembxOOXIRBgbE3woNw== +"@terrastruct/d2@^0.1.33": + version "0.1.33" + resolved "https://registry.yarnpkg.com/@terrastruct/d2/-/d2-0.1.33.tgz#8e9bde66c3316f3e18ffb576bdeab5c667f328a3" + integrity sha512-eK5hyfGIJFolC7sUsiKvWdY9xGFctTe3d+PSijo09IYDso8psztC+A4SammizXtlwYZpnnW0AtDjfBYauceSeA== jsonc-parser@^3.0.0: version "3.2.0"