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});