df5dc32eee
Allows for selections and dreams to be sent to the resource manager, allows resource deselection when clicking on the currently selected item, put some extra comments and allow saving of a selected canvas area Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com> Former-commit-id: 963312115d4827de1c8d4fac88a97dd64db7fa15
317 lines
8.9 KiB
JavaScript
317 lines
8.9 KiB
JavaScript
const stampTool = () =>
|
|
toolbar.registerTool(
|
|
"res/icons/file-up.svg",
|
|
"Stamp Image",
|
|
(state, opt) => {
|
|
// Draw new cursor immediately
|
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
|
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
|
|
|
|
// Start Listeners
|
|
mouse.listen.canvas.onmousemove.on(state.movecb);
|
|
mouse.listen.canvas.left.onclick.on(state.drawcb);
|
|
|
|
// For calls from other tools to paste image
|
|
if (opt && opt.image) {
|
|
const resource = state.addResource(
|
|
opt.name || "Clipboard",
|
|
opt.image,
|
|
opt.temporary === undefined ? true : opt.temporary
|
|
);
|
|
state.selected = resource;
|
|
document.getElementById(`resource-${resource.id}`).click();
|
|
state.ctxmenu.uploadButton.disabled = true;
|
|
state.back = opt.back || null;
|
|
toolbar.lock();
|
|
} else if (opt) {
|
|
throw Error(
|
|
"Pasting from other tools must be in format {image, name?, temporary?, back?}"
|
|
);
|
|
} else {
|
|
state.ctxmenu.uploadButton.disabled = "";
|
|
}
|
|
},
|
|
(state, opt) => {
|
|
// Clear Listeners
|
|
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
|
mouse.listen.canvas.left.onclick.clear(state.drawcb);
|
|
|
|
// Deselect
|
|
state.selected = null;
|
|
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
|
child.classList.remove("selected");
|
|
});
|
|
},
|
|
{
|
|
init: (state) => {
|
|
state.snapToGrid = true;
|
|
state.resources = [];
|
|
state.selected = null;
|
|
state.back = null;
|
|
|
|
const selectResource = (resource) => {
|
|
if (state.ctxmenu.uploadButton.disabled) return;
|
|
|
|
const resourceWrapper = resource.dom.wrapper;
|
|
|
|
const wasSelected = resourceWrapper.classList.contains("selected");
|
|
|
|
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
|
child.classList.remove("selected");
|
|
});
|
|
|
|
// Select
|
|
if (!wasSelected) {
|
|
resourceWrapper.classList.add("selected");
|
|
state.selected = resource;
|
|
}
|
|
// If already selected, clear selection
|
|
else {
|
|
resourceWrapper.classList.remove("selected");
|
|
state.selected = null;
|
|
}
|
|
};
|
|
|
|
// Synchronizes resources array with the DOM
|
|
const syncResources = () => {
|
|
// Creates DOM elements when needed
|
|
state.resources.forEach((resource) => {
|
|
if (
|
|
!state.ctxmenu.resourceList.querySelector(
|
|
`#resource-${resource.id}`
|
|
)
|
|
) {
|
|
console.debug(
|
|
`Creating resource element 'resource-${resource.id}'`
|
|
);
|
|
const resourceWrapper = document.createElement("div");
|
|
resourceWrapper.id = `resource-${resource.id}`;
|
|
resourceWrapper.textContent = resource.name;
|
|
resourceWrapper.classList.add("resource");
|
|
|
|
resourceWrapper.addEventListener("click", () =>
|
|
selectResource(resource)
|
|
);
|
|
|
|
resourceWrapper.addEventListener("mouseover", () => {
|
|
state.ctxmenu.previewPane.style.display = "block";
|
|
state.ctxmenu.previewPane.style.backgroundImage = `url(${resource.image.src})`;
|
|
});
|
|
resourceWrapper.addEventListener("mouseleave", () => {
|
|
state.ctxmenu.previewPane.style.display = "none";
|
|
});
|
|
|
|
state.ctxmenu.resourceList.appendChild(resourceWrapper);
|
|
resource.dom = {wrapper: resourceWrapper};
|
|
}
|
|
});
|
|
|
|
// Removes DOM elements when needed
|
|
const elements = Array.from(state.ctxmenu.resourceList.children);
|
|
|
|
if (elements.length > state.resources.length)
|
|
elements.forEach((element) => {
|
|
let remove = true;
|
|
state.resources.some((resource) => {
|
|
if (element.id.endsWith(resource.id)) remove = false;
|
|
});
|
|
|
|
if (remove) state.ctxmenu.resourceList.removeChild(element);
|
|
});
|
|
};
|
|
|
|
// Adds a image resource (temporary allows only one draw, used for pasting)
|
|
state.addResource = (name, image, temporary = false) => {
|
|
const id = guid();
|
|
const resource = {
|
|
id,
|
|
name,
|
|
image,
|
|
temporary,
|
|
};
|
|
state.resources.push(resource);
|
|
syncResources();
|
|
|
|
// Select this resource
|
|
selectResource(resource);
|
|
|
|
return resource;
|
|
};
|
|
|
|
// Deletes a resource (Yes, functionality is here, but we don't have an UI for this yet)
|
|
// Used for temporary images too
|
|
state.deleteResource = (id) => {
|
|
state.resources = state.resources.filter((v) => v.id !== id);
|
|
|
|
syncResources();
|
|
};
|
|
|
|
state.movecb = (evn) => {
|
|
if (evn.target.id === "overlayCanvas") {
|
|
let x = evn.x;
|
|
let y = evn.y;
|
|
if (state.snapToGrid) {
|
|
x += snap(evn.x, true, 64);
|
|
y += snap(evn.y, true, 64);
|
|
}
|
|
|
|
// Draw selected image
|
|
if (state.selected) {
|
|
ovCtx.drawImage(state.selected.image, x, y);
|
|
}
|
|
|
|
// Draw current cursor location
|
|
ovCtx.lineWidth = 3;
|
|
ovCtx.strokeStyle = "#FFF";
|
|
|
|
ovCtx.beginPath();
|
|
ovCtx.moveTo(x, y + 10);
|
|
ovCtx.lineTo(x, y - 10);
|
|
ovCtx.moveTo(x + 10, y);
|
|
ovCtx.lineTo(x - 10, y);
|
|
ovCtx.stroke();
|
|
}
|
|
};
|
|
|
|
state.drawcb = (evn) => {
|
|
if (evn.target.id === "overlayCanvas") {
|
|
let x = evn.x;
|
|
let y = evn.y;
|
|
if (state.snapToGrid) {
|
|
x += snap(evn.x, true, 64);
|
|
y += snap(evn.y, true, 64);
|
|
}
|
|
|
|
const resource = state.selected;
|
|
|
|
if (resource) {
|
|
commands.runCommand("drawImage", "Image Stamp", {
|
|
image: resource.image,
|
|
x,
|
|
y,
|
|
});
|
|
|
|
if (resource.temporary) state.deleteResource(resource.id);
|
|
}
|
|
|
|
if (state.back) {
|
|
toolbar.unlock();
|
|
state.back({message: "Returning from stamp", pasted: true});
|
|
}
|
|
}
|
|
};
|
|
state.cancelcb = (evn) => {
|
|
if (evn.target.id === "overlayCanvas") {
|
|
if (state.back) {
|
|
toolbar.unlock();
|
|
state.back({message: "Returning from stamp", pasted: false});
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates context menu
|
|
*/
|
|
if (!state.ctxmenu) {
|
|
state.ctxmenu = {};
|
|
// Snap To Grid Checkbox
|
|
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
|
|
state,
|
|
"snapToGrid",
|
|
"Snap To Grid"
|
|
).label;
|
|
|
|
// Create resource list
|
|
const uploadButtonId = `upload-btn-${guid()}`;
|
|
|
|
const resourceManager = document.createElement("div");
|
|
resourceManager.classList.add("resource-manager");
|
|
const resourceList = document.createElement("div");
|
|
resourceList.classList.add("resource-list");
|
|
|
|
const previewPane = document.createElement("div");
|
|
previewPane.classList.add("preview-pane");
|
|
|
|
const uploadLabel = document.createElement("label");
|
|
uploadLabel.classList.add("upload-button");
|
|
uploadLabel.classList.add("button");
|
|
uploadLabel.classList.add("tool");
|
|
uploadLabel.textContent = "Upload Image";
|
|
uploadLabel.htmlFor = uploadButtonId;
|
|
const uploadButton = document.createElement("input");
|
|
uploadButton.id = uploadButtonId;
|
|
uploadButton.type = "file";
|
|
uploadButton.accept = "image/*";
|
|
uploadButton.multiple = true;
|
|
uploadButton.style.display = "none";
|
|
|
|
uploadButton.addEventListener("change", (evn) => {
|
|
[...uploadButton.files].forEach((file) => {
|
|
if (file.type.startsWith("image/")) {
|
|
console.info("Uploading Image " + file.name);
|
|
const url = window.URL || window.webkitURL;
|
|
const image = document.createElement("img");
|
|
image.src = url.createObjectURL(file);
|
|
|
|
state.addResource(file.name, image, false);
|
|
}
|
|
});
|
|
|
|
uploadButton.value = null;
|
|
});
|
|
|
|
uploadLabel.appendChild(uploadButton);
|
|
resourceManager.appendChild(resourceList);
|
|
resourceManager.appendChild(uploadLabel);
|
|
resourceManager.appendChild(previewPane);
|
|
|
|
resourceManager.addEventListener(
|
|
"drop",
|
|
(evn) => {
|
|
evn.preventDefault();
|
|
resourceManager.classList.remove("dragging");
|
|
|
|
if (evn.dataTransfer.items) {
|
|
Array.from(evn.dataTransfer.items).forEach((item) => {
|
|
if (item.kind === "file" && item.type.startsWith("image/")) {
|
|
const file = item.getAsFile();
|
|
const url = window.URL || window.webkitURL;
|
|
const image = document.createElement("img");
|
|
image.src = url.createObjectURL(file);
|
|
|
|
state.addResource(file.name, image, false);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
{passive: false}
|
|
);
|
|
resourceManager.addEventListener(
|
|
"dragover",
|
|
(evn) => {
|
|
evn.preventDefault();
|
|
},
|
|
{passive: false}
|
|
);
|
|
|
|
resourceManager.addEventListener("dragover", (evn) => {
|
|
resourceManager.classList.add("dragging");
|
|
});
|
|
|
|
resourceManager.addEventListener("dragover", (evn) => {
|
|
resourceManager.classList.remove("dragging");
|
|
});
|
|
|
|
state.ctxmenu.uploadButton = uploadButton;
|
|
state.ctxmenu.previewPane = previewPane;
|
|
state.ctxmenu.resourceManager = resourceManager;
|
|
state.ctxmenu.resourceList = resourceList;
|
|
}
|
|
},
|
|
populateContextMenu: (menu, state) => {
|
|
menu.appendChild(state.ctxmenu.snapToGridLabel);
|
|
menu.appendChild(state.ctxmenu.resourceManager);
|
|
},
|
|
shortcut: "U",
|
|
}
|
|
);
|