// ==UserScript==
// @name Color Picker
// @namespace https://greasyfork.org/users/281093
// @match https://sketchful.io/*
// @grant none
// @version 0.7.3
// @author Bell
// @description Color picker for Sketchful
// @require https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js
// @run-at document-end
// jshint esversion: 6
// ==/UserScript==
const 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;
colorPicker.setAttribute('data-toggle', 'tooltip');
colorPicker.setAttribute('data-original-title', 'Color Picker');
colorPicker.setAttribute('data-placement', 'bottom');
colorPicker.title = 'Color Picker';
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);
}
let pickingColor = false;
function pickerIconOn(e) {
if (e.code !== 'AltLeft') return;
canvas.style.cursor = 'crosshair';
pickingColor = true;
e.preventDefault();
}
function pickerIconOff(e) {
if (e.code !== 'AltLeft' || !pickingColor) return;
pickingColor = false;
regenerateCursor();
e.preventDefault();
}
function regenerateCursor() {
const selectedTool = document.querySelector('.gameToolsSelected');
selectedTool.id === 'gameToolsDraw' ? selectedTool.nextSibling.click() :
selectedTool.previousSibling.click();
selectedTool.click();
}
function addListeners() {
canvas.addEventListener('pointerdown', pickCanvasColor, false);
document.addEventListener('keydown', pickerIconOn, true);
document.addEventListener('keyup', pickerIconOff, true);
const 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);
document.addEventListener('paste', e => {
if (document.activeElement.tagName === "INPUT") return;
const paste = (e.clipboardData || window.clipboardData).getData('text');
const coolorRegex = /coolors\.co\/([a-f0-9-]+)/;
const match = coolorRegex.exec(paste);
if (match)
addHexFromString(match[1]);
}, false);
colorsDiv.addEventListener('pointerenter', () => {
colorsDiv.addEventListener('pointerdown', editColor, true);
});
colorsDiv.addEventListener('pointerleave', () => {
colorsDiv.removeEventListener('pointerdown', editColor, true);
});
document.addEventListener('keydown', e=> {
if (!e.ctrlKey || e.code !== "KeyZ") return;
e.preventDefault();
});
}
function updatePageStyle() {
document.querySelector('#gameToolsSlider').style.top = '77px';
gameTools.style.height = '200px';
}
function toggleLock() {
const 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() {
const index = lockedPalettes.indexOf(paletteIndex);
if (index < 0) return;
lockedPalettes.splice(index, 1);
localStorage.setItem('lockedPalettes', JSON.stringify(lockedPalettes));
}
function updateLock() {
const 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() {
const prevPaletteBtn = document.createElement('button');
const saveColorBtn = document.createElement('button');
const nextPaletteBtn = document.createElement('button');
const lockBtn = document.createElement('button');
const saveTooltip = 'Save Color<br>Hold <strong>shift</strong> to save the current palette.';
const lockTooltip = 'Lock Current Palette';
addButton(prevPaletteBtn, 'arrow-left', '5px 5px 5px 45px;');
addButton(saveColorBtn, 'save', '5px 5px 5px 75px;', saveTooltip, 'savePalette');
addButton(nextPaletteBtn, 'arrow-right', '5px 5px 5px 105px;');
addButton(lockBtn, 'unlock-alt', '5px 5px 5px 135px;', lockTooltip, '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;
}
const 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) {
const regEx = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/;
const [, 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() {
const formattedPaletteData = JSON.stringify(palettes[paletteIndex]).replace(/\],/g, '],\n\n');
download('palette.txt', formattedPaletteData);
}
function download(filename, text) {
const pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
if (document.createEvent) {
const 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;
const 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) {
const palette = palettes[paletteIndex];
palette.splice(color.index, 1);
changePalette();
if (isPaletteEmpty(palette)) deletePalette(paletteIndex);
savePalettes();
}
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 (const 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, tooltip = '', id = '') {
const 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}`);
tooltip && button.setAttribute('data-toggle', 'tooltip');
tooltip && button.setAttribute('data-original-title', tooltip);
button.setAttribute('data-placement', 'bottom');
button.title = tooltip;
button.id = id;
gameTools.appendChild(button);
}
function updatePicker(event) {
const color = event.target.value;
colorPickerWrapper.style.backgroundColor = color;
colorInput.value = color;
setColor(color);
}
function updateInput(event) {
const hexFound = /([0-9A-Fa-f]{3}){1,2}/.exec(event.target.value);
if (!hexFound) return;
const color = '#' + hexFound[0];
colorPickerWrapper.style.backgroundColor = color;
colorPicker.value = color;
setColor(color);
}
function setColor(color) {
const 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;
event.preventDefault();
event.stopImmediatePropagation();
const pos = getPos(event);
const [r, g, b] = ctx.getImageData(pos.x, pos.y, 1, 1).data;
const color = `rgb(${r}, ${g}, ${b})`;
updateColorInputs(rgbToHex(color));
setColor(color);
}
function getPos(event) {
const canvasRect = canvas.getBoundingClientRect();
const 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 => {
const 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;
const coolorMatch = loadedString.match(coolorRegex);
const arrayMatch = loadedString.match(arrayRegex);
if (coolorMatch) {
const palette = coolorMatch[1].split(',').map(color => `#${color}`);
addPalette(palette);
return;
}
else if (arrayMatch) {
const paletteMatch = arrayMatch.map(palette => palette.match(hexRegex));
paletteMatch.forEach(palette => addPalette(palette));
}
else {
addHexFromString(loadedString);
}
}
function addHexFromString(string) {
const hexRegex = /([0-9A-Fa-f]{3}){1,2}/g;
const hexCodesFound = [...new Set(string.match(hexRegex))];
console.log('Hex codes found: ', hexCodesFound);
const codes = hexCodesFound.map(code => '#' + code);
codes.forEach(code => addColor(code));
changePalette();
savePalettes();
}
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)';
}