2022-11-24 21:34:34 -06:00
|
|
|
const stampTool = () =>
|
|
|
|
toolbar.registerTool(
|
|
|
|
"res/icons/file-up.svg",
|
|
|
|
"Stamp Image",
|
|
|
|
(state, opt) => {
|
2022-11-29 18:16:59 -06:00
|
|
|
state.loaded = true;
|
|
|
|
|
2022-11-24 21:34:34 -06:00
|
|
|
// Draw new cursor immediately
|
|
|
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
2022-11-29 14:55:25 -06:00
|
|
|
state.movecb({...mouse.coords.world.pos});
|
2022-11-24 21:34:34 -06:00
|
|
|
|
|
|
|
// Start Listeners
|
2022-11-29 14:55:25 -06:00
|
|
|
mouse.listen.world.onmousemove.on(state.movecb);
|
|
|
|
mouse.listen.world.btn.left.onclick.on(state.drawcb);
|
|
|
|
mouse.listen.world.btn.right.onclick.on(state.cancelcb);
|
2022-11-24 21:34:34 -06:00
|
|
|
|
|
|
|
// For calls from other tools to paste image
|
|
|
|
if (opt && opt.image) {
|
2022-11-25 12:22:16 -06:00
|
|
|
state.addResource(
|
2022-11-24 21:34:34 -06:00
|
|
|
opt.name || "Clipboard",
|
|
|
|
opt.image,
|
2022-12-02 04:22:47 -06:00
|
|
|
opt.temporary === undefined ? true : opt.temporary,
|
|
|
|
false
|
2022-11-24 21:34:34 -06:00
|
|
|
);
|
|
|
|
state.ctxmenu.uploadButton.disabled = true;
|
|
|
|
state.back = opt.back || null;
|
2022-11-24 21:55:16 -06:00
|
|
|
toolbar.lock();
|
2022-11-24 21:34:34 -06:00
|
|
|
} else if (opt) {
|
|
|
|
throw Error(
|
|
|
|
"Pasting from other tools must be in format {image, name?, temporary?, back?}"
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
state.ctxmenu.uploadButton.disabled = "";
|
|
|
|
}
|
|
|
|
},
|
|
|
|
(state, opt) => {
|
2022-11-29 18:16:59 -06:00
|
|
|
state.loaded = false;
|
|
|
|
|
2022-11-24 21:34:34 -06:00
|
|
|
// Clear Listeners
|
2022-11-29 14:55:25 -06:00
|
|
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
|
|
|
mouse.listen.world.btn.left.onclick.clear(state.drawcb);
|
|
|
|
mouse.listen.world.btn.right.onclick.clear(state.cancelcb);
|
2022-11-24 22:46:49 -06:00
|
|
|
|
|
|
|
// Deselect
|
|
|
|
state.selected = null;
|
|
|
|
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
|
|
|
child.classList.remove("selected");
|
|
|
|
});
|
2022-11-24 21:34:34 -06:00
|
|
|
},
|
|
|
|
{
|
|
|
|
init: (state) => {
|
2022-11-29 18:16:59 -06:00
|
|
|
state.loaded = false;
|
2022-11-24 21:34:34 -06:00
|
|
|
state.snapToGrid = true;
|
|
|
|
state.resources = [];
|
|
|
|
state.selected = null;
|
|
|
|
state.back = null;
|
|
|
|
|
2022-11-25 12:22:16 -06:00
|
|
|
state.lastMouseMove = {x: 0, y: 0};
|
|
|
|
|
2022-12-02 04:22:47 -06:00
|
|
|
state.selectResource = (resource, nolock = true) => {
|
|
|
|
if (nolock && state.ctxmenu.uploadButton.disabled) return;
|
|
|
|
|
|
|
|
console.debug(
|
|
|
|
`[stamp] Selecting Resource '${resource && resource.name}'[${
|
|
|
|
resource && resource.id
|
|
|
|
}]`
|
|
|
|
);
|
2022-11-25 10:16:22 -06:00
|
|
|
|
2022-11-25 12:22:16 -06:00
|
|
|
const resourceWrapper = resource && resource.dom.wrapper;
|
2022-11-25 10:16:22 -06:00
|
|
|
|
2022-11-25 12:22:16 -06:00
|
|
|
const wasSelected =
|
|
|
|
resourceWrapper && resourceWrapper.classList.contains("selected");
|
2022-11-25 10:16:22 -06:00
|
|
|
|
|
|
|
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
|
|
|
child.classList.remove("selected");
|
|
|
|
});
|
|
|
|
|
|
|
|
// Select
|
|
|
|
if (!wasSelected) {
|
2022-11-25 12:22:16 -06:00
|
|
|
resourceWrapper && resourceWrapper.classList.add("selected");
|
2022-11-25 10:16:22 -06:00
|
|
|
state.selected = resource;
|
|
|
|
}
|
|
|
|
// If already selected, clear selection
|
|
|
|
else {
|
|
|
|
resourceWrapper.classList.remove("selected");
|
|
|
|
state.selected = null;
|
|
|
|
}
|
2022-11-25 12:22:16 -06:00
|
|
|
|
|
|
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
2022-11-29 18:16:59 -06:00
|
|
|
if (state.loaded) state.movecb(state.lastMouseMove);
|
2022-11-25 10:16:22 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
// Synchronizes resources array with the DOM
|
2022-11-24 21:34:34 -06:00
|
|
|
const syncResources = () => {
|
2022-11-25 10:16:22 -06:00
|
|
|
// Creates DOM elements when needed
|
2022-11-24 21:34:34 -06:00
|
|
|
state.resources.forEach((resource) => {
|
2022-11-25 10:16:22 -06:00
|
|
|
if (
|
|
|
|
!state.ctxmenu.resourceList.querySelector(
|
|
|
|
`#resource-${resource.id}`
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
console.debug(
|
2022-12-02 04:22:47 -06:00
|
|
|
`[stamp] Creating Resource Element [resource-${resource.id}]`
|
2022-11-25 10:16:22 -06:00
|
|
|
);
|
2022-11-24 21:34:34 -06:00
|
|
|
const resourceWrapper = document.createElement("div");
|
|
|
|
resourceWrapper.id = `resource-${resource.id}`;
|
|
|
|
resourceWrapper.classList.add("resource");
|
2022-12-02 11:31:49 -06:00
|
|
|
const resourceTitle = document.createElement("span");
|
|
|
|
resourceTitle.textContent = resource.name;
|
|
|
|
resourceTitle.classList.add("resource-title");
|
|
|
|
resourceWrapper.appendChild(resourceTitle);
|
2022-11-24 21:34:34 -06:00
|
|
|
|
2022-11-25 10:16:22 -06:00
|
|
|
resourceWrapper.addEventListener("click", () =>
|
2022-11-25 12:22:16 -06:00
|
|
|
state.selectResource(resource)
|
2022-11-25 10:16:22 -06:00
|
|
|
);
|
2022-11-24 21:34:34 -06:00
|
|
|
|
|
|
|
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";
|
|
|
|
});
|
|
|
|
|
2022-12-02 11:31:49 -06:00
|
|
|
// Add action buttons
|
|
|
|
const actionArray = document.createElement("div");
|
|
|
|
actionArray.classList.add("actions");
|
|
|
|
|
|
|
|
const renameButton = document.createElement("button");
|
|
|
|
renameButton.addEventListener("click", () => {
|
|
|
|
const name = prompt("Rename your resource:", resource.name);
|
|
|
|
if (name) {
|
|
|
|
resource.name = name;
|
|
|
|
resourceTitle.textContent = name;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
renameButton.title = "Rename Resource";
|
|
|
|
renameButton.appendChild(document.createElement("div"));
|
|
|
|
renameButton.classList.add("rename-btn");
|
|
|
|
|
|
|
|
const trashButton = document.createElement("button");
|
|
|
|
trashButton.addEventListener("click", () => {
|
|
|
|
state.ctxmenu.previewPane.style.display = "none";
|
|
|
|
state.deleteResource(resource.id);
|
|
|
|
});
|
|
|
|
trashButton.title = "Delete Resource";
|
|
|
|
trashButton.appendChild(document.createElement("div"));
|
|
|
|
trashButton.classList.add("delete-btn");
|
|
|
|
|
|
|
|
actionArray.appendChild(renameButton);
|
|
|
|
actionArray.appendChild(trashButton);
|
|
|
|
resourceWrapper.appendChild(actionArray);
|
2022-11-24 21:34:34 -06:00
|
|
|
state.ctxmenu.resourceList.appendChild(resourceWrapper);
|
2022-11-25 10:16:22 -06:00
|
|
|
resource.dom = {wrapper: resourceWrapper};
|
2022-11-24 21:34:34 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-11-25 10:16:22 -06:00
|
|
|
// Removes DOM elements when needed
|
2022-11-24 21:34:34 -06:00
|
|
|
const elements = Array.from(state.ctxmenu.resourceList.children);
|
|
|
|
|
|
|
|
if (elements.length > state.resources.length)
|
|
|
|
elements.forEach((element) => {
|
2022-11-24 22:46:49 -06:00
|
|
|
let remove = true;
|
|
|
|
state.resources.some((resource) => {
|
2022-12-02 04:22:47 -06:00
|
|
|
if (element.id.endsWith(resource.id)) {
|
|
|
|
remove = false;
|
|
|
|
}
|
2022-11-24 22:46:49 -06:00
|
|
|
});
|
|
|
|
|
2022-12-02 04:22:47 -06:00
|
|
|
if (remove) {
|
|
|
|
console.debug(`[stamp] Sync Removing Element [${element.id}]`);
|
|
|
|
state.ctxmenu.resourceList.removeChild(element);
|
|
|
|
}
|
2022-11-24 21:34:34 -06:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-11-25 10:16:22 -06:00
|
|
|
// Adds a image resource (temporary allows only one draw, used for pasting)
|
2022-12-02 04:22:47 -06:00
|
|
|
state.addResource = (name, image, temporary = false, nolock = true) => {
|
2022-11-24 21:34:34 -06:00
|
|
|
const id = guid();
|
|
|
|
const resource = {
|
|
|
|
id,
|
|
|
|
name,
|
|
|
|
image,
|
|
|
|
temporary,
|
|
|
|
};
|
2022-12-02 04:22:47 -06:00
|
|
|
|
|
|
|
console.info(`[stamp] Adding Resource '${name}'[${id}]`);
|
|
|
|
|
2022-11-24 21:34:34 -06:00
|
|
|
state.resources.push(resource);
|
|
|
|
syncResources();
|
2022-11-25 10:16:22 -06:00
|
|
|
|
|
|
|
// Select this resource
|
2022-12-02 04:22:47 -06:00
|
|
|
state.selectResource(resource, nolock);
|
2022-11-25 10:16:22 -06:00
|
|
|
|
2022-11-24 21:34:34 -06:00
|
|
|
return resource;
|
|
|
|
};
|
2022-11-25 10:16:22 -06:00
|
|
|
|
|
|
|
// Deletes a resource (Yes, functionality is here, but we don't have an UI for this yet)
|
|
|
|
// Used for temporary images too
|
2022-11-24 21:34:34 -06:00
|
|
|
state.deleteResource = (id) => {
|
2022-12-02 04:22:47 -06:00
|
|
|
const resourceIndex = state.resources.findIndex((v) => v.id === id);
|
|
|
|
const resource = state.resources[resourceIndex];
|
|
|
|
console.info(
|
|
|
|
`[stamp] Deleting Resource '${resource.name}'[${resource.id}]`
|
|
|
|
);
|
|
|
|
|
|
|
|
state.resources.splice(resourceIndex, 1);
|
2022-11-24 21:34:34 -06:00
|
|
|
|
|
|
|
syncResources();
|
|
|
|
};
|
|
|
|
|
|
|
|
state.movecb = (evn) => {
|
2022-11-29 14:55:25 -06:00
|
|
|
let x = evn.x;
|
|
|
|
let y = evn.y;
|
|
|
|
if (state.snapToGrid) {
|
|
|
|
x += snap(evn.x, true, 64);
|
|
|
|
y += snap(evn.y, true, 64);
|
|
|
|
}
|
2022-11-25 12:22:16 -06:00
|
|
|
|
2022-11-29 14:55:25 -06:00
|
|
|
state.lastMouseMove = evn;
|
2022-11-24 21:34:34 -06:00
|
|
|
|
2022-11-29 14:55:25 -06:00
|
|
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
2022-11-24 21:34:34 -06:00
|
|
|
|
2022-11-29 14:55:25 -06:00
|
|
|
// Draw selected image
|
|
|
|
if (state.selected) {
|
|
|
|
ovCtx.drawImage(state.selected.image, x, y);
|
2022-11-24 21:34:34 -06:00
|
|
|
}
|
2022-11-29 14:55:25 -06:00
|
|
|
|
|
|
|
// 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();
|
2022-11-24 21:34:34 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
state.drawcb = (evn) => {
|
2022-11-29 14:55:25 -06:00
|
|
|
let x = evn.x;
|
|
|
|
let y = evn.y;
|
|
|
|
if (state.snapToGrid) {
|
|
|
|
x += snap(evn.x, true, 64);
|
|
|
|
y += snap(evn.y, true, 64);
|
|
|
|
}
|
2022-11-24 21:34:34 -06:00
|
|
|
|
2022-11-29 14:55:25 -06:00
|
|
|
const resource = state.selected;
|
2022-11-24 21:34:34 -06:00
|
|
|
|
2022-11-29 14:55:25 -06:00
|
|
|
if (resource) {
|
|
|
|
commands.runCommand("drawImage", "Image Stamp", {
|
|
|
|
image: resource.image,
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
});
|
2022-11-24 21:34:34 -06:00
|
|
|
|
2022-12-02 04:22:47 -06:00
|
|
|
if (resource.temporary) {
|
|
|
|
state.deleteResource(resource.id);
|
|
|
|
}
|
2022-11-29 14:55:25 -06:00
|
|
|
}
|
2022-11-24 21:34:34 -06:00
|
|
|
|
2022-11-29 14:55:25 -06:00
|
|
|
if (state.back) {
|
|
|
|
toolbar.unlock();
|
|
|
|
const backfn = state.back;
|
|
|
|
state.back = null;
|
|
|
|
backfn({message: "Returning from stamp", pasted: true});
|
2022-11-24 21:34:34 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
state.cancelcb = (evn) => {
|
2022-11-29 14:55:25 -06:00
|
|
|
state.selectResource(null);
|
|
|
|
|
|
|
|
if (state.back) {
|
|
|
|
toolbar.unlock();
|
|
|
|
const backfn = state.back;
|
|
|
|
state.back = null;
|
|
|
|
backfn({message: "Returning from stamp", pasted: false});
|
2022-11-24 21:34:34 -06:00
|
|
|
}
|
|
|
|
};
|
2022-11-25 10:16:22 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates context menu
|
|
|
|
*/
|
2022-11-24 21:34:34 -06:00
|
|
|
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);
|
|
|
|
|
2022-11-25 10:16:22 -06:00
|
|
|
state.addResource(file.name, image, false);
|
2022-11-24 21:34:34 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2022-11-25 10:16:22 -06:00
|
|
|
},
|
|
|
|
populateContextMenu: (menu, state) => {
|
2022-11-24 21:34:34 -06:00
|
|
|
menu.appendChild(state.ctxmenu.snapToGridLabel);
|
|
|
|
menu.appendChild(state.ctxmenu.resourceManager);
|
|
|
|
},
|
|
|
|
shortcut: "U",
|
|
|
|
}
|
|
|
|
);
|