Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions css/activities.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,72 @@
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the background for this container should be the same blue we use for toolbars.
Maybe the buttons should be icons with hover help text instead of labels. (We'll need to decide on what icons.)

position: sticky;
display: flex;
justify-content: space-between;
top: 0;
z-index: 10;
display: flex;
gap: 10px;
background: #2196F3;
margin: 0;
padding: 5px;
border-bottom: 1px solid #d9d9d9;
}

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

#restoreLastIcon, #restoreAllIcon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
cursor: pointer;
}


.material-icons.md-48 {
font-size: 32px;
}


.hidden {
display: none;
}

.ui-menu {
position: relative;
background-color: rgba(255, 255, 255, 1);
Expand Down
2 changes: 2 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@
>
<a style="color: transparent;">space&nbsp;&nbsp;&nbsp;</a>
</li>

<li>
<a
id="restoreIcon"
Expand All @@ -789,6 +790,7 @@
>restore_from_trash</i
></a
>
<div id="trashList"></div>
</li>
<li>
<a
Expand Down
209 changes: 148 additions & 61 deletions js/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -3320,91 +3320,91 @@ class Activity {
const restoreTrash = (activity) => {
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;
}
for(let i=this.blocks.trashStacks.length - 1;i<this.blocks.trashStacks.length;i++){
this._restoreTrashById(this.blocks.trashStacks[i]);
}
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 (
Expand All @@ -3415,11 +3415,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();
Expand All @@ -3429,21 +3425,112 @@ 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;
}
};

// Attach keydown event listener to document
document.addEventListener("keydown", this.handleKeyDown);
const trashList = document.getElementById('trashList');
const trashView = document.createElement('div');
trashView.id = 'trashView';
trashView.classList.add('trash-view');

// Sticky icons
const buttonContainer = document.createElement('div');
buttonContainer.classList.add('button-container');

const restoreLastIcon = document.createElement('a');
restoreLastIcon.id = 'restoreLastIcon';
restoreLastIcon.classList.add('restore-last-icon');
restoreLastIcon.innerHTML = '<i class="material-icons md-48">restore_from_trash</i>';
restoreLastIcon.addEventListener('click', () => {
for(let i=this.blocks.trashStacks.length - 1;i<this.blocks.trashStacks.length;i++){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems overly complicated. Why do it this way?
Also, please be sure to include spaces around operators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems overly complicated. Why do it this way? Also, please be sure to include spaces around operators.

Yes this way is little bit complicated. I tried to handle the edge case many different ways but it not works. But it works for restore all because it uses loop. So to handle this case I switched to loop so that functionality remains constant for all cases.

this._restoreTrashById(this.blocks.trashStacks[i]);
}
trashView.classList.add('hidden');
});

const restoreAllIcon = document.createElement('a');
restoreAllIcon.id = 'restoreAllIcon';
restoreAllIcon.classList.add('restore-all-icon');
restoreAllIcon.innerHTML = '<i class="material-icons md-48">delete_sweep</i>';
restoreAllIcon.addEventListener('click', () => {
while (this.blocks.trashStacks.length > 0) {
this._restoreTrashById(this.blocks.trashStacks[0]);
}
trashView.classList.add('hidden');
});
restoreLastIcon.setAttribute("title", _("Restore last item"));
restoreAllIcon.setAttribute("title", _("Restore all items"));

buttonContainer.appendChild(restoreLastIcon);
buttonContainer.appendChild(restoreAllIcon);
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);
}
};

/*
* Open aux menu
Expand Down Expand Up @@ -5814,7 +5901,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});
Expand Down
Loading