您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds keybindings to all mapping function in https://inkarnate.com/
// ==UserScript== // @name Keybindings for Inkarnate // @description Adds keybindings to all mapping function in https://inkarnate.com/ // @namespace azzurite // @version 1.2.0 // @locale en // @author azzurite // @match https://inkarnate.com/maps // @run-at document-idle // @noframes // @grant none // ==/UserScript== (function() { 'use strict'; const keybinds = { sculpt: { mode: { add: 'a', subtract: 's' }, shape: { circle: 'c', block: 'b', hex: 'h' }, size: { more: 'ArrowRight', less: 'ArrowLeft', moreMore: 'ArrowUp', lessLess: 'ArrowDown' } }, brush: { chooseTexture: { open: 'q' }, layer: { fg: 'f', bg: 'g' }, shape: { circle: 'c', block: 'b', hex: 'h' }, size: { more: 'ArrowRight', less: 'ArrowLeft', moreMore: 'ArrowUp', lessLess: 'ArrowDown' }, softness: { more: 'o', less: 'i', moreMore: 'p', lessLess: 'u' }, opacity: { moreMore: 'l', lessLess: 'k' } }, object: { mode: { place: 'q', select: 'w' }, layer: { fg: 'f', bg: 'g' }, chooseObject: { open: 'e' }, scale: { value: { more: 'o', less: 'i', moreMore: 'p', lessLess: 'u' } }, }, pattern: { mode: { place: 'q', select: 'w' }, choosePattern: { open: 'e' }, scale: { more: 'o', less: 'i', moreMore: 'p', lessLess: 'u' }, cycleSelection: { cycle: 'c' } }, text: { mode: { place: 'q', select: 'w' }, size: { more: 'o', less: 'i', moreMore: 'p', lessLess: 'u' }, bold: { toggle: 'b' } }, note: { mode: { place: 'q', select: 'w' }, }, grid: { type: { hex: 'h', square: 's' }, opacity: { more: 'o', less: 'i', moreMore: 'p', lessLess: 'u' }, size: { more: 'ArrowRight', less: 'ArrowLeft', moreMore: 'ArrowUp', lessLess: 'ArrowDown' }, width: { more: 'l', less: 'k' } }, global: { tools: { sculpt: '1', brush: '2', object: '3', pattern: '4', text: '5', note: '6', grid: '7', zoom: '8' }, zoom: { upleft: 103, // these are key codes for the numpad up: 104, upright: 105, right: 102, downright: 99, down: 98, downleft: 97, left: 100, zoomIn: 107, zoomOut: 109, reset: 101 }, delete: 'Delete' } }; // from https://stackoverflow.com/a/53739792/1805439 function flattenObject(ob) { var toReturn = {}; for (var i in ob) { if (!ob.hasOwnProperty(i)) continue; if ((typeof ob[i]) == 'object' && ob[i] !== null) { var flatObject = flattenObject(ob[i]); for (var x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; toReturn[i + '.' + x] = flatObject[x]; } } else { toReturn[i] = ob[i]; } } return toReturn; } // inkarnate has jquery + angular let $ = window.jQuery; let angular = window.angular; const defaultSteps = { more: 1, less: -1, moreMore: 5, lessLess: -5 }; let configs = { sculpt: { size: { type: 'number', min: 3, max: 128 } }, brush: { chooseTexture: { type: 'button', selector: '#brush-texture-popup' }, size: { type: 'number', min: 1, max: 128 }, softness: { type: 'number', min: 0, max: 1, more: 0.01, less: -0.01, moreMore: 0.05, lessLess: -0.05, decimalPlaces: 2 }, opacity: { type: 'number', min: 0, max: 1, more: 0.01, less: -0.01, moreMore: 0.05, lessLess: -0.05, decimalPlaces: 2 } }, object: { chooseObject: { type: 'button', selector: '#object-popup' }, scale: { value: { type: 'number', min: 0.15, max: 2, more: 0.01, less: -0.01, moreMore: 0.05, lessLess: -0.05, decimalPlaces: 2 } }, }, pattern: { choosePattern: { type: 'button', selector: '#pattern-popup' }, scale: { type: 'number', alternateProperty: 'object.globalScale', min: 0.15, max: 2, more: 0.01, less: -0.01, moreMore: 0.05, lessLess: -0.05, decimalPlaces: 2 }, cycleSelection: { type: 'cycleSelection' } }, text: { size: { type: 'number', min: 5, max: 128 }, bold: { type: 'toggle' }, color: { type: 'button', selector: 'button.color-picker' }, shadow: { type: 'button', selector: '#text-shadow-popup' } }, grid: { opacity: { type: 'number', min: 0, max: 1, more: 0.01, less: -0.01, moreMore: 0.05, lessLess: -0.05, decimalPlaces: 2 }, size: { type: 'number', min: 16, max: 256 }, width: { type: 'number', min: 1, max: 5, more: 0.5, less: -0.5 } }, } function getScope() { return angular.element(document.getElementById('map-builder-view')).scope(); } function getByPath(obj, path, separator = '.') { var parts = path.split('.'); return parts.reduce((prev, curr) => prev && prev[curr], obj) } function setByPath(obj, path, value) { var parts = path.split('.'); parts.reduce(function(prev, cur, idx) { if (idx == parts.length - 1) { prev[cur] = value; } else { return prev[cur] || {}; } }, obj); } function changeScope(func) { let scope = getScope(); scope.$apply(() => { func(scope); }); } function getCurrentTool() { return getScope().tool; } function clampNumber(config, num) { const min = config.min || 0; const max = config.max || 128; return Math.min(Math.max(min, num), max); } function roundNumber(config, num) { const decimalPlaces = config.decimalPlaces || 1; const multiplier = Math.pow(10, decimalPlaces); return Math.round(num * multiplier) / multiplier; } function getStep(config, stepName) { return config[stepName] || defaultSteps[stepName]; } let originalSelection = []; let curIdx = -1; function cycleSelection() { let scopeSelection = getScope().selected; if (scopeSelection.length <= 1 && curIdx === -1) { return; } if (curIdx === -1) { curIdx = 0; originalSelection = scopeSelection; changeScope(s => { s.selected = [originalSelection[curIdx]]; }); return; } if (originalSelection[curIdx] === scopeSelection[0]) { if (++curIdx >= originalSelection.length) { curIdx = -1; changeScope(s => { s.selected = originalSelection; }); } else { changeScope(s => { s.selected = [originalSelection[curIdx]]; }); } } else { // user selected something different curIdx = -1; originalSelection = []; cycleSelection(); } } function handleKeyForTool(tool, key) { const binds = keybinds[tool]; const flatBinds = flattenObject(binds); for (const [path, bind] of Object.entries(flatBinds)) { if (key !== bind) { continue; } const pathArr = path.split('.'); let propPath = tool + '.' + pathArr.slice(0, pathArr.length - 1).join('.'); const val = pathArr[pathArr.length - 1]; //console.log(`Found bind: ${propPath} with value ${val}`); const config = getByPath(configs, propPath) || {}; if (config.alternateProperty) { propPath = config.alternateProperty; } switch (config.type) { case 'cycleSelection': { cycleSelection(); break; } case 'toggle': { changeScope(scope => { const oldVal = getByPath(scope, propPath); setByPath(scope, propPath, !oldVal); }); break; } case 'button': { //console.log(`Clicking button ${config.selector}`); $(config.selector).click(); break; } case 'number': { changeScope(scope => { const oldVal = getByPath(scope, propPath); const step = getStep(config, val); const newVal = oldVal + step; const newValClamped = clampNumber(config, newVal); const rounded = roundNumber(config, newValClamped); //console.log(`Set ${propPath} to ${rounded}`); setByPath(scope, propPath, rounded); }); break; } default: case 'select': { changeScope(scope => { setByPath(scope, propPath, val); }); break; } } } } function handleZoom(action, altKey) { function move(direction, amount) { changeScope(s => { s.zoom.translate[direction] += amount * (altKey ? 3 : 1); }); } function zoom(amount) { changeScope(s => { const newScale = s.zoom.scale * amount; s.zoom.scale = clampNumber({min: 1, max: 5}, roundNumber({decimalPlaces: 2}, newScale)); }); } const actions = { upleft: () => { move('y', 10); move('x', 10); }, up: move.bind(null, 'y', 10), upright: () => { move('y', 10); move('x', -10); }, right: move.bind(null, 'x', -10), downright: () => { move('y', -10); move('x', -10); }, down: move.bind(null, 'y', -10), downleft: () => { move('y', -10); move('x', 10); }, left: move.bind(null, 'x', 10), zoomIn: zoom.bind(null, 1.05), zoomOut: zoom.bind(null, 0.95), reset: () => { changeScope(s => { s.zoom.scale = 1; s.zoom.translate.x = 0; s.zoom.translate.y = 0; }); } }; actions[action](); } function keyHandler(e) { if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') { return; } if (keybinds.global.delete === e.key) { changeScope(s => { if (s.radialMenuActions().includes('delete')) { s.performRadialMenuAction('delete'); } }); return } const isZoomKey = Object.values(keybinds.global.zoom).includes(e.keyCode); if (isZoomKey) { const [zoomAction, _] = Object.entries(keybinds.global.zoom).find(elem => elem[1] === e.keyCode); handleZoom(zoomAction, e.altKey); return; } const isToolKey = Object.values(keybinds.global.tools).includes(e.key); if (isToolKey) { const [tool, _] = Object.entries(keybinds.global.tools).find(elem => elem[1] === e.key); changeScope(scope => scope.selectTool(tool)); return; } // console.log(`Keypress: ${e.key}`); let cur = getCurrentTool(); // console.log(`Cur tool: ${cur}`); if (!keybinds[cur]) { // console.log(`No Keybinds for tool "${getCurrentTool()}" configured.`); return; } handleKeyForTool(cur, e.key); } $(document).keydown(keyHandler); })();