diff --git a/index.html b/index.html
index 9c41a37e..c841783b 100644
--- a/index.html
+++ b/index.html
@@ -1339,6 +1339,45 @@
Drag and drop code blocks to create your program. Use arrow keys
and Enter to navigate and interact with blocks.
+
+
+
+
+
+
+
diff --git a/locale/de.js b/locale/de.js
index 52531d0d..b70e23e0 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -1209,6 +1209,11 @@ export default {
RightLeg_option: "Rechtes Schienbein",
RightFoot_option: "Rechter Fuß",
+ // Workspace toolbar
+ toolbar_undo_ui: "Rückgängig",
+ toolbar_redo_ui: "Wiederholen",
+ toolbar_zoom_out_ui: "Verkleinern",
+ toolbar_zoom_in_ui: "Vergrößern",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Tastaturkürzel",
shortcut_panel_close: "Tastaturkürzel schließen",
diff --git a/locale/en.js b/locale/en.js
index e65b535f..4e0e8597 100644
--- a/locale/en.js
+++ b/locale/en.js
@@ -1259,6 +1259,11 @@ export default {
update_available_ui: "A new version of Flock is available.",
reload_button_ui: "Reload",
+ // Workspace toolbar
+ toolbar_undo_ui: "Undo",
+ toolbar_redo_ui: "Redo",
+ toolbar_zoom_out_ui: "Zoom out",
+ toolbar_zoom_in_ui: "Zoom in",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Keyboard shortcuts",
shortcut_panel_close: "Close keyboard shortcuts",
diff --git a/locale/es.js b/locale/es.js
index 99f5005f..12781ba8 100644
--- a/locale/es.js
+++ b/locale/es.js
@@ -1222,6 +1222,11 @@ export default {
RightLeg_option: "Espinilla derecha", // human
RightFoot_option: "Pie derecho", // human
+ // Workspace toolbar
+ toolbar_undo_ui: "Deshacer",
+ toolbar_redo_ui: "Rehacer",
+ toolbar_zoom_out_ui: "Alejar",
+ toolbar_zoom_in_ui: "Acercar",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Atajos de teclado",
shortcut_panel_close: "Cerrar atajos de teclado",
diff --git a/locale/fr.js b/locale/fr.js
index bf8ad46b..fd1e4e3f 100644
--- a/locale/fr.js
+++ b/locale/fr.js
@@ -1220,6 +1220,11 @@ export default {
RightLeg_option: "Tibia droit",
RightFoot_option: "Pied droit",
+ // Workspace toolbar
+ toolbar_undo_ui: "Annuler",
+ toolbar_redo_ui: "Rétablir",
+ toolbar_zoom_out_ui: "Dézoomer",
+ toolbar_zoom_in_ui: "Zoomer",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Raccourcis clavier",
shortcut_panel_close: "Fermer les raccourcis clavier",
diff --git a/locale/it.js b/locale/it.js
index 489d5675..ecec3eca 100644
--- a/locale/it.js
+++ b/locale/it.js
@@ -1211,6 +1211,11 @@ export default {
RightLeg_option: "Stinco destro",
RightFoot_option: "Piede destro",
+ // Workspace toolbar
+ toolbar_undo_ui: "Annulla",
+ toolbar_redo_ui: "Ripeti",
+ toolbar_zoom_out_ui: "Riduci zoom",
+ toolbar_zoom_in_ui: "Aumenta zoom",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Scorciatoie da tastiera",
shortcut_panel_close: "Chiudi le scorciatoie da tastiera",
diff --git a/locale/pl.js b/locale/pl.js
index e135d699..bcfc3c53 100644
--- a/locale/pl.js
+++ b/locale/pl.js
@@ -1216,6 +1216,11 @@ export default {
RightLeg_option: "Prawe podudzie",
RightFoot_option: "Prawa stopa",
+ // Workspace toolbar
+ toolbar_undo_ui: "Cofnij",
+ toolbar_redo_ui: "Ponów",
+ toolbar_zoom_out_ui: "Oddal",
+ toolbar_zoom_in_ui: "Przybliż",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Skróty klawiaturowe",
shortcut_panel_close: "Zamknij skróty klawiaturowe",
diff --git a/locale/pt.js b/locale/pt.js
index e042a4b2..f46f90a1 100644
--- a/locale/pt.js
+++ b/locale/pt.js
@@ -1208,6 +1208,11 @@ export default {
RightLeg_option: "Canela direita",
RightFoot_option: "Pé direito",
+ // Workspace toolbar
+ toolbar_undo_ui: "Desfazer",
+ toolbar_redo_ui: "Refazer",
+ toolbar_zoom_out_ui: "Reduzir zoom",
+ toolbar_zoom_in_ui: "Aumentar zoom",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Atalhos de teclado",
shortcut_panel_close: "Fechar atalhos de teclado",
diff --git a/locale/sv.js b/locale/sv.js
index fd932f9e..857f49d1 100644
--- a/locale/sv.js
+++ b/locale/sv.js
@@ -1198,6 +1198,11 @@ export default {
RightLeg_option: "Höger smalben",
RightFoot_option: "Höger fot",
+ // Workspace toolbar
+ toolbar_undo_ui: "Ångra",
+ toolbar_redo_ui: "Gör om",
+ toolbar_zoom_out_ui: "Zooma ut",
+ toolbar_zoom_in_ui: "Zooma in",
// Keyboard shortcuts panel — title and close button
shortcut_panel_title: "Tangentbordsgenvägar",
shortcut_panel_close: "Stäng tangentbordsgenvägar",
diff --git a/main/accessibility.js b/main/accessibility.js
index d4f18fe3..a3c00faf 100644
--- a/main/accessibility.js
+++ b/main/accessibility.js
@@ -16,6 +16,12 @@ const AreaManager = {
{ selector: "#resizer", label: "5", pad: -3, name: "Resizer" },
{ selector: ".blocklyToolbox", label: "6", name: "Toolbox" },
{ selector: "svg.blocklySvg", label: "7", name: "Code editor" },
+ {
+ selector: "#blocklyZoomControls",
+ label: "8",
+ name: "Workspace controls",
+ extend: { top: -8 },
+ },
],
init() {
@@ -133,11 +139,16 @@ const AreaManager = {
const highlight = document.createElement("div");
const pad = area.pad ?? 1;
+ const ext = area.extend ?? {};
+ const eTop = ext.top ?? 0;
+ const eBottom = ext.bottom ?? 0;
+ const eLeft = ext.left ?? 0;
+ const eRight = ext.right ?? 0;
highlight.className = "area-outline";
- highlight.style.top = `${rect.top - pad}px`;
- highlight.style.left = `${rect.left - pad}px`;
- highlight.style.width = `${rect.width + pad * 2}px`;
- highlight.style.height = `${rect.height + pad * 2}px`;
+ highlight.style.top = `${rect.top - pad - eTop}px`;
+ highlight.style.left = `${rect.left - pad - eLeft}px`;
+ highlight.style.width = `${rect.width + pad * 2 + eLeft + eRight}px`;
+ highlight.style.height = `${rect.height + pad * 2 + eTop + eBottom}px`;
container.appendChild(highlight);
}
});
diff --git a/main/input.js b/main/input.js
index 63ac4ab8..393a03ab 100644
--- a/main/input.js
+++ b/main/input.js
@@ -130,6 +130,11 @@ export function setupInput() {
pushUnique(workspaceGroup);
}
+ // 6b) Workspace toolbar
+ ["#undoBtn", "#redoBtn", "#zoomOutBtn", "#zoomInBtn"].forEach((sel) =>
+ pushUnique(document.querySelector(sel)),
+ );
+
// 7) Main UI controls (in natural order)
[
"#menuBtn",
diff --git a/main/main.js b/main/main.js
index 5b89614d..15f507cd 100644
--- a/main/main.js
+++ b/main/main.js
@@ -587,6 +587,20 @@ function initializeApp() {
stopCodeButton.addEventListener("click", stopCode);
exportCodeButton.addEventListener("click", () => exportCode(workspace));
+ // Add toolbar buttons
+ const zoomInBtn = document.getElementById("zoomInBtn");
+ const zoomOutBtn = document.getElementById("zoomOutBtn");
+ const undoBtn = document.getElementById("undoBtn");
+ const redoBtn = document.getElementById("redoBtn");
+ if (zoomInBtn)
+ zoomInBtn.addEventListener("click", () => workspace.zoomCenter(1));
+ if (zoomOutBtn)
+ zoomOutBtn.addEventListener("click", () => workspace.zoomCenter(-1));
+ if (undoBtn)
+ undoBtn.addEventListener("click", () => workspace.undo(false));
+ if (redoBtn)
+ redoBtn.addEventListener("click", () => workspace.undo(true));
+
// Make open button work with keyboard
if (openButton) {
openButton.addEventListener("click", () => {
diff --git a/style.css b/style.css
index 8befb751..58e00c94 100644
--- a/style.css
+++ b/style.css
@@ -474,6 +474,34 @@ button {
position: relative;
background-color: #f3f3f3;
}
+
+#codePanel {
+ position: relative;
+}
+
+#blocklyZoomControls {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 50px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 4px;
+ padding: 0 8px;
+ background-color: var(--color-bg);
+ box-shadow: 0px 0px 10px var(--color-shadow);
+ z-index: 10;
+}
+
+#blocklyZoomControls .toolbar-divider {
+ width: 1px;
+ height: 24px;
+ background-color: var(--color-border);
+ margin: 0 4px;
+}
[data-theme="dark"] #canvasArea {
background-color: #2c2c2c !important;
}
@@ -815,6 +843,14 @@ button {
justify-content: center;
touch-action: manipulation;
}
+
+ #blocklyZoomControls {
+ bottom: calc(9px + max(8px, env(safe-area-inset-bottom, 0px)));
+ }
+
+ #blocklyDiv {
+ height: calc(100% - 59px - max(8px, env(safe-area-inset-bottom, 0px))) !important;
+ }
}
@media only screen and (orientation: landscape) and (max-width: 768px) {
@@ -868,7 +904,7 @@ button {
#blocklyDiv {
width: 100% !important;
- height: 100% !important;
+ height: calc(100% - 50px) !important;
position: relative;
}
diff --git a/style/blockly.css b/style/blockly.css
index a75269bd..1ff35190 100644
--- a/style/blockly.css
+++ b/style/blockly.css
@@ -4,9 +4,9 @@
--color-bg: white;
--color-text: black;
--color-text-label: var(--color-text);
- --axis-x-color: #1A9EE0;
- --axis-y-color: #00CC96;
- --axis-z-color: #F07020;
+ --axis-x-color: #1a9ee0;
+ --axis-y-color: #00cc96;
+ --axis-z-color: #f07020;
--color-border-highlight: #ee5d6c;
--color-search-highlight: #ed808b;
--color-shadow: grey;
@@ -191,7 +191,9 @@ textarea.blocklyCommentText.blocklyTextarea.blocklyText {
fill: rgba(255, 255, 255, 0.85) !important;
}
-[data-theme="low-vision"] .blocklyMainWorkspaceScrollbar .blocklyScrollbarHandle {
+[data-theme="low-vision"]
+ .blocklyMainWorkspaceScrollbar
+ .blocklyScrollbarHandle {
fill: rgba(224, 224, 224, 0.85) !important;
}
@@ -339,6 +341,8 @@ path.blocklyPath.blockly-ws-search-highlight.blockly-ws-search-current {
border-radius: 4px;
}
+.blocklyZoomIn,
+.blocklyZoomOut,
.blocklyZoomReset {
display: none;
}
@@ -578,7 +582,6 @@ body[data-theme="dark"] .blocklyTreeLabel {
border-radius: 4px;
}
-
/* Add custom selection outline for both level 1 and 2 categories */
.blocklyToolboxCategoryContainer[aria-selected="true"][aria-level="1"]
> .blocklyToolboxCategory,
@@ -623,7 +626,9 @@ body[data-theme="low-vision"]
}
[data-theme="low-vision"] .blocklyEditableText > rect,
-[data-theme="low-vision"] .blocklyNonEditableText > rect:not(.blocklyDropdownRect) {
+[data-theme="low-vision"]
+ .blocklyNonEditableText
+ > rect:not(.blocklyDropdownRect) {
fill: #ffffff !important;
stroke: #cfcfcf !important;
stroke-width: 1.5px !important;
@@ -802,14 +807,22 @@ body[data-theme="low-vision"]
}
/* Keyboard focus for DROPDOWN fields */
-.blocklyKeyboardNavigation .blocklyActiveFocus.blocklyDropdownField .blocklyDropdownRect,
-.blocklyKeyboardNavigation .blocklyActiveFocus.blocklyFieldDropdown .blocklyDropdownRect,
-.blocklyKeyboardNavigation .blocklyActiveFocus.blocklyEditableText .blocklyDropdownRect {
+.blocklyKeyboardNavigation
+ .blocklyActiveFocus.blocklyDropdownField
+ .blocklyDropdownRect,
+.blocklyKeyboardNavigation
+ .blocklyActiveFocus.blocklyFieldDropdown
+ .blocklyDropdownRect,
+.blocklyKeyboardNavigation
+ .blocklyActiveFocus.blocklyEditableText
+ .blocklyDropdownRect {
stroke: var(--block-outline-focus) !important;
stroke-width: 3px !important;
}
-.blocklyKeyboardNavigation .blocklyActiveFocus.blocklyImageField image[style*="cursor: pointer"] {
+.blocklyKeyboardNavigation
+ .blocklyActiveFocus.blocklyImageField
+ image[style*="cursor: pointer"] {
stroke: var(--block-outline-focus);
stroke-width: 5px;
}
@@ -821,19 +834,27 @@ body[data-theme="low-vision"]
}
/* Passive keyboard focus for DROPDOWN fields */
-.blocklyKeyboardNavigation .blocklyPassiveFocus.blocklyDropdownField .blocklyDropdownRect,
-.blocklyKeyboardNavigation .blocklyPassiveFocus.blocklyFieldDropdown .blocklyDropdownRect,
-.blocklyKeyboardNavigation .blocklyPassiveFocus.blocklyEditableText .blocklyDropdownRect {
+.blocklyKeyboardNavigation
+ .blocklyPassiveFocus.blocklyDropdownField
+ .blocklyDropdownRect,
+.blocklyKeyboardNavigation
+ .blocklyPassiveFocus.blocklyFieldDropdown
+ .blocklyDropdownRect,
+.blocklyKeyboardNavigation
+ .blocklyPassiveFocus.blocklyEditableText
+ .blocklyDropdownRect {
stroke: var(--color-outline-focus) !important;
stroke-width: 3px !important;
stroke-dasharray: 4 2;
}
-.blocklyKeyboardNavigation .blocklyPassiveFocus.blocklyImageField image[style*="cursor: pointer"] {
+.blocklyKeyboardNavigation
+ .blocklyPassiveFocus.blocklyImageField
+ image[style*="cursor: pointer"] {
stroke: var(--color-outline-focus);
stroke-width: 5px;
- stroke-dasharray: 4 2; /* Blockly-style short dash */
- stroke-linecap: butt; /* square dash ends */
+ stroke-dasharray: 4 2; /* Blockly-style short dash */
+ stroke-linecap: butt; /* square dash ends */
vector-effect: non-scaling-stroke;
}
@@ -948,13 +969,21 @@ textarea.blocklyCommentText.blocklyTextarea.blocklyText {
box-sizing: border-box !important;
}
-
/* XYZ axis input outlines — survive theme changes via CSS */
[data-xyz-active] > [data-axis="X"] .blocklyPath,
-[data-axis="X"].blocklySelected .blocklyPath { stroke: var(--axis-x-color) !important; stroke-width: 2px !important; }
+[data-axis="X"].blocklySelected .blocklyPath {
+ stroke: var(--axis-x-color) !important;
+ stroke-width: 2px !important;
+}
[data-xyz-active] > [data-axis="Y"] .blocklyPath,
-[data-axis="Y"].blocklySelected .blocklyPath { stroke: var(--axis-y-color) !important; stroke-width: 2px !important; }
+[data-axis="Y"].blocklySelected .blocklyPath {
+ stroke: var(--axis-y-color) !important;
+ stroke-width: 2px !important;
+}
[data-xyz-active] > [data-axis="Z"] .blocklyPath,
-[data-axis="Z"].blocklySelected .blocklyPath { stroke: var(--axis-z-color) !important; stroke-width: 2px !important; }
+[data-axis="Z"].blocklySelected .blocklyPath {
+ stroke: var(--axis-z-color) !important;
+ stroke-width: 2px !important;
+}