Color Picker

Color picker for Sketchful

当前为 2020-07-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Color Picker
  3. // @namespace https://greasyfork.org/users/281093
  4. // @match https://sketchful.io/*
  5. // @grant none
  6. // @version 0.6.2
  7. // @author Bell
  8. // @description Color picker for Sketchful
  9. // @run-at document-end
  10. // jshint esversion: 6
  11. // ==/UserScript==
  12.  
  13. let defaultPalettes = [
  14. [
  15. "#ffffff", "#d3d1d2", "#f70f0f", "#ff7200", "#fce700", "#02cb00", "#01fe94", "#05b0ff", "#221ecd", "#a300bd", "#cc7fad", "#fdad88", "#9e5425",
  16. "#514f54", "#a9a7a8", "#ae0b00", "#c84706", "#ec9e06", "#007612", "#049d6f", "#00579d", "#0f0b96", "#6e0083", "#a65673", "#e38a5e", "#5e320d",
  17. "#000000", "#827c80", "#57060c", "#8b2500", "#9e6600", "#003f00", "#00766a", "#003b75", "#0e0151", "#3c0350", "#73314d", "#d1754e", "#421e06"
  18. ],
  19.  
  20. [
  21. "#3a3a3c", "#8e8e93", "#f8f9fa", "#ffadad", "#ffd6a5", "#fdffb6", "#caffbf", "#9bf6ff", "#a0c4ff", "#bdb2ff", "#ffc6ff", "#fdad88", "#9e5425",
  22. "#2c2c2e", "#636366", "#e0e0e0", "#ff7070", "#f3a220", "#f9e079", "#049d6f", "#92ddea", "#6dafe0", "#ab87ff", "#ff87ab", "#e38a5e", "#5e320d",
  23. "#1c1c1e", "#48484a", "#c2c2c2", "#f54d4d", "#dc8700", "#f0c808", "#00766a", "#219bc3", "#548bbc", "#715aff", "#ff5d8f", "#d1754e", "#421e06"
  24. ],
  25.  
  26. [
  27. "#081c15", "#1b4332", "#2d6a4f", "#40916c", "#52b788", "#74c69d", "#95d5b2", "#b7e4c7", "#d8f3dc", "#000000", "#faf9f9", "#ffd6ba", "#fec89a",
  28. "#774936", "#8a5a44", "#9d6b53", "#b07d62", "#c38e70", "#cd9777", "#d69f7e", "#deab90", "#e6b8a2", "#edc4b3", "#ffb5a7", "#fcd5ce", "#f8edeb",
  29. "#cb997e", "#eddcd2", "#fff1e6", "#f0efeb", "#ddbea9", "#a5a58d", "#b7b7a4", "#6d6875", "#b5838d", "#e5989b", "#ffb4a2", "#ffcdb2", "#f9dcc4"
  30. ],
  31.  
  32. [
  33. "#10002b", "#240046", "#3c096c", "#5a189a", "#7b2cbf", "#9d4edd", "#c77dff", "#e0aaff", "#efcefa", "#d4b2d8", "#a88fac", "#826c7f", "#5d4e60",
  34. "#7c6f93", "#886f93", "#a967ad", "#ad6789", "#db81ad", "#ff6c91", "#ff736c", "#ff9e46", "#faa275", "#ff8c61", "#ce6a85", "#985277", "#5c374c",
  35. "#721b65", "#b80d57", "#f8615a", "#ffd868", "#bb596b", "#f96d80", "#ff9a76", "#ffc4a3", "#00e0ff", "#74f9ff", "#a6fff2", "#e8ffe8", "#ffffff"
  36. ],
  37.  
  38. [
  39. "#007f5f", "#2b9348", "#55a630", "#80b918", "#aacc00", "#bfd200", "#d4d700", "#dddf00", "#eeef20", "#ffff3f", "#03045e", "#0077b6", "#00b4d8",
  40. "#ff4800", "#ff5400", "#ff6000", "#ff6d00", "#ff7900", "#ff8500", "#ff9100", "#ff9e00", "#ffaa00", "#ffb600", "#90e0ef", "#caf0f8", "#000000",
  41. "#143642", "#263c41", "#38413f", "#4a473e", "#5c4d3c", "#6f523b", "#815839", "#935e38", "#a56336", "#b76935", "#000000", "#ffffff", "#ffffff"
  42. ]
  43. ];
  44. let palettes = JSON.parse(localStorage.getItem('palettes')) || defaultPalettes;
  45. let paletteIndex = parseInt(localStorage.getItem("paletteIndex")) || 0;
  46. let lockedPalettes = JSON.parse(localStorage.getItem('lockedPalettes')) || [0];
  47. let activeColor = {
  48. node: null,
  49. index: null
  50. };
  51.  
  52. const canvas = document.querySelector("#canvas");
  53. const ctx = canvas.getContext("2d");
  54. const gameTools = document.querySelector("#gameTools");
  55. const chatBox = document.querySelector("#gameChat");
  56. const colorButtons = document.querySelectorAll(".gameToolsColor");
  57. const colorsDiv = document.querySelector("#gameToolsColors");
  58. const colorButton = document.querySelector("#gameToolsColors > div:nth-child(1) > div:nth-child(1)");
  59.  
  60. const colorPickerWrapper = document.createElement("div");
  61. const colorInput = document.createElement("input");
  62. const colorPicker = document.createElement("input");
  63. const inputStyle = `margin: 5px 0; height: 20px; width: 35%; text-align: center; border: none;font-weight: 800; border-radius: 5px; background-color: #CBCBCB;`;
  64. const wrapperStyle = `position: absolute; margin: 5px 35%; height: 20px; width: 37px; border-radius: 5px;`;
  65.  
  66. (function init() {
  67. addPicker();
  68. updatePageStyle();
  69. addObservers();
  70. addListeners();
  71. changePalette();
  72. })();
  73.  
  74. function addPicker() {
  75. colorPicker.type = "color";
  76. colorPicker.setAttribute("style", "opacity: 0; width: 37px; cursor: pointer;");
  77. colorPicker.oninput = updatePicker;
  78. colorPicker.setAttribute("data-toggle", "tooltip");
  79. colorPicker.setAttribute("data-original-title", "Color Picker");
  80. colorPicker.setAttribute("data-placement", "bottom");
  81. colorPicker.title = "Color Picker";
  82. colorPickerWrapper.setAttribute("style", wrapperStyle);
  83. colorPickerWrapper.style.backgroundColor = colorPicker.value;
  84. colorPickerWrapper.appendChild(colorPicker);
  85. gameTools.appendChild(colorPickerWrapper);
  86.  
  87. colorInput.oninput = updateInput;
  88. colorInput.onclick = selectInputText;
  89. colorInput.setAttribute("style", inputStyle);
  90. colorInput.setAttribute("spellcheck", "false");
  91. colorInput.setAttribute("maxlength", "7");
  92. colorInput.value = colorPicker.value;
  93. gameTools.appendChild(colorInput);
  94. addButtons();
  95. }
  96.  
  97. function addObservers() {
  98. const heightObserver = new MutationObserver(adjustChatSize);
  99. const config = {
  100. attributes: true,
  101. };
  102. heightObserver.observe(gameTools, config);
  103. heightObserver.observe(chatBox, config);
  104. }
  105.  
  106. function addListeners() {
  107. canvas.addEventListener("pointerdown", pickCanvasColor, false);
  108. colorsDiv.onpointerdown = editColor;
  109. let saveBtn = document.querySelector("#savePalette");
  110. saveBtn.addEventListener("dragenter", highlight, false);
  111. saveBtn.addEventListener("dragleave", unhighlight, false);
  112. saveBtn.addEventListener("drop", handleDrop, false);
  113. saveBtn.addEventListener("dragover", e => {
  114. e.preventDefault();
  115. }, false);
  116.  
  117. document.addEventListener('keydown', e => {
  118. if (e.altKey && e.shiftKey && !isPaletteLocked(paletteIndex)) {
  119. colorsDiv.style.boxShadow = "0 0 0 2px red";
  120. }
  121. }, false);
  122. document.addEventListener('keyup', e => {
  123. if (e.altKey || e.shiftKey) {
  124. colorsDiv.style.boxShadow = "";
  125. }
  126. }, false);
  127. }
  128.  
  129. function updatePageStyle() {
  130. document.querySelector("#gameToolsSlider").style.top = "77px";
  131. gameTools.style.height = "200px";
  132. }
  133.  
  134. function toggleLock() {
  135. let lockBtn = document.querySelector("#lockButton");
  136. if (lockBtn.getAttribute("state") === "unlocked") {
  137. lockPalette(lockBtn);
  138. } else {
  139. unlockPalette(lockBtn);
  140. }
  141. updateLock();
  142. }
  143.  
  144. function lockPalette() {
  145. lockedPalettes.push(paletteIndex);
  146. localStorage.setItem("lockedPalettes", JSON.stringify(lockedPalettes));
  147. }
  148.  
  149. function unlockPalette() {
  150. let index = lockedPalettes.indexOf(paletteIndex);
  151. if (index < 0) return;
  152. lockedPalettes.splice(index, 1);
  153. localStorage.setItem("lockedPalettes", JSON.stringify(lockedPalettes));
  154. }
  155.  
  156. function updateLock() {
  157. let lockBtn = document.querySelector("#lockButton");
  158. if (isPaletteLocked(paletteIndex)) {
  159. lockBtn.classList.remove("fa-unlock-alt");
  160. lockBtn.classList.add("fa-lock");
  161. lockBtn.setAttribute("state", "locked");
  162. colorsDiv.style.boxShadow = "";
  163. } else {
  164. lockBtn.classList.add("fa-unlock-alt");
  165. lockBtn.classList.remove("fa-lock");
  166. lockBtn.setAttribute("state", "unlocked");
  167. }
  168. resetActiveColor();
  169. }
  170.  
  171. function addButtons() {
  172. let prevPaletteBtn = document.createElement("button");
  173. let saveColorBtn = document.createElement("button");
  174. let nextPaletteBtn = document.createElement("button");
  175. let lockBtn = document.createElement("button");
  176. const saveTooltip = "Save Color<br>Hold <strong>shift</strong> to save the current palette.";
  177. const lockTooltip = "Lock Current Palette";
  178.  
  179. addButton(prevPaletteBtn, "arrow-left", "5px 5px 5px 45px;");
  180. addButton(saveColorBtn, "save", "5px 5px 5px 75px;", saveTooltip, "savePalette");
  181. addButton(nextPaletteBtn, "arrow-right", "5px 5px 5px 105px;");
  182. addButton(lockBtn, "unlock-alt", "5px 5px 5px 135px;", lockTooltip, "lockButton");
  183. lockBtn.setAttribute("state", "unlocked");
  184.  
  185. prevPaletteBtn.addEventListener("click", prevPalette, false);
  186. saveColorBtn.addEventListener("click", saveColor, false);
  187. nextPaletteBtn.addEventListener("click", nextPalette, false);
  188. lockBtn.addEventListener("click", toggleLock, false);
  189. }
  190.  
  191. function nextPalette() {
  192. paletteIndex = paletteIndex < (palettes.length - 1) ? paletteIndex + 1 : 0;
  193. localStorage.setItem("paletteIndex", paletteIndex);
  194. changePalette();
  195. }
  196.  
  197. function prevPalette() {
  198. paletteIndex = paletteIndex > 0 ? paletteIndex - 1 : palettes.length - 1;
  199. localStorage.setItem("paletteIndex", paletteIndex);
  200. changePalette();
  201. }
  202.  
  203. function saveColor(e) {
  204. if (e.shiftKey) {
  205. downloadPalettes();
  206. return;
  207. }
  208. let currentPalette = palettes[paletteIndex];
  209. if (activeColor.index) {
  210. currentPalette[activeColor.index] = colorPicker.value;
  211. } else {
  212. addColor(colorPicker.value);
  213. }
  214. changePalette();
  215. savePalettes();
  216. }
  217.  
  218. function addColor(color) {
  219. if (palettes[paletteIndex].length > 38 || isPaletteLocked(paletteIndex)) {
  220. palettes.push([]);
  221. paletteIndex = palettes.length - 1;
  222. }
  223. palettes[paletteIndex].push(color);
  224. }
  225.  
  226. function rgbToHex(rgb) {
  227. let regEx = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/;
  228. let [, r, g, b] = regEx.exec(rgb);
  229.  
  230. function hex(x) {
  231. return ("0" + parseInt(x).toString(16)).slice(-2);
  232. }
  233.  
  234. return `#${hex(r)}${hex(g)}${hex(b)}`;
  235. }
  236.  
  237. function savePalettes() {
  238. localStorage.setItem("palettes", JSON.stringify(palettes));
  239. }
  240.  
  241. function downloadPalettes() {
  242. let formattedPaletteData = JSON.stringify(palettes[paletteIndex]).replace(/\]\,/g, "],\n\n");
  243. download("palette.txt", formattedPaletteData);
  244. }
  245.  
  246. function download(filename, text) {
  247. let pom = document.createElement('a');
  248. pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  249. pom.setAttribute('download', filename);
  250.  
  251. if (document.createEvent) {
  252. let event = document.createEvent('MouseEvents');
  253. event.initEvent('click', true, true);
  254. pom.dispatchEvent(event);
  255. } else {
  256. pom.click();
  257. }
  258. }
  259.  
  260. function isPaletteLocked(index) {
  261. return lockedPalettes.includes(index);
  262. }
  263.  
  264. function updateColorInputs(colorValue) {
  265. colorPickerWrapper.style.backgroundColor = colorValue;
  266. colorPicker.value = colorValue;
  267. colorInput.value = colorValue;
  268. }
  269.  
  270. function editColor(event) {
  271. if (!event.target.classList.contains("gameToolsColor")) return;
  272. if (!event.shiftKey) updateColorInputs(rgbToHex(event.target.style.backgroundColor));
  273.  
  274. if (isPaletteLocked(paletteIndex)) return;
  275. let color = {
  276. node: event.target,
  277. index: Array.prototype.indexOf.call(colorButtons, event.target)
  278. };
  279.  
  280. if (event.altKey && event.shiftKey) {
  281. deletePalette(paletteIndex);
  282. } else if (event.altKey && color.index >= 0) {
  283. let palette = palettes[paletteIndex];
  284. palette.splice(color.index, 1);
  285. changePalette();
  286. if (isPaletteEmpty(palette)) deletePalette(paletteIndex);
  287. } else if (event.shiftKey && color.index >= 0) {
  288. setActiveColor(color);
  289. }
  290. }
  291.  
  292. function deletePalette(index) {
  293. if (palettes.length < 2) return;
  294. palettes.splice(index, 1);
  295. lockedPalettes = lockedPalettes.map(lockedIndex => {
  296. return lockedIndex > index ? lockedIndex - 1 : lockedIndex;
  297. });
  298. localStorage.setItem("lockedPalettes", JSON.stringify(lockedPalettes));
  299. prevPalette();
  300. savePalettes();
  301. updateLock();
  302. }
  303.  
  304. function changePalette() {
  305. if (paletteIndex < 0 || paletteIndex >= palettes.length) {
  306. paletteIndex = 0;
  307. localStorage.setItem("paletteIndex", paletteIndex);
  308. }
  309. colorButtons.forEach((button, idx) => {
  310. button.style.backgroundColor = palettes[paletteIndex][idx] || "#fff";
  311. });
  312. updateLock();
  313. }
  314.  
  315. function isPaletteEmpty(palette) {
  316. if (!palette) return true;
  317. let empty = true;
  318. for (let color of palette) {
  319. if (color) {
  320. empty = false;
  321. break;
  322. }
  323. }
  324. return empty;
  325. }
  326.  
  327. function setActiveColor(color) {
  328. resetActiveColor();
  329. activeColor = color;
  330. activeColor.node.style.border = "solid 2px red";
  331. }
  332.  
  333. function resetActiveColor() {
  334. if (activeColor.node) {
  335. activeColor.node.style.border = "";
  336. activeColor = {
  337. node: null,
  338. index: null
  339. };
  340. }
  341. }
  342.  
  343. function addButton(button, icon, pos, tooltip = "", id = "") {
  344. let buttonStyle = `margin: ${pos}; position: absolute; height: 20px; border: none; background-color: #CBCBCB; border-radius: 5px;`;
  345. button.setAttribute("style", buttonStyle);
  346. button.setAttribute("class", `fas fa-${icon}`);
  347. tooltip && button.setAttribute("data-toggle", "tooltip");
  348. tooltip && button.setAttribute("data-original-title", tooltip);
  349. button.setAttribute("data-placement", "bottom");
  350. button.title = tooltip;
  351. button.id = id;
  352. gameTools.appendChild(button);
  353. }
  354.  
  355. function updatePicker(event) {
  356. let color = event.target.value;
  357. colorPickerWrapper.style.backgroundColor = color;
  358. colorInput.value = color;
  359. setColor(color);
  360. }
  361.  
  362. function updateInput(event) {
  363. let hexFound = /([0-9A-Fa-f]{3}){1,2}/.exec(event.target.value);
  364. if (!hexFound) return;
  365. let color = "#" + hexFound[0];
  366. colorPickerWrapper.style.backgroundColor = color;
  367. colorPicker.value = color;
  368. setColor(color);
  369. }
  370.  
  371. function setColor(color) {
  372. let prevColor = colorButton.style.backgroundColor;
  373. colorButton.style.backgroundColor = color;
  374. colorButton.dispatchEvent(new Event("pointerdown"));
  375. colorButton.style.backgroundColor = prevColor;
  376. }
  377.  
  378. function selectInputText() {
  379. colorInput.select();
  380. }
  381.  
  382. function pickCanvasColor(event) {
  383. if (!event.altKey) return;
  384. event.preventDefault();
  385. event.stopImmediatePropagation();
  386. let pos = getPos(event);
  387. let [r, g, b, a] = ctx.getImageData(pos.x, pos.y, 1, 1).data;
  388. let color = `rgb(${r}, ${g}, ${b})`;
  389. updateColorInputs(rgbToHex(color));
  390. setColor(color);
  391. }
  392.  
  393. function getPos(event) {
  394. let canvasRect = canvas.getBoundingClientRect();
  395. let canvasScale = canvas.width / canvasRect.width;
  396. return {
  397. x: (event.clientX - canvasRect.left) * canvasScale,
  398. y: (event.clientY - canvasRect.top) * canvasScale
  399. };
  400. }
  401.  
  402. function handleDrop(e) {
  403. e.preventDefault();
  404. colorsDiv.style.filter = "";
  405. handleFiles(e.dataTransfer.files);
  406. }
  407.  
  408. function handleFiles(files) {
  409. if (!files) return;
  410. files = [...files];
  411. files.forEach(file => {
  412. let reader = new FileReader();
  413. reader.readAsText(file);
  414. reader.onload = loadPalette;
  415. });
  416. }
  417.  
  418. function addPalette(palette) {
  419. if (palettes[paletteIndex].length + palette.length < 40 && !isPaletteLocked(paletteIndex)) {
  420. palettes[paletteIndex] = palettes[paletteIndex].concat(palette);
  421. } else {
  422. palettes.push(palette);
  423. paletteIndex = palettes.length - 1;
  424. localStorage.setItem("paletteIndex", paletteIndex);
  425. }
  426. resetPaletteState();
  427. }
  428.  
  429. function loadPalette(event) {
  430. const loadedString = event.target.result;
  431. const coolorRegex = /CSV \*\/\s*(\S+)/;
  432. const arrayRegex = /\[\[?\s*([^\]]+)/g;
  433. const hexRegex = /#([0-9A-Fa-f]{3}){1,2}/g;
  434. let coolorMatch = loadedString.match(coolorRegex);
  435. let arrayMatch = loadedString.match(arrayRegex);
  436. if (coolorMatch) {
  437. let palette = coolorMatch[1].split(",").map(color => `#${color}`);
  438. addPalette(palette);
  439. return;
  440. } else if (arrayMatch) {
  441. let palettes = arrayMatch.map(palette => palette.match(hexRegex));
  442. palettes.forEach(palette => addPalette(palette));
  443. } else {
  444. let hexCodesFound = [...new Set(loadedString.match(hexRegex))];
  445. console.log("Hex codes found: ", hexCodesFound);
  446. hexCodesFound.forEach(code => addColor(code));
  447. changePalette();
  448. savePalettes();
  449. }
  450. }
  451.  
  452. function replacePalettes(newPalettes) {
  453. palettes = newPalettes;
  454. paletteIndex = 0;
  455. lockedPalettes = [];
  456. resetPaletteState();
  457. }
  458.  
  459. function resetPaletteState() {
  460. updateLock();
  461. changePalette();
  462. savePalettes();
  463. }
  464.  
  465. function highlight(e) {
  466. e.preventDefault();
  467. colorsDiv.style.filter = "brightness(0.6)";
  468. }
  469.  
  470. function unhighlight(e) {
  471. e.preventDefault();
  472. colorsDiv.style.filter = "";
  473. }
  474.  
  475. function isDrawing() {
  476. return document.querySelector("#gameTools").style.display !== "none";
  477. }
  478.  
  479. function adjustChatSize() {
  480. chatBox.style.height = isDrawing() ? "calc(100% - 200px)" : "calc(100% - 180px)";
  481. }