diff --git a/css/activities.css b/css/activities.css
index 1c09e1fec1..9d91f05d57 100644
--- a/css/activities.css
+++ b/css/activities.css
@@ -90,6 +90,66 @@
font-size: large;
}
+.trash-view {
+ position: relative;
+ background-color: white;
+ max-width: 396px;
+ max-height: 200px;
+ overflow-y: auto;
+ font-size: 16px;
+ color: black;
+ border: 2px solid #87cefa;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ text-align: left;
+}
+
+.button-container {
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ display: flex;
+ gap: 10px;
+ background: #2196F3;
+ margin: 0;
+ padding: 5px;
+ border-bottom: 1px solid #d9d9d9;
+}
+
+.restore-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 90px;
+ height: 35px;
+ cursor: pointer;
+ background-color: white;
+ border-radius: 8px;
+}
+
+.trash-item {
+ padding: 2px 12px;
+ margin: 1px 0;
+ border-radius: 4px;
+ transition: background-color 0.3s;
+}
+
+.trash-item.hover {
+ background-color: #d9d9d9;
+}
+
+.trash-item-icon {
+ width: 30px;
+ height: 30px;
+ margin-right: 10px;
+ vertical-align: middle;
+}
+
+.hidden {
+ display: none;
+}
+
.ui-menu {
position: relative;
background-color: rgba(255, 255, 255, 1);
diff --git a/index.html b/index.html
index aea8f6d80a..ff5c876b22 100644
--- a/index.html
+++ b/index.html
@@ -780,6 +780,7 @@
>
space
+
restore_from_trash
+
{
if (!activity.blocks || !activity.blocks.trashStacks || activity.blocks.trashStacks.length === 0) {
activity.textMsg(
- _("Nothing in the trash to restore."),
+ _("Trash can is empty."),
3000
);
return;
}
- activity._restoreTrash();
+
+ if (docById("helpfulWheelDiv").style.display !== "none") {
+ docById("helpfulWheelDiv").style.display = "none";
+ activity.__tick();
+ }
+ };
+
+ const restoreTrashPop = (activity) => {
+ if (!activity.blocks || !activity.blocks.trashStacks || activity.blocks.trashStacks.length === 0) {
+ activity.textMsg(
+ _("Trash can is empty."),
+ 3000
+ );
+ return;
+ }
+ const lastId = this.blocks.trashStacks[this.blocks.trashStacks.length - 1];
+ if (lastId) this._restoreTrashById(lastId);
activity.textMsg(
_("Item restored from the trash."),
3000
);
-
+
if (docById("helpfulWheelDiv").style.display !== "none") {
docById("helpfulWheelDiv").style.display = "none";
activity.__tick();
}
- };
+
+ }
- this._restoreTrash = () => {
+ this._restoreTrashById = (blockId) => {
+ const blockIndex = this.blocks.trashStacks.indexOf(blockId);
+ if (blockIndex === -1) return; // Block not found in trash
+
+ this.blocks.trashStacks.splice(blockIndex, 1); // Remove from trash
+
for (const name in this.palettes.dict) {
this.palettes.dict[name].hideMenu(true);
}
-
+
this.blocks.activeBlock = null;
this.refreshCanvas();
const dx = 0;
const dy = -this.cellSize * 3; // Reposition
-
- if (this.blocks.trashStacks.length === 0) {
- return;
- }
-
- const thisBlock = this.blocks.trashStacks.pop();
-
- // Restore drag group in trash
- this.blocks.findDragGroup(thisBlock);
+
+ // Restore drag group
+ this.blocks.findDragGroup(blockId);
for (let b = 0; b < this.blocks.dragGroup.length; b++) {
const blk = this.blocks.dragGroup[b];
this.blocks.blockList[blk].trash = false;
this.blocks.moveBlockRelative(blk, dx, dy);
this.blocks.blockList[blk].show();
}
-
- this.blocks.raiseStackToTop(thisBlock);
-
- if (
- this.blocks.blockList[thisBlock].name === "start" ||
- this.blocks.blockList[thisBlock].name === "drum"
- ) {
- const turtle = this.blocks.blockList[thisBlock].value;
+
+ this.blocks.raiseStackToTop(blockId);
+ const restoredBlock = this.blocks.blockList[blockId];
+
+ if (restoredBlock.name === 'start' || restoredBlock.name === 'drum') {
+ const turtle = restoredBlock.value;
this.turtles.turtleList[turtle].inTrash = false;
this.turtles.turtleList[turtle].container.visible = true;
- } else if (this.blocks.blockList[thisBlock].name === "action") {
- // We need to add a palette entry for this action.
- // But first we need to ensure we have a unqiue name,
- // as the name could have been taken in the interim.
- const actionArg = this.blocks.blockList[
- this.blocks.blockList[thisBlock].connections[1]
- ];
+ } else if (restoredBlock.name === 'action') {
+ const actionArg = this.blocks.blockList[restoredBlock.connections[1]];
if (actionArg !== null) {
let label;
const oldName = actionArg.value;
- // Mark the action block as still being in the
- // trash so that its name won't be considered when
- // looking for a unique name.
- this.blocks.blockList[thisBlock].trash = true;
+ restoredBlock.trash = true;
const uniqueName = this.blocks.findUniqueActionName(oldName);
- this.blocks.blockList[thisBlock].trash = false;
+ restoredBlock.trash = false;
if (uniqueName !== actionArg) {
actionArg.value = uniqueName;
-
- label = actionArg.value.toString();
- if (label.length > 8) {
- label = label.substr(0, 7) + "...";
- }
+ label = uniqueName.length > 8 ? uniqueName.substr(0, 7) + '...' : uniqueName;
actionArg.text.text = label;
if (actionArg.label !== null) {
actionArg.label.value = uniqueName;
}
-
actionArg.container.updateCache();
-
- // Check the drag group to ensure any do blocks are updated (in case of recursion).
for (let b = 0; b < this.blocks.dragGroup.length; b++) {
const me = this.blocks.blockList[this.blocks.dragGroup[b]];
if (
@@ -3415,11 +3414,7 @@ class Activity {
) {
me.privateData = uniqueName;
me.value = uniqueName;
-
- label = me.value.toString();
- if (label.length > 8) {
- label = label.substr(0, 7) + "...";
- }
+ label = uniqueName.length > 8 ? uniqueName.substr(0, 7) + '...' : uniqueName;
me.text.text = label;
me.overrideName = label;
me.regenerateArtwork();
@@ -3429,22 +3424,108 @@ class Activity {
}
}
}
-
+ activity.textMsg(
+ _("Item restored from the trash."),
+ 3000
+ );
+
this.refreshCanvas();
- };
+ };
+
+ // Add event listener for trash icon click
+ document.getElementById('restoreIcon').addEventListener('click', () => {
+ this._renderTrashView();
+ });
+
+ // function to hide trashView from canvas
+ function handleClickOutsideTrashView(trashView) {
+ let firstClick = true;
+ document.addEventListener('click', (event) => {
+ if (firstClick) {
+ firstClick = false;
+ return;
+ }
+ if (!trashView.contains(event.target) && event.target !== trashView) {
+ trashView.style.display = 'none';
+ }
+ });
+ }
- this.handleKeyDown = (event) => {
-
- if (event.ctrlKey && event.key === "z") {
- this._restoreTrash(activity);
- activity.__tick();
- event.preventDefault();
+ this._renderTrashView = () => {
+ if (!activity.blocks || !activity.blocks.trashStacks || activity.blocks.trashStacks.length === 0) {
+ return;
+ }
+ const trashList = document.getElementById('trashList');
+ const trashView = document.createElement('div');
+ trashView.id = 'trashView';
+ trashView.classList.add('trash-view');
+
+ // Sticky buttons
+ const buttonContainer = document.createElement('div');
+ buttonContainer.classList.add('button-container');
+
+ const restoreLastBtn = document.createElement('button');
+ restoreLastBtn.textContent = "Restore last";
+ restoreLastBtn.classList.add('restore-button');
+ restoreLastBtn.addEventListener('click', () => {
+ const lastId = this.blocks.trashStacks[this.blocks.trashStacks.length - 1];
+ if (lastId) this._restoreTrashById(lastId);
+ trashView.classList.add('hidden');
+ });
+
+ const restoreAllBtn = document.createElement('button');
+ restoreAllBtn.textContent = "Restore all";
+ restoreAllBtn.classList.add('restore-button');
+ restoreAllBtn.addEventListener('click', () => {
+ while (this.blocks.trashStacks.length > 0) {
+ this._restoreTrashById(this.blocks.trashStacks[0]);
+ }
+ trashView.classList.add('hidden');
+ });
+
+ buttonContainer.appendChild(restoreLastBtn);
+ buttonContainer.appendChild(restoreAllBtn);
+ trashView.appendChild(buttonContainer);
+
+ // Render trash items
+ this.blocks.trashStacks.forEach((blockId) => {
+ const block = this.blocks.blockList[blockId];
+ const listItem = document.createElement('div');
+ listItem.classList.add('trash-item');
+
+ const svgData = block.artwork;
+ const encodedData = 'data:image/svg+xml;utf8,' + encodeURIComponent(svgData);
+
+ const img = document.createElement('img');
+ img.src = encodedData;
+ img.alt = 'Block Icon';
+ img.classList.add('trash-item-icon');
+
+ const textNode = document.createTextNode(block.name);
+
+ listItem.appendChild(img);
+ listItem.appendChild(textNode);
+ listItem.dataset.blockId = blockId;
+
+ listItem.addEventListener('mouseover', () => listItem.classList.add('hover'));
+ listItem.addEventListener('mouseout', () => listItem.classList.remove('hover'));
+ listItem.addEventListener('click', () => {
+ this._restoreTrashById(blockId);
+ trashView.classList.add('hidden');
+ });
+ handleClickOutsideTrashView(trashView);
+
+ trashView.appendChild(listItem);
+ });
+
+ const existingView = document.getElementById('trashView');
+ if (existingView) {
+ trashList.replaceChild(trashView, existingView);
+ } else {
+ trashList.appendChild(trashView);
}
};
- // Attach keydown event listener to document
- document.addEventListener("keydown", this.handleKeyDown);
-
/*
* Open aux menu
*/
@@ -5814,7 +5895,7 @@ class Activity {
this.helpfulWheelItems.push({label: "Increase block size", icon: "imgsrc:data:image/svg+xml;base64," + window.btoa(base64Encode(BIGGERBUTTON)), display: true, fn: doLargerBlocks});
if (!this.helpfulWheelItems.find(ele => ele.label === "Restore"))
- this.helpfulWheelItems.push({label: "Restore", icon: "imgsrc:header-icons/restore-from-trash.svg", display: true, fn: restoreTrash});
+ this.helpfulWheelItems.push({label: "Restore", icon: "imgsrc:header-icons/restore-from-trash.svg", display: true, fn: restoreTrashPop});
if (!this.helpfulWheelItems.find(ele => ele.label === "Turtle Wrap Off"))
this.helpfulWheelItems.push({label: "Turtle Wrap Off", icon: "imgsrc:header-icons/wrap-text.svg", display: true, fn: this.toolbar.changeWrap});