// ==UserScript==
// @name Color Picker
// @namespace https://greasyfork.org/users/281093
// @match https://sketchful.io/*
// @grant none
// @version 0.6.1
// @author Bell
// @description Color picker for Sketchful
// @run-at document-end
// jshint esversion: 6
// ==/UserScript==
let defaultPalettes = [
[
"#ffffff", "#d3d1d2", "#f70f0f", "#ff7200", "#fce700", "#02cb00", "#01fe94", "#05b0ff", "#221ecd", "#a300bd", "#cc7fad", "#fdad88", "#9e5425",
"#514f54", "#a9a7a8", "#ae0b00", "#c84706", "#ec9e06", "#007612", "#049d6f", "#00579d", "#0f0b96", "#6e0083", "#a65673", "#e38a5e", "#5e320d",
"#000000", "#827c80", "#57060c", "#8b2500", "#9e6600", "#003f00", "#00766a", "#003b75", "#0e0151", "#3c0350", "#73314d", "#d1754e", "#421e06"
],
[
"#3a3a3c", "#8e8e93", "#f8f9fa", "#ffadad", "#ffd6a5", "#fdffb6", "#caffbf", "#9bf6ff", "#a0c4ff", "#bdb2ff", "#ffc6ff", "#fdad88", "#9e5425",
"#2c2c2e", "#636366", "#e0e0e0", "#ff7070", "#f3a220", "#f9e079", "#049d6f", "#92ddea", "#6dafe0", "#ab87ff", "#ff87ab", "#e38a5e", "#5e320d",
"#1c1c1e", "#48484a", "#c2c2c2", "#f54d4d", "#dc8700", "#f0c808", "#00766a", "#219bc3", "#548bbc", "#715aff", "#ff5d8f", "#d1754e", "#421e06"
],
[
"#081c15", "#1b4332", "#2d6a4f", "#40916c", "#52b788", "#74c69d", "#95d5b2", "#b7e4c7", "#d8f3dc", "#000000", "#faf9f9", "#ffd6ba", "#fec89a",
"#774936", "#8a5a44", "#9d6b53", "#b07d62", "#c38e70", "#cd9777", "#d69f7e", "#deab90", "#e6b8a2", "#edc4b3", "#ffb5a7", "#fcd5ce", "#f8edeb",
"#cb997e", "#eddcd2", "#fff1e6", "#f0efeb", "#ddbea9", "#a5a58d", "#b7b7a4", "#6d6875", "#b5838d", "#e5989b", "#ffb4a2", "#ffcdb2", "#f9dcc4"
],
[
"#10002b", "#240046", "#3c096c", "#5a189a", "#7b2cbf", "#9d4edd", "#c77dff", "#e0aaff", "#efcefa", "#d4b2d8", "#a88fac", "#826c7f", "#5d4e60",
"#7c6f93", "#886f93", "#a967ad", "#ad6789", "#db81ad", "#ff6c91", "#ff736c", "#ff9e46", "#faa275", "#ff8c61", "#ce6a85", "#985277", "#5c374c",
"#721b65", "#b80d57", "#f8615a", "#ffd868", "#bb596b", "#f96d80", "#ff9a76", "#ffc4a3", "#00e0ff", "#74f9ff", "#a6fff2", "#e8ffe8", "#ffffff"
],
[
"#007f5f", "#2b9348", "#55a630", "#80b918", "#aacc00", "#bfd200", "#d4d700", "#dddf00", "#eeef20", "#ffff3f", "#03045e", "#0077b6", "#00b4d8",
"#ff4800", "#ff5400", "#ff6000", "#ff6d00", "#ff7900", "#ff8500", "#ff9100", "#ff9e00", "#ffaa00", "#ffb600", "#90e0ef", "#caf0f8", "#000000",
"#143642", "#263c41", "#38413f", "#4a473e", "#5c4d3c", "#6f523b", "#815839", "#935e38", "#a56336", "#b76935", "#000000", "#ffffff", "#ffffff"
]
];
let palettes = JSON.parse(localStorage.getItem('palettes')) || defaultPalettes;
let paletteIndex = parseInt(localStorage.getItem("paletteIndex")) || 0;
let lockedPalettes = JSON.parse(localStorage.getItem('lockedPalettes')) || [0];
let activeColor = {
node: null,
index: null
};
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
const gameTools = document.querySelector("#gameTools");
const chatBox = document.querySelector("#gameChat");
const colorButtons = document.querySelectorAll(".gameToolsColor");
const colorsDiv = document.querySelector("#gameToolsColors");
const colorButton = document.querySelector("#gameToolsColors > div:nth-child(1) > div:nth-child(1)");
const colorPickerWrapper = document.createElement("div");
const colorInput = document.createElement("input");
const colorPicker = document.createElement("input");
const inputStyle = `margin: 5px 0; height: 20px; width: 35%; text-align: center; border: none;font-weight: 800; border-radius: 5px; background-color: #CBCBCB;`;
const wrapperStyle = `position: absolute; margin: 5px 35%; height: 20px; width: 37px; border-radius: 5px;`;
(function init() {
addPicker();
updatePageStyle();
addObservers();
addListeners();
changePalette();
})();
function addPicker() {
colorPicker.type = "color";
colorPicker.setAttribute("style", "opacity: 0; width: 37px; cursor: pointer;");
colorPicker.oninput = updatePicker;
colorPickerWrapper.setAttribute("style", wrapperStyle);
colorPickerWrapper.style.backgroundColor = colorPicker.value;
colorPickerWrapper.appendChild(colorPicker);
gameTools.appendChild(colorPickerWrapper);
colorInput.oninput = updateInput;
colorInput.onclick = selectInputText;
colorInput.setAttribute("style", inputStyle);
colorInput.setAttribute("spellcheck", "false");
colorInput.setAttribute("maxlength", "7");
colorInput.value = colorPicker.value;
gameTools.appendChild(colorInput);
addButtons();
}
function addObservers() {
const heightObserver = new MutationObserver(adjustChatSize);
const config = {
attributes: true,
};
heightObserver.observe(gameTools, config);
heightObserver.observe(chatBox, config);
}
function addListeners() {
canvas.addEventListener("pointerdown", pickCanvasColor, false);
colorsDiv.onpointerdown = editColor;
let saveBtn = document.querySelector("#savePalette");
saveBtn.addEventListener("dragenter", highlight, false);
saveBtn.addEventListener("dragleave", unhighlight, false);
saveBtn.addEventListener("drop", handleDrop, false);
saveBtn.addEventListener("dragover", e => {
e.preventDefault();
}, false);
document.addEventListener('keydown', e => {
if (e.altKey && e.shiftKey && !isPaletteLocked(paletteIndex)) {
colorsDiv.style.boxShadow = "0 0 0 2px red";
}
}, false);
document.addEventListener('keyup', e => {
if (e.altKey || e.shiftKey) {
colorsDiv.style.boxShadow = "";
}
}, false);
}
function updatePageStyle() {
document.querySelector("#gameToolsSlider").style.top = "77px";
gameTools.style.height = "200px";
}
function toggleLock() {
let lockBtn = document.querySelector("#lockButton");
if (lockBtn.getAttribute("state") === "unlocked") {
lockPalette(lockBtn);
} else {
unlockPalette(lockBtn);
}
updateLock();
}
function lockPalette() {
lockedPalettes.push(paletteIndex);
localStorage.setItem("lockedPalettes", JSON.stringify(lockedPalettes));
}
function unlockPalette() {
let index = lockedPalettes.indexOf(paletteIndex);
if (index < 0) return;
lockedPalettes.splice(index, 1);
localStorage.setItem("lockedPalettes", JSON.stringify(lockedPalettes));
}
function updateLock() {
let lockBtn = document.querySelector("#lockButton");
if (isPaletteLocked(paletteIndex)) {
lockBtn.classList.remove("fa-unlock-alt");
lockBtn.classList.add("fa-lock");
lockBtn.setAttribute("state", "locked");
colorsDiv.style.boxShadow = "";
} else {
lockBtn.classList.add("fa-unlock-alt");
lockBtn.classList.remove("fa-lock");
lockBtn.setAttribute("state", "unlocked");
}
resetActiveColor();
}
function addButtons() {
let prevPaletteBtn = document.createElement("button");
let saveColorBtn = document.createElement("button");
let nextPaletteBtn = document.createElement("button");
let lockBtn = document.createElement("button");
addButton(prevPaletteBtn, "arrow-left", "5px 5px 5px 45px;");
addButton(saveColorBtn, "save", "5px 5px 5px 75px;", "savePalette");
addButton(nextPaletteBtn, "arrow-right", "5px 5px 5px 105px;");
addButton(lockBtn, "unlock-alt", "5px 5px 5px 135px;", "lockButton");
lockBtn.setAttribute("state", "unlocked");
prevPaletteBtn.addEventListener("click", prevPalette, false);
saveColorBtn.addEventListener("click", saveColor, false);
nextPaletteBtn.addEventListener("click", nextPalette, false);
lockBtn.addEventListener("click", toggleLock, false);
}
function nextPalette() {
paletteIndex = paletteIndex < (palettes.length - 1) ? paletteIndex + 1 : 0;
localStorage.setItem("paletteIndex", paletteIndex);
changePalette();
}
function prevPalette() {
paletteIndex = paletteIndex > 0 ? paletteIndex - 1 : palettes.length - 1;
localStorage.setItem("paletteIndex", paletteIndex);
changePalette();
}
function saveColor(e) {
if (e.shiftKey) {
downloadPalettes();
return;
}
let currentPalette = palettes[paletteIndex];
if (activeColor.index) {
currentPalette[activeColor.index] = colorPicker.value;
} else {
addColor(colorPicker.value);
}
changePalette();
savePalettes();
}
function addColor(color) {
if (palettes[paletteIndex].length > 38 || isPaletteLocked(paletteIndex)) {
palettes.push([]);
paletteIndex = palettes.length - 1;
}
palettes[paletteIndex].push(color);
}
function rgbToHex(rgb) {
let regEx = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/;
let [, r, g, b] = regEx.exec(rgb);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return `#${hex(r)}${hex(g)}${hex(b)}`;
}
function savePalettes() {
localStorage.setItem("palettes", JSON.stringify(palettes));
}
function downloadPalettes() {
let formattedPaletteData = JSON.stringify(palettes[paletteIndex]).replace(/\]\,/g, "],\n\n");
download("palette.txt", formattedPaletteData);
}
function download(filename, text) {
let pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
if (document.createEvent) {
let event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
pom.dispatchEvent(event);
} else {
pom.click();
}
}
function isPaletteLocked(index) {
return lockedPalettes.includes(index);
}
function updateColorInputs(colorValue) {
colorPickerWrapper.style.backgroundColor = colorValue;
colorPicker.value = colorValue;
colorInput.value = colorValue;
}
function editColor(event) {
if (!event.target.classList.contains("gameToolsColor")) return;
if (!event.shiftKey) updateColorInputs(rgbToHex(event.target.style.backgroundColor));
if (isPaletteLocked(paletteIndex)) return;
let color = {
node: event.target,
index: Array.prototype.indexOf.call(colorButtons, event.target)
};
if (event.altKey && event.shiftKey) {
deletePalette(paletteIndex);
} else if (event.altKey && color.index >= 0) {
let palette = palettes[paletteIndex];
palette.splice(color.index, 1);
changePalette();
if (isPaletteEmpty(palette)) deletePalette(paletteIndex);
} else if (event.shiftKey && color.index >= 0) {
setActiveColor(color);
}
}
function deletePalette(index) {
if (palettes.length < 2) return;
palettes.splice(index, 1);
lockedPalettes = lockedPalettes.map(lockedIndex => {
return lockedIndex > index ? lockedIndex - 1 : lockedIndex;
});
localStorage.setItem("lockedPalettes", JSON.stringify(lockedPalettes));
prevPalette();
savePalettes();
updateLock();
}
function changePalette() {
if (paletteIndex < 0 || paletteIndex >= palettes.length) {
paletteIndex = 0;
localStorage.setItem("paletteIndex", paletteIndex);
}
colorButtons.forEach((button, idx) => {
button.style.backgroundColor = palettes[paletteIndex][idx] || "#fff";
});
updateLock();
}
function isPaletteEmpty(palette) {
if (!palette) return true;
let empty = true;
for (let color of palette) {
if (color) {
empty = false;
break;
}
}
return empty;
}
function setActiveColor(color) {
resetActiveColor();
activeColor = color;
activeColor.node.style.border = "solid 2px red";
}
function resetActiveColor() {
if (activeColor.node) {
activeColor.node.style.border = "";
activeColor = {
node: null,
index: null
};
}
}
function addButton(button, icon, pos, id = "") {
let buttonStyle = `margin: ${pos}; position: absolute; height: 20px; border: none; background-color: #CBCBCB; border-radius: 5px;`;
button.setAttribute("style", buttonStyle);
button.setAttribute("class", `fas fa-${icon}`);
button.id = id;
gameTools.appendChild(button);
}
function updatePicker(event) {
let color = event.target.value;
colorPickerWrapper.style.backgroundColor = color;
colorInput.value = color;
setColor(color);
}
function updateInput(event) {
let hexFound = /([0-9A-Fa-f]{3}){1,2}/.exec(event.target.value);
if (!hexFound) return;
let color = "#" + hexFound[0];
colorPickerWrapper.style.backgroundColor = color;
colorPicker.value = color;
setColor(color);
}
function setColor(color) {
let prevColor = colorButton.style.backgroundColor;
colorButton.style.backgroundColor = color;
colorButton.dispatchEvent(new Event("pointerdown"));
colorButton.style.backgroundColor = prevColor;
}
function selectInputText() {
colorInput.select();
}
function pickCanvasColor(event) {
if (!event.altKey) return;
console.log("yes");
event.preventDefault();
event.stopImmediatePropagation();
let pos = getPos(event);
let [r, g, b, a] = ctx.getImageData(pos.x, pos.y, 1, 1).data;
let color = `rgb(${r}, ${g}, ${b})`;
updateColorInputs(rgbToHex(color));
setColor(color);
}
function getPos(event) {
let canvasRect = canvas.getBoundingClientRect();
let canvasScale = canvas.width / canvasRect.width;
return {
x: (event.clientX - canvasRect.left) * canvasScale,
y: (event.clientY - canvasRect.top) * canvasScale
};
}
function handleDrop(e) {
e.preventDefault();
colorsDiv.style.filter = "";
handleFiles(e.dataTransfer.files);
}
function handleFiles(files) {
if (!files) return;
files = [...files];
files.forEach(file => {
let reader = new FileReader();
reader.readAsText(file);
reader.onload = loadPalette;
});
}
function addPalette(palette) {
if (palettes[paletteIndex].length + palette.length < 40 && !isPaletteLocked(paletteIndex)) {
palettes[paletteIndex] = palettes[paletteIndex].concat(palette);
} else {
palettes.push(palette);
paletteIndex = palettes.length - 1;
localStorage.setItem("paletteIndex", paletteIndex);
}
resetPaletteState();
}
function loadPalette(event) {
const loadedString = event.target.result;
const coolorRegex = /CSV \*\/\s*(\S+)/;
const arrayRegex = /\[\[?\s*([^\]]+)/g;
const hexRegex = /#([0-9A-Fa-f]{3}){1,2}/g;
let coolorMatch = loadedString.match(coolorRegex);
let arrayMatch = loadedString.match(arrayRegex);
if (coolorMatch) {
let palette = coolorMatch[1].split(",").map(color => `#${color}`);
addPalette(palette);
return;
} else if (arrayMatch) {
let palettes = arrayMatch.map(palette => palette.match(hexRegex));
palettes.forEach(palette => addPalette(palette));
} else {
let hexCodesFound = [...new Set(loadedString.match(hexRegex))];
console.log("Hex codes found: ", hexCodesFound);
hexCodesFound.forEach(code => addColor(code));
changePalette();
savePalettes();
}
}
function replacePalettes(newPalettes) {
palettes = newPalettes;
paletteIndex = 0;
lockedPalettes = [];
resetPaletteState();
}
function resetPaletteState() {
updateLock();
changePalette();
savePalettes();
}
function highlight(e) {
e.preventDefault();
colorsDiv.style.filter = "brightness(0.6)";
}
function unhighlight(e) {
e.preventDefault();
colorsDiv.style.filter = "";
}
function isDrawing() {
return document.querySelector("#gameTools").style.display !== "none";
}
function adjustChatSize() {
chatBox.style.height = isDrawing() ? "calc(100% - 200px)" : "calc(100% - 180px)";
}