// ==UserScript== // @name ๐œ—๐œšโ‹†โ‚Šหšpicmix layer manager + favorites // @namespace http://tampermonkey.net/ // @version 1.0 // @description adds a layer panel to picmix for easier stamp management, enables favoriting stamps // @match *://*.picmix.com/maker/app* // @grant none // @author etoilee // ==/UserScript== (function() { 'use strict'; document.body.dataset.layerPluginActive = 'true'; function getStampKeyFromSrcThumb(srcThumbUrl) { const match = srcThumbUrl.match(/_([a-f0-9]+)\.(png|gif|jpg)$/i); if (match && match[1]) { return match[1]; } return null; } function getObjectName(object) { if (object.isType('image') || object.isType('sprite')) { const stampInfo = window.maker.stampInfos.find(info => info.element === object._originalElement); if (stampInfo && stampInfo.name) { return stampInfo.name; } return object.isType('sprite') ? 'Animated Stamp' : 'Stamp'; } else if (object.isType('i-text')) { return object.text; } return object.type || 'Object'; } function createFavoriteButton(stampId, stampKey, isFavorite) { const favoriteButton = document.createElement('button'); const favoritedColor = '#FFD700'; const unfavoritedColor = 'black'; favoriteButton.classList.add('stamp-favorite-button'); favoriteButton.style.cssText = ` background: none; border: none; cursor: pointer; padding: 0; margin-left: auto; font-size: 1em; color: ${isFavorite ? favoritedColor : unfavoritedColor}; transition: color 0.2s; text-shadow: 0px 0px 2px white; line-height: 1; z-index: 10; position: relative; `; favoriteButton.innerHTML = isFavorite ? '&#9733;' : '&#9734;'; favoriteButton.title = isFavorite ? 'Unfavorite' : 'Favorite'; favoriteButton.onclick = function(event) { event.stopPropagation(); const currentIsFavorite = favoriteButton.innerHTML === '&#9733;'; const originalAddRemoveStampFavorite = window.addRemoveStampFavorite; const originalSuccessCallback = window.addRemoveStampFavorite.successCallback; window.addRemoveStampFavorite.successCallback = function(json) { if (originalSuccessCallback) { originalSuccessCallback(json); } const targetStampInfo = window.maker.stampInfos.find(info => String(info.id) === String(stampId) && String(info.key) === String(stampKey)); if (targetStampInfo) { targetStampInfo.isFavorite = !currentIsFavorite; } if (document.getElementById('picmix-layers-panel')) { updateLayerPanel(); } window.addRemoveStampFavorite.successCallback = originalSuccessCallback; }; originalAddRemoveStampFavorite( this, stampId, stampKey, '&star;', '&starf;', 'favorite-icon', 'remove-favorite-icon' ); }; return favoriteButton; } const originalDisplayStamps = window.maker.displayStamps; window.maker.displayStamps = function(_stamps) { _stamps.forEach(stamp => { if (!stamp.key && stamp.srcThumb) { stamp.key = getStampKeyFromSrcThumb(stamp.srcThumb); } const existingStampInfo = window.maker.stampInfos.find(info => String(info.id) === String(stamp.id) && String(info.key) === String(stamp.key)); if (existingStampInfo) { stamp.isFavorite = existingStampInfo.isFavorite; } else if (typeof stamp.isFavorite === 'undefined') { stamp.isFavorite = false; } }); originalDisplayStamps.apply(this, arguments); const stampList = document.getElementById('mk-stamps-list'); if (stampList) { stampList.querySelectorAll('.mk-stamp-preview').forEach((stampPreview, index) => { const stampInfo = _stamps[index]; if (stampInfo && stampInfo.id && stampInfo.key) { if (!stampPreview.querySelector('.stamp-favorite-button')) { const favoriteButton = createFavoriteButton(stampInfo.id, stampInfo.key, stampInfo.isFavorite); stampPreview.style.position = 'relative'; favoriteButton.style.position = 'absolute'; favoriteButton.style.top = '2px'; favoriteButton.style.right = '2px'; stampPreview.appendChild(favoriteButton); } else { const existingButton = stampPreview.querySelector('.stamp-favorite-button'); existingButton.innerHTML = stampInfo.isFavorite ? '&#9733;' : '&#9734;'; existingButton.style.color = stampInfo.isFavorite ? 'gold' : '#ccc'; existingButton.title = stampInfo.isFavorite ? 'Unfavorite' : 'Favorite'; } } }); } }; function updateLayerPanel() { const layersList = document.getElementById('picmix-layers-list'); if (!layersList) return; layersList.innerHTML = ''; const objects = window.maker.canvas.getObjects(); const activeObject = window.maker.canvas.getActiveObject(); for (let i = objects.length - 1; i >= 0; i--) { const obj = objects[i]; if (obj.isDrawingBackground) continue; const listItem = document.createElement('li'); listItem.dataset.objectId = window.maker.canvas._objects.indexOf(obj); listItem.style.cssText = ` display: flex; align-items: center; padding: 5px; border-radius: 10px; background: linear-gradient(to bottom,rgba(255,255,255,1) 0,rgba(240,240,240,1) 100%); cursor: pointer; border: 2px solid transparent; position: relative; font-size: 12px; color: #black; overflow: hidden; `; if (activeObject === obj) { listItem.style.borderColor = '#5fb0ee'; listItem.style.background = 'linear-gradient(to bottom,rgba(227,243,255,1) 0,rgba(189,228,255,1) 100%)'; } const thumbCanvas = document.createElement('canvas'); thumbCanvas.width = 30; thumbCanvas.height = 30; const thumbCtx = thumbCanvas.getContext('2d'); const originalProps = { scaleX: obj.scaleX, scaleY: obj.scaleY, left: obj.left, top: obj.top, angle: obj.angle, originX: obj.originX, originY: obj.originY }; const scaleFactor = Math.min(thumbCanvas.width / (obj.width * (obj.scaleX || 1)), thumbCanvas.height / (obj.height * (obj.scaleY || 1))); obj.set({ scaleX: scaleFactor, scaleY: scaleFactor, left: thumbCanvas.width / 2, top: thumbCanvas.height / 2, originX: 'center', originY: 'center', angle: 0 }); obj.render(thumbCtx); obj.set(originalProps); obj.setCoords(); const img = document.createElement('img'); img.src = thumbCanvas.toDataURL(); img.style.cssText = 'width: 30px; height: 30px; margin-right: 5px; background: #eee; border-radius:10px;'; listItem.appendChild(img); const nameSpan = document.createElement('span'); nameSpan.textContent = getObjectName(obj); nameSpan.style.cssText = 'flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;'; nameSpan.title = getObjectName(obj); listItem.appendChild(nameSpan); const stampId = window.maker.getStampIdFromObject(obj); if (stampId) { const stampInfo = window.maker.stampInfos.find(info => String(info.id) === String(stampId)); if (stampInfo && stampInfo.key) { const favoriteButton = createFavoriteButton(stampInfo.id, stampInfo.key, stampInfo.isFavorite); listItem.appendChild(favoriteButton); } } const controlsDiv = document.createElement('div'); controlsDiv.style.cssText = 'margin-left: 5px; display: flex; gap: 2px;'; const alphaClipButtonHTML = document.body.dataset.alphaClipPluginActive === 'true' ? `<button class="layer-control-btn alpha-clip-placeholder" data-object-index="${i}" title="Toggle Alpha Clip" style="background: ${obj.clipPath ? '#2e9942' : '#555'}; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#x2702;</button>` : ''; controlsDiv.innerHTML = ` ${alphaClipButtonHTML} <button class="layer-control-btn" data-action="bringForward" title="Bring Forward" style="background: dodgerblue; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#9650;</button> <button class="layer-control-btn" data-action="sendBackward" title="Send Backward" style="background: dodgerblue ; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#9660;</button> <button class="layer-control-btn" data-action="remove" title="Delete Layer" style="background: indianred; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#x2715;</button> `; listItem.appendChild(controlsDiv); listItem.addEventListener('click', (event) => { if (event.target.closest('.layer-control-btn, .stamp-favorite-button')) return; window.maker.canvas.setActiveObject(obj); window.maker.canvas.requestRenderAll(); updateLayerPanel(); }); controlsDiv.querySelector('[data-action="bringForward"]').addEventListener('click', () => { obj.bringForward(); updateLayerPanel(); }); controlsDiv.querySelector('[data-action="sendBackward"]').addEventListener('click', () => { obj.sendBackwards(); updateLayerPanel(); }); controlsDiv.querySelector('[data-action="remove"]').addEventListener('click', () => { window.maker.canvas.remove(obj); updateLayerPanel(); }); layersList.appendChild(listItem); } document.dispatchEvent(new CustomEvent('layerPanelUpdated')); } function addLayerPanelHTML() { const panelHtml = ` <style> #picmix-layers-panel { position: absolute; right: 10px; top: 100px; width: 250px; background: linear-gradient(101deg,rgba(223, 30, 126, 0.52) 0%, rgba(247, 141, 52, 0.19) 99%); border-radius: 15px; color: black; font-family: sans-serif; z-index: 1; box-sizing: border-box; } #picmix-layers-panel-header { display: flex; justify-content: space-between; border-radius:10px; margin:5px; align-items: center; padding: 5px 10px; background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 38%, rgba(237, 237, 237, 1) 55%, rgb(231 229 229) 69%, rgba(230, 230, 230, 1) 98%); border-bottom: 1px solid #bfbfbf; } #picmix-layers-panel h3 { margin: 0; font-size: 14px; text-align: center; flex-grow: 1; } #picmix-layers-toggle-btn { background: none; border: none; color: black; font-size: 18px; cursor: pointer; padding: 0 5px; line-height: 1; } #picmix-layers-list { list-style: none; padding: 0; margin: 0; max-height: 70vh; overflow-y: auto; display: none; padding: 10px; } #picmix-layers-panel.expanded #picmix-layers-list { display: block; } #picmix-layers-list::-webkit-scrollbar { width: 8px; } #picmix-layers-list::-webkit-scrollbar-track { background: transparent; } #picmix-layers-list::-webkit-scrollbar-thumb { background-color: white; border-radius: 4px; border: 2px solid #ddd; } </style> <div id="picmix-layers-panel"> <div id="picmix-layers-panel-header"> <h3>Layers</h3> <button id="picmix-layers-toggle-btn">&#9660;</button> </div> <ul id="picmix-layers-list"></ul> </div> `; document.body.insertAdjacentHTML('beforeend', panelHtml); const toggleButton = document.getElementById('picmix-layers-toggle-btn'); const layersPanel = document.getElementById('picmix-layers-panel'); toggleButton.addEventListener('click', () => { const isExpanded = layersPanel.classList.toggle('expanded'); toggleButton.innerHTML = isExpanded ? '&#9650;' : '&#9660;'; if (isExpanded) { updateLayerPanel(); } }); } function setupCanvasEventHandlers() { const canvasEvents = ['object:added', 'selection:created', 'selection:updated', 'selection:cleared', 'object:removed', 'object:modified']; canvasEvents.forEach(eventName => { window.maker.canvas.on(eventName, () => { if (document.getElementById('picmix-layers-panel').classList.contains('expanded')) { updateLayerPanel(); } }); }); } function initialize() { if (window.maker && window.maker.canvas && !document.getElementById('picmix-layers-panel')) { addLayerPanelHTML(); setupCanvasEventHandlers(); window.updateLayerManagerPanel = updateLayerPanel; } else { setTimeout(initialize, 500); } } initialize(); })();