/* eslint-disable */
// ==UserScript==
// @name adjust-figma-toolbelt
// @name:zh-CN adjust-figma-toolbelt
// @namespace https://github.com/palmcivet/adjust-figma-toolbelt
// @version 1.0.6
// @license MIT
// @author Palm Civet
// @description adjust-figma-toolbelt is a versatile script designed to enhance your Figma experience by allowing you to reposition the toolbelt with ease.
// @description:zh-CN adjust-figma-toolbelt 是一个用于增强 Figma 体验的脚本,可以轻松地将工具栏调整到想要的位置。
// @match https://www.figma.com/design/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// ==/UserScript==
(function() {
"use strict";
// @license MIT
const PREFIX = "adjust";
const STORAGE_LEFT = `__${PREFIX.toUpperCase()}_LEFT__`;
const STORAGE_BOTTOM = `__${PREFIX.toUpperCase()}_BOTTOM__`;
const TOOLBELT_SELECTOR = `[class*="positioned_design_toolbelt--root--"]`;
const FLOAT_MENU_STYLE = `
${TOOLBELT_SELECTOR} [class*="dropdown--dropdown--"] {
margin-top: 6px;
bottom: unset !important;
}
${TOOLBELT_SELECTOR} [class*="scroll_container--clipContainer--"] {
height: auto !important;
}
${TOOLBELT_SELECTOR} [class*="pointing_dropdown--scrollIndicator--"] {
display: none !important;
}`;
const DEFAULT_PADDING = 12;
const DEFAULT_TOP = 48 + DEFAULT_PADDING;
const FLOAT_MENU_TOP = 184;
const LABEL_PIN_DEFAULT_TOP = "⬆️ Pin the toolbelt to the top";
const LABEL_UNPIN_DEFAULT_TOP = "🔝 The toolbelt is already at the top";
const LABEL_START_CUSTOM_POSITION = "🎨 Customize the toolbelt position";
const LABEL_STOP_CUSTOM_POSITION = "💾 Save the position and exit";
// @license MIT
function findToolbeltElement() {
const element = window.document.querySelector(TOOLBELT_SELECTOR);
if (!element) {
return null;
}
return element;
}
// @license MIT
async function getToolbeltPosition() {
const left = await GM.getValue(STORAGE_LEFT);
const bottom = await GM.getValue(STORAGE_BOTTOM);
console.info(`[${PREFIX}] Read persistent data: ${left}, ${bottom}.`);
return {
left: left ?? null,
bottom: bottom ?? DEFAULT_PADDING
};
}
async function setToolbeltPosition({
left,
bottom
}) {
console.info(`[${PREFIX}] Save persistent data: ${left}, ${bottom}.`);
await GM.setValue(STORAGE_LEFT, left);
await GM.setValue(STORAGE_BOTTOM, bottom);
}
// @license MIT
function createMenuStore() {
const menuStore2 = /* @__PURE__ */ new Map();
const _removeMenus = () => {
for (const menu of menuStore2.values()) {
GM_unregisterMenuCommand(menu.id);
}
};
const _registerMenus = () => {
for (const menu of menuStore2.values()) {
const newId = GM_registerMenuCommand(menu.label, menu.callback);
menu.id = newId;
}
};
const update = (type, label, callback) => {
_removeMenus();
menuStore2.set(type, {
id: 0,
label,
callback
});
_registerMenus();
};
return {
update
};
}
function createFloatMenuStore() {
let styleElement;
const update = (element) => {
setTimeout(() => {
const { top, bottom } = element.getBoundingClientRect();
const toolbeltBottom = window.innerHeight - bottom;
console.log(top, FLOAT_MENU_TOP, toolbeltBottom, FLOAT_MENU_TOP);
if (top < FLOAT_MENU_TOP && toolbeltBottom > FLOAT_MENU_TOP) {
styleElement = GM_addStyle(FLOAT_MENU_STYLE);
} else {
styleElement == null ? void 0 : styleElement.remove();
}
}, 1e3);
};
return {
update
};
}
function createTransitionStore() {
let transitionStyle = null;
const enable = (element) => {
if (transitionStyle === null) {
transitionStyle = element.style.transition;
}
element.style.transition = "bottom 0.3s ease-in-out";
};
const disable = (element) => {
if (transitionStyle !== null) {
element.style.transition = transitionStyle;
}
};
return {
enable,
disable
};
}
const menuStore = createMenuStore();
const floatMenuStore = createFloatMenuStore();
const transitionStore = createTransitionStore();
// @license MIT
function moveToolbeltToTop(element, top) {
const { height: toolbeltHeight } = element.getBoundingClientRect();
const { innerHeight: viewportHeight } = window;
const bottom = viewportHeight - toolbeltHeight - top;
element.style.bottom = `${bottom}px`;
return { left: null, bottom };
}
function moveToolbeltToBottom(element, bottom) {
element.style.bottom = `${bottom}px`;
return { left: null, bottom };
}
async function resizeWindow(element) {
const { height } = element.getBoundingClientRect();
const position = await getToolbeltPosition();
if (position.bottom + height + DEFAULT_PADDING > window.innerHeight) {
moveToolbeltToTop(element, DEFAULT_TOP);
} else {
moveToolbeltToBottom(element, position.bottom);
}
}
async function registerInitialize(element) {
transitionStore.enable(element);
const data = await getToolbeltPosition();
if (data.bottom !== null) {
moveToolbeltToBottom(element, data.bottom);
resizeWindow(element);
}
floatMenuStore.update(element);
}
function registerResizeWindow(element) {
const onResize = () => {
resizeWindow(element);
};
window.addEventListener("resize", onResize);
return {
unregister: () => {
window.removeEventListener("resize", onResize);
}
};
}
function bindMouseEvent(element) {
let isDragging = false;
let startY = 0;
let toolbeltY = 0;
let position;
const onMouseDown = (event) => {
event.stopPropagation();
startY = event.clientY;
const { y } = element.getBoundingClientRect();
toolbeltY = y;
window.document.addEventListener("mousemove", onMouseMove);
window.document.addEventListener("mouseup", onMouseUp);
isDragging = true;
};
const onMouseMove = (event) => {
if (!isDragging) {
return;
}
const { clientY } = event;
const offsetY = clientY - startY;
position = moveToolbeltToTop(element, toolbeltY + offsetY);
};
const onMouseUp = (event) => {
onMouseMove(event);
isDragging = false;
window.document.removeEventListener("mousemove", onMouseMove);
window.document.removeEventListener("mouseup", onMouseUp);
};
return {
enable: () => {
element.addEventListener("mousedown", onMouseDown);
},
disable: () => {
element.removeEventListener("mousedown", onMouseDown);
return position;
}
};
}
function bindCursorStyle(element) {
let cursorStyle = "default";
const enable = () => {
cursorStyle = element.style.cursor;
element.style.cursor = "move";
};
const disable = () => {
element.style.cursor = cursorStyle;
};
return {
enable,
disable
};
}
// @license MIT
function registerDefaultMenu(element) {
const onPin = () => {
menuStore.update("DEFAULT", LABEL_UNPIN_DEFAULT_TOP, onUnpin);
const position = moveToolbeltToTop(element, DEFAULT_TOP);
setToolbeltPosition(position);
floatMenuStore.update(element);
};
const onUnpin = () => {
menuStore.update("DEFAULT", LABEL_PIN_DEFAULT_TOP, onPin);
const position = moveToolbeltToBottom(element, DEFAULT_PADDING);
setToolbeltPosition(position);
floatMenuStore.update(element);
};
menuStore.update("DEFAULT", LABEL_PIN_DEFAULT_TOP, onPin);
}
function registerCustomizeMenu(element) {
const cursorStyle = bindCursorStyle(element);
const mouseEvent = bindMouseEvent(element);
const onStart = () => {
transitionStore.disable(element);
menuStore.update("CUSTOMIZE", LABEL_STOP_CUSTOM_POSITION, onStop);
cursorStyle.enable();
mouseEvent.enable();
};
const onStop = () => {
menuStore.update("CUSTOMIZE", LABEL_START_CUSTOM_POSITION, onStart);
cursorStyle.disable();
const position = mouseEvent.disable();
setToolbeltPosition(position);
floatMenuStore.update(element);
transitionStore.enable(element);
};
menuStore.update("CUSTOMIZE", LABEL_START_CUSTOM_POSITION, onStart);
}
// @license MIT
(() => {
function onReady() {
const element = findToolbeltElement();
if (!element) {
console.error(
`[${PREFIX}] Can not find figma toolbelt: \`${TOOLBELT_SELECTOR}\`.`
);
return;
}
registerInitialize(element);
registerResizeWindow(element);
registerDefaultMenu(element);
registerCustomizeMenu(element);
console.info(`[${PREFIX}] The script is enabled.`);
}
const _onload_ = window.onload || function() {
};
window.onload = (event) => {
_onload_(event);
setTimeout(onReady, 1e3);
};
})();
})();