├── .babelrc ├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── manifest.json ├── manifest_beta.json ├── manifest_prod.json ├── package-lock.json ├── package.json ├── secrets_default.json ├── src ├── App.css ├── App.js ├── Core.js ├── ui │ ├── Bulletproof.css │ ├── Element.js │ ├── FigmaUI.css │ ├── components │ │ ├── display │ │ │ ├── DisplayComponent.css │ │ │ └── DisplayComponent.js │ │ ├── select │ │ │ ├── SelectComponent.css │ │ │ └── SelectComponent.js │ │ └── toolbar │ │ │ ├── ToolbarComponent.css │ │ │ └── ToolbarComponent.js │ ├── icons │ │ ├── IconCheck.js │ │ └── IconShow.js │ └── views │ │ ├── form │ │ ├── FormView.css │ │ └── FormView.js │ │ └── preferences │ │ ├── PreferencesView.css │ │ └── PreferencesView.js └── utils │ ├── DisplayNetwork.js │ ├── Router.js │ └── Tracking.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | "transform-custom-element-classes", 5 | "webpack-alias" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | charset = utf-8 9 | 10 | [*.{js, html, css}] 11 | indent_style = tab 12 | indent_size = 2 13 | 14 | [*.{yml, conf, json}] 15 | indent_style = space 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://paypal.me/basiclines'] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .nova 4 | secrets.json 5 | node_modules/ 6 | dist/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ismael González 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Super Tidy 2 | *A Figma plugin to easily align, rename and reorder your frames based in their canvas position.* 3 | 4 | Super Tidy renames your frames and reorders them in the layers list by their position in the canvas. It also replicates the Figma Tidy feature so you can run it all at once: Rename, Reorder and Tidy. 5 | 6 | ## Tutorials & related articles 7 | * [How to use Figma Super Tidy](https://www.youtube.com/watch?v=i681_evMv2k) 8 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Super Tidy", 3 | "id": "731260060173130163", 4 | "api": "1.0.0", 5 | "ui": "dist/ui.html", 6 | "main": "dist/core.js", 7 | "menu": [ 8 | {"name": "Rename", "command": "rename"}, 9 | {"name": "Reorder", "command": "reorder"}, 10 | {"name": "Tidy", "command": "tidy"}, 11 | {"name": "Pager", "command": "pager"}, 12 | {"name": "Run all", "command": "all"}, 13 | {"separator": true}, 14 | {"name": "Run custom...", "command": "options"} 15 | ], 16 | "editorType": ["figma", "figjam"] 17 | } 18 | -------------------------------------------------------------------------------- /manifest_beta.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Super Tidy Beta", 3 | "id": "952126876347866723", 4 | "api": "1.0.0", 5 | "ui": "dist/ui.html", 6 | "main": "dist/core.js", 7 | "menu": [ 8 | {"name": "Rename", "command": "rename"}, 9 | {"name": "Reorder", "command": "reorder"}, 10 | {"name": "Tidy", "command": "tidy"}, 11 | {"name": "Run all", "command": "all"}, 12 | {"separator": true}, 13 | {"name": "Run custom...", "command": "options"} 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /manifest_prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Super Tidy", 3 | "id": "731260060173130163", 4 | "api": "1.0.0", 5 | "ui": "dist/ui.html", 6 | "main": "dist/core.js", 7 | "menu": [ 8 | {"name": "Rename", "command": "rename"}, 9 | {"name": "Reorder", "command": "reorder"}, 10 | {"name": "Tidy", "command": "tidy"}, 11 | {"name": "Run all", "command": "all"}, 12 | {"separator": true}, 13 | {"name": "Run custom...", "command": "options"} 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "NODE_ENV=development npx webpack --mode=development --watch", 4 | "build": "NODE_ENV=production npx webpack --mode=production", 5 | "test": "echo \"Error: no test specified\" && exit 1" 6 | }, 7 | "devDependencies": { 8 | "babel-core": "^6.26.0", 9 | "babel-loader": "^7.1.2", 10 | "babel-plugin-transform-custom-element-classes": "^0.1.0", 11 | "babel-plugin-webpack-alias": "^2.1.2", 12 | "babel-preset-env": "^1.6.1", 13 | "css-loader": "^3.5.2", 14 | "html-webpack-inline-source-plugin": "0.0.10", 15 | "html-webpack-plugin": "^3.2.0", 16 | "style-loader": "^1.1.4", 17 | "url-loader": "^4.1.0", 18 | "webpack": "^4.42.1", 19 | "webpack-cli": "^3.3.11" 20 | }, 21 | "dependencies": { 22 | "@basiclines/leo": "^0.6.4", 23 | "ua-parser-js": "^0.7.24" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /secrets_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "README": "DUPLICATE ME AS secrets.json", 3 | "AMPLITUDE_KEY": "" 4 | } 5 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family:'Inter'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url("https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.7") format("woff2"), url("https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.7") format("woff") 6 | } 7 | 8 | @font-face { 9 | font-family:'Inter'; 10 | font-style: normal; 11 | font-weight: 500; 12 | src: url("https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7") format("woff2"), url("https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7") format("woff") 13 | } 14 | 15 | @font-face { 16 | font-family: 'Inter'; 17 | font-style: normal; 18 | font-weight: 600; 19 | src: url("https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7") format("woff2"), url("https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7") format("woff") 20 | } 21 | 22 | :root { 23 | --type-small-normal: 400 11px/16px "Inter", sans-serif; 24 | --type-small-medium: 500 11px/16px "Inter", sans-serif; 25 | --type-small-bold: 600 11px/16px "Inter", sans-serif; 26 | --type-medium-normal: 400 12px/16px "Inter", sans-serif; 27 | --type-medium-medium: 500 12px/16px "Inter", sans-serif; 28 | --type-medium-bold: 600 12px/16px "Inter", sans-serif; 29 | --type-large-normal: 400 13px/24px "Inter", sans-serif; 30 | --type-large-medium: 500 13px/24px "Inter", sans-serif; 31 | --type-large-bold: 600 13px/24px "Inter", sans-serif; 32 | --type-xlarge-normal: 400 14px/24px "Inter", sans-serif; 33 | --type-xlarge-medium: 500 14px/24px "Inter", sans-serif; 34 | --type-xlarge-bold: 600 14px/24px "Inter", sans-serif; 35 | 36 | --color-interactive-positive: #18A0FB; 37 | --color-interactive-negative: #E41761; 38 | --color-text-primary: #03121C; 39 | --color-text-secondary: #64646C; 40 | --color-text-tertiary: #AAAAB2; 41 | --color-background-primary: #FFFFFF; 42 | --color-decorator-strong: #C5CED2; 43 | --color-decorator-regular: #D5DDE2; 44 | --color-decorator-soft: #F1F4F4; 45 | 46 | --color-separator: rgba(0, 0, 0, 0.12); 47 | } 48 | 49 | body { text-align: left; overflow: hidden; } 50 | root-ui { transition: opacity 0.05s linear; display: block; position: relative; height: 539px; } 51 | root-ui[notready] { opacity: 0; pointer-events: none; } 52 | 53 | .c-icon { 54 | display: inline-flex; 55 | align-items: center; 56 | justify-content: center; 57 | vertical-align: middle; 58 | width: 16px; 59 | height: 16px; 60 | pointer-events: none; 61 | } 62 | .c-icon img { width: 100%; height: 100%; } 63 | 64 | .view { 65 | display: block; 66 | position: absolute; 67 | top: 40px; 68 | left: 0; 69 | right: 0; 70 | bottom: 0; 71 | overflow-y: auto; 72 | } 73 | 74 | .switch .switch__container { flex-shrink: 0; align-self: flex-start; margin-top: 4px; } 75 | .switch__label p { color: var(--color-text-secondary); } 76 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import 'src/ui/Bulletproof.css' 2 | import 'src/App.css' 3 | import 'src/ui/FigmaUI.css' 4 | 5 | import Tracking from 'src/utils/Tracking' 6 | import Router from 'src/utils/Router' 7 | import Element from 'src/ui/Element' 8 | 9 | import 'src/ui/components/toolbar/ToolbarComponent' 10 | import 'src/ui/views/form/FormView' 11 | import 'src/ui/views/preferences/PreferencesView' 12 | import 'src/ui/components/display/DisplayComponent' 13 | 14 | 15 | class ui extends Element { 16 | 17 | beforeMount() { 18 | window.addEventListener('message', e => { 19 | let msg = event.data.pluginMessage 20 | if (msg.type == 'init-hidden' || msg.type == 'init') { 21 | this.data.preferences = msg.preferences 22 | Tracking.setup(WP_AMPLITUDE_KEY, msg.UUID) 23 | Tracking.track('openPlugin', { cmd: msg.cmd }) 24 | } 25 | 26 | if (msg.type == 'init') { 27 | this.insertDisplay(msg.AD_LAST_SHOWN_DATE, msg.AD_LAST_SHOWN_IMPRESSION) 28 | } 29 | }) 30 | 31 | Router.setup({ 32 | index: '#index', 33 | preferences: '#preferences' 34 | }) 35 | } 36 | 37 | bind() { 38 | Router.on('change:url', url => this.showActiveView(url)) 39 | } 40 | 41 | insertDisplay(lastShownDate, lastShownImpression) { 42 | let elem = document.createElement('c-display') 43 | elem.setAttribute('lastshowndate', lastShownDate) 44 | elem.setAttribute('lastshownimpression', lastShownImpression) 45 | elem.setAttribute('hidden', '') 46 | document.body.insertBefore(elem, document.body.querySelector('root-ui')) 47 | } 48 | 49 | showActiveView(url) { 50 | let view = url.replace('#', '') 51 | this.findAll('[data-view]').forEach(view => view.setAttribute('hidden', '')) 52 | this.find(`[data-view="${view}"]`).removeAttribute('hidden') 53 | } 54 | 55 | render() { 56 | if (!this.data.preferences) return ''; 57 | return` 58 | 59 | 60 | 68 | 69 | ` 70 | } 71 | } 72 | 73 | customElements.define('root-ui', ui) 74 | -------------------------------------------------------------------------------- /src/Core.js: -------------------------------------------------------------------------------- 1 | import Tracking from 'src/utils/Tracking' 2 | 3 | const cmd = figma.command 4 | figma.showUI(__html__, { visible: false }) 5 | 6 | function getNodesGroupedbyPosition(nodes) { 7 | // Prepare nodes 8 | var input_ids = nodes.reduce((acc, item) => { 9 | acc.push({ id: item.id, x: item.x, y: item.y, width: item.width, height: item.height, name: item.name }) 10 | return acc 11 | }, []) 12 | 13 | // Sort by X 14 | input_ids.sort((current, next) => { 15 | return current.x - next.x 16 | }) 17 | 18 | // Create rows and columns 19 | var rows = [] 20 | input_ids.map(item => { 21 | var rowExist = rows.find(row => row.y + item.height/2 > item.y && row.y - item.height/2 < item.y) 22 | if (rowExist) { 23 | rowExist.columns.push(item) 24 | } else { 25 | rows.push({ y: item.y, columns: [item] }) 26 | } 27 | }) 28 | 29 | // Sort by Y 30 | return rows.sort((current, next) => current.y - next.y); 31 | } 32 | 33 | function getNameByPosition(row, col, startName) { 34 | 35 | var padLength = startName.length 36 | var parseStartName = parseInt(startName) 37 | var rowName = parseStartName + row * Math.pow(10, padLength - 1) 38 | var colName = rowName + col 39 | var name = '' 40 | 41 | function zeroPad(num, places) { 42 | var zero = places - num.toString().length + 1; 43 | return Array(+(zero > 0 && zero)).join("0") + num; 44 | } 45 | 46 | if (col == 0) { 47 | name = (row == 0) ? zeroPad(rowName, padLength) : rowName.toString(); 48 | } else { 49 | name = (row == 0) ? zeroPad(colName, padLength) : colName.toString(); 50 | } 51 | 52 | return name 53 | } 54 | 55 | function cmdRename(renameStrategy, startName) { 56 | var selection = figma.currentPage.selection 57 | var parent = (selection[0].type == 'PAGE') ? figma.currentPage : selection[0].parent 58 | var allNodes = parent.children 59 | var groupedNodes = getNodesGroupedbyPosition(selection) 60 | 61 | groupedNodes.forEach((row, rowidx) => { 62 | row.columns.forEach((col, colidx) => { 63 | var name = getNameByPosition(rowidx, colidx, startName) 64 | var match = allNodes.find(node => node.id === col.id) 65 | 66 | if (renameStrategy == 'merge') { 67 | match.name = `${name}_${match.name}` 68 | } else 69 | if (renameStrategy == 'replace') { 70 | match.name = name 71 | } 72 | }) 73 | }) 74 | } 75 | 76 | function cmdReorder() { 77 | var selection = figma.currentPage.selection 78 | var parent = (selection[0].type == 'PAGE') ? figma.currentPage : selection[0].parent 79 | var allNodes = parent.children 80 | var groupedNodes = getNodesGroupedbyPosition(selection) 81 | 82 | groupedNodes.reverse().forEach(row => { 83 | row.columns.reverse().forEach(col => { 84 | var match = allNodes.find(node => node.id === col.id) 85 | parent.appendChild(match) 86 | }) 87 | }) 88 | } 89 | 90 | function cmdTidy(xSpacing, ySpacing, wrapInstances) { 91 | var selection = figma.currentPage.selection 92 | var parent = (selection[0].type == 'PAGE') ? figma.currentPage : selection[0].parent 93 | var allNodes = parent.children 94 | var groupedNodes = getNodesGroupedbyPosition(selection) 95 | 96 | var x0 = 0 97 | var y0 = 0 98 | var xPos = 0 99 | var yPos = 0 100 | var tallestInRow = [] 101 | 102 | // Store tallest node per row 103 | groupedNodes.forEach((row, rowidx) => { 104 | let sortedRowColumns = row.columns.slice() 105 | sortedRowColumns.sort((prev, next) => { 106 | return (prev.height > next.height) ? -1 : 1; 107 | }) 108 | tallestInRow.push(sortedRowColumns[0].height) 109 | }) 110 | 111 | // Reposition nodes 112 | groupedNodes.forEach((row, rowidx) => { 113 | row.columns.forEach((col, colidx) => { 114 | if (rowidx == 0 && colidx == 0) { 115 | x0 = col.x 116 | y0 = col.y 117 | xPos = col.x 118 | yPos = col.y 119 | } 120 | var match = allNodes.find(node => node.id === col.id) 121 | var newXPos = (colidx == 0) ? xPos : xPos + xSpacing; 122 | var newYPos = yPos 123 | 124 | // Wrap instances with a frame around 125 | if (wrapInstances && match.type == 'INSTANCE') { 126 | var instanceParent = figma.createFrame() 127 | instanceParent.x = newXPos 128 | instanceParent.y = newYPos 129 | instanceParent.resize(match.width, match.height) 130 | instanceParent.appendChild(match) 131 | match.x = 0 132 | match.y = 0 133 | figma.currentPage.selection = figma.currentPage.selection.concat(instanceParent) 134 | } else { 135 | match.x = newXPos 136 | match.y = newYPos 137 | } 138 | 139 | xPos = newXPos + match.width 140 | }) 141 | 142 | xPos = x0 143 | yPos = yPos + (tallestInRow[rowidx] + ySpacing) 144 | }) 145 | } 146 | 147 | function cmdPager(pager_variable) { 148 | var selection = figma.currentPage.selection 149 | var parent = (selection[0].type == 'PAGE') ? figma.currentPage : selection[0].parent 150 | var allNodes = parent.children 151 | var groupedNodes = getNodesGroupedbyPosition(selection) 152 | var frameIndex = 0 153 | 154 | function searchPagerNodes(node, idx) { 155 | if (typeof node.children != 'undefined') { 156 | node.children.forEach(child => { 157 | searchPagerNodes(child, idx) 158 | }) 159 | } else if (node.type == 'TEXT' && node.name == pager_variable) { 160 | var font = node.fontName 161 | figma.loadFontAsync(font).then(() => { 162 | node.characters = idx.toString() 163 | }) 164 | } 165 | } 166 | 167 | groupedNodes.forEach(row => { 168 | row.columns.forEach(col => { 169 | var frame = allNodes.find(node => node.id === col.id) 170 | searchPagerNodes(frame, frameIndex) 171 | ++frameIndex 172 | }) 173 | }) 174 | 175 | } 176 | 177 | // Obtain UUID and preferences then trigger init event 178 | Promise.all([ 179 | figma.clientStorage.getAsync('UUID'), 180 | figma.clientStorage.getAsync('preferences'), 181 | figma.clientStorage.getAsync('AD_LAST_SHOWN_DATE'), 182 | figma.clientStorage.getAsync('AD_LAST_SHOWN_IMPRESSION'), 183 | figma.clientStorage.getAsync('spacing') // legacy 184 | ]).then(values => { 185 | let UUID = values[0] 186 | let preferences = values[1] 187 | let AD_LAST_SHOWN_DATE = values[2] || 572083200 // initial date, if no date was saved previously 188 | let AD_LAST_SHOWN_IMPRESSION = values[3] || 0 // initial impressions 189 | let spacing = values[4] 190 | 191 | let SPACING = { x: 100, y: 200 } 192 | let START_NAME = '000' 193 | let PAGER_VARIABLE = '{current}' 194 | let WRAP_INSTANCES = true 195 | let RENAME_STRATEGY_REPLACE = 'replace' 196 | let RENAME_STRATEGY_MERGE = 'merge' 197 | let PREFERENCES = { 198 | spacing: SPACING, 199 | start_name: START_NAME, 200 | pager_variable: PAGER_VARIABLE, 201 | wrap_instances: WRAP_INSTANCES, 202 | rename_strategy: RENAME_STRATEGY_REPLACE 203 | } 204 | 205 | if (!UUID) { 206 | UUID = Tracking.createUUID() 207 | figma.clientStorage.setAsync('UUID', UUID) 208 | } 209 | 210 | // legacy spacing preference 211 | if (typeof spacing != 'undefined') { 212 | PREFERENCES.spacing = spacing 213 | } 214 | 215 | if (typeof preferences == 'undefined') { 216 | preferences = PREFERENCES 217 | } 218 | 219 | figma.ui.postMessage({ 220 | type: 'init-hidden', 221 | UUID: UUID, 222 | cmd: cmd, 223 | preferences: preferences 224 | }) 225 | 226 | // Command triggered by user 227 | if (cmd == 'rename') { 228 | // RUNS WITHOUT UI 229 | cmdRename(preferences.rename_strategy, preferences.start_name) 230 | figma.notify('Super Tidy: Rename') 231 | setTimeout(() => figma.closePlugin(), 100) 232 | } else 233 | if (cmd == 'reorder') { 234 | // RUNS WITHOUT UI 235 | cmdReorder() 236 | figma.notify('Super Tidy: Reorder') 237 | setTimeout(() => figma.closePlugin(), 100) 238 | } else 239 | if (cmd == 'tidy') { 240 | // RUNS WITHOUT UI 241 | cmdTidy(preferences.spacing.x, preferences.spacing.y, preferences.wrap_instances) 242 | figma.notify('Super Tidy: Tidy') 243 | setTimeout(() => figma.closePlugin(), 100) 244 | } else 245 | if (cmd == 'pager') { 246 | // RUNS WITHOUT UI 247 | cmdPager(preferences.pager_variable) 248 | figma.notify('Super Tidy: Pager') 249 | setTimeout(() => figma.closePlugin(), 100) 250 | } else 251 | if (cmd == 'all') { 252 | // RUNS WITHOUT UI 253 | cmdTidy(preferences.spacing.x, preferences.spacing.y, preferences.wrap_instances) 254 | cmdReorder() 255 | cmdRename(preferences.rename_strategy, preferences.start_name) 256 | cmdPager(preferences.pager_variable) 257 | figma.notify('Super Tidy') 258 | setTimeout(() => figma.closePlugin(), 100) 259 | } else 260 | if (cmd == 'options') { 261 | // OPEN UI 262 | figma.showUI(__html__, { width: 320, height: 540 }) 263 | figma.ui.postMessage({ 264 | type: 'init', 265 | UUID: UUID, 266 | cmd: cmd, 267 | preferences: preferences, 268 | AD_LAST_SHOWN_DATE: AD_LAST_SHOWN_DATE, 269 | AD_LAST_SHOWN_IMPRESSION: AD_LAST_SHOWN_IMPRESSION 270 | }) 271 | figma.ui.postMessage({ type: 'selection', selection: figma.currentPage.selection }) 272 | 273 | figma.on('selectionchange', () => { 274 | figma.ui.postMessage({ type: 'selection', selection: figma.currentPage.selection }) 275 | }) 276 | 277 | figma.ui.onmessage = msg => { 278 | if (msg.type === 'tidy') { 279 | var RENAMING_ENABLED = msg.options.renaming 280 | var REORDER_ENABLED = msg.options.reorder 281 | var TIDY_ENABLED = msg.options.tidy 282 | var PAGER_ENABLED = msg.options.pager 283 | 284 | if (TIDY_ENABLED) cmdTidy(preferences.spacing.x, preferences.spacing.y, preferences.wrap_instances) 285 | if (RENAMING_ENABLED) cmdRename(preferences.rename_strategy, preferences.start_name) 286 | if (REORDER_ENABLED) cmdReorder() 287 | if (PAGER_ENABLED) cmdPager(preferences.pager_variable) 288 | figma.notify('Super Tidy') 289 | setTimeout(() => figma.closePlugin(), 100) 290 | } else 291 | if (msg.type === 'preferences') { 292 | preferences = msg.preferences 293 | figma.clientStorage.setAsync('preferences', preferences) 294 | figma.notify('Preferences saved') 295 | } else 296 | if (msg.type === 'displayImpression') { 297 | figma.ui.resize(320, 540+124) 298 | figma.clientStorage.setAsync('AD_LAST_SHOWN_DATE', Date.now()) 299 | figma.clientStorage.setAsync('AD_LAST_SHOWN_IMPRESSION', parseInt(AD_LAST_SHOWN_IMPRESSION)+1) 300 | } 301 | 302 | if (msg.type === 'resetImpression') { 303 | figma.clientStorage.setAsync('AD_LAST_SHOWN_IMPRESSION', 0) 304 | } 305 | } 306 | } 307 | }) 308 | -------------------------------------------------------------------------------- /src/ui/Bulletproof.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Bulletproof 3 | * All base styles that we need to normalize the starting point for cross-browser development. 4 | */ 5 | 6 | 7 | /*Base font */ 8 | html { min-height: 100%; position: relative; overflow-x: hidden; } 9 | body { margin: 0; padding: 0; text-align: center; vertical-align: middle; overflow-x: hidden; } 10 | 11 | 12 | /* Text conventions */ 13 | h1, h2, h3, h4, h5, h6 { margin: 0; padding: 0; font-weight: normal; } 14 | ul, ol, dl, dt, dd { padding: 0; margin: 0; list-style-type: none; } 15 | p, li { 16 | word-wrap: break-word; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | q { quotes: none; display: block; } 21 | pre, blockquote { padding: 0; margin: 0; } 22 | em { font-style: italic; } 23 | address { font-style: normal; display: inline; } 24 | abbr, acronym { cursor: help; border: none; } 25 | 26 | 27 | /* Headings */ 28 | h2 { font-weight: bold; } 29 | 30 | 31 | /* Links */ 32 | a { text-decoration: none; outline: none; } 33 | a:hover { text-decoration: underline; } 34 | a img { border: none; } 35 | a abbr { cursor: pointer; } 36 | 37 | 38 | /* Tables */ 39 | table { border-collapse: collapse; border-spacing: 0; width: 100%; } 40 | td { vertical-align: top; } 41 | caption, th { text-align: left; } 42 | 43 | 44 | /* Forms */ 45 | form { margin: 0; } 46 | fieldset { margin: 0; padding: 0; border: none; } 47 | legend { padding: 0; display: block; } 48 | 49 | button { height: auto; width: auto; overflow: visible; display: inline-block; vertical-align: middle; background: none; border: none; outline: none; white-space: nowrap; cursor: pointer; } 50 | button::-moz-focus-inner { padding: 0; border: none; } 51 | 52 | input, textarea, select, button { margin: 0; padding: 0; vertical-align: middle; } 53 | input:focus, textarea:focus { outline: none!important; } 54 | textarea { resize: none; overflow: auto; } 55 | 56 | input[type="text"], 57 | input[type="email"], 58 | input[type="number"], 59 | textarea { } 60 | 61 | input[type="text"]:focus, 62 | input[type="email"]:focus, 63 | input[type="number"]:focus, 64 | textarea:focus { } 65 | 66 | input:invalid { box-shadow: none; } 67 | input:-moz-submit-invalid { box-shadow: none; } 68 | input:-moz-ui-invalid { box-shadow:none; } 69 | 70 | input[type=number]::-webkit-outer-spin-button, 71 | input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } 72 | input[type=number] { -moz-appearance:textfield; } 73 | 74 | [contenteditable] { outline: none; word-wrap: break-word; } 75 | textarea::-webkit-input-placeholder, 76 | input::-webkit-input-placeholder { color: inherit; opacity: 1; } 77 | 78 | textarea::-moz-placeholder, 79 | input::-moz-placeholder { color: inherit; opacity: 1; } 80 | 81 | textarea:focus::-webkit-input-placeholder, 82 | input:focus::-webkit-input-placeholder { color: inherit; } 83 | 84 | textareafir:focus::-moz-placeholder, 85 | input:focus::-moz-placeholder { color: inherit; } 86 | 87 | input:-webkit-autofill { 88 | -webkit-box-shadow: 0 0 0 50px white inset; 89 | -webkit-text-fill-color: inherit; 90 | } 91 | input:-webkit-autofill:focus { 92 | -webkit-box-shadow: 0 0 0 50px white inset; 93 | -webkit-text-fill-color: inherit; 94 | } 95 | 96 | 97 | /* Media */ 98 | img { display: block; padding: 0; margin: 0; } 99 | img:-moz-broken { line-height: inherit; overflow: hidden; } 100 | 101 | 102 | /* HTML 5 */ 103 | article, aside, details, figcaption, figure, footer, header, hgroup, 104 | menu, nav, section, video, audio, canvas, progress, meter, time 105 | { display: block; padding: 0; margin: 0; } 106 | 107 | [hidden] { display: none!important; } 108 | 109 | /* Clearfix */ 110 | .cf { *zoom: 1; } .cf:before,.cf:after { content: ""; display: table; } .cf:after { clear: both; } 111 | -------------------------------------------------------------------------------- /src/ui/Element.js: -------------------------------------------------------------------------------- 1 | import LEOElement from 'leo/element' 2 | 3 | class Element extends LEOElement { 4 | } 5 | 6 | export default Element 7 | -------------------------------------------------------------------------------- /src/ui/FigmaUI.css: -------------------------------------------------------------------------------- 1 | /* FIGMA UI */ 2 | @font-face{font-family:Inter;font-style:normal;font-weight:400;src:url(https://rsms.me/inter/font-files/Inter-Regular.woff2?v=3.7) format("woff2"),url(https://rsms.me/inter/font-files/Inter-Regular.woff?v=3.7) format("woff")}@font-face{font-family:Inter;font-style:normal;font-weight:500;src:url(https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7) format("woff2"),url(https://rsms.me/inter/font-files/Inter-Medium.woff2?v=3.7) format("woff")}@font-face{font-family:Inter;font-style:normal;font-weight:600;src:url(https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7) format("woff2"),url(https://rsms.me/inter/font-files/Inter-SemiBold.woff2?v=3.7) format("woff")}.icon{width:32px;height:32px;cursor:default;color:#000;background-repeat:no-repeat;background-position:0 0}.icon--blue{color:#18a0fb;background-position:0 -64px}.icon--black-3{color:rgba(0,0,0,.3);background-position:0 -32px}.icon--button{border:2px solid transparent;border-radius:2px;outline:0;background-position:-2px -2px}.icon--button:hover{background-color:rgba(0,0,0,.06)}.icon--button:active{border:2px solid #18a0fb;background-color:rgba(0,0,0,.06)}.icon--button:disabled{opacity:.37}.icon--text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-family:Inter,sans-serif;font-size:11px}.icon--adjust{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m12%2016.05v-7.05h1v7.05c1.1411.2316%202%201.2405%202%202.45s-.8589%202.2184-2%202.45v2.05h-1v-2.05c-1.1411-.2316-2-1.2405-2-2.45s.8589-2.2184%202-2.45zm2%202.45c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm5%204.5h1v-7.05c1.1411-.2316%202-1.2405%202-2.45s-.8589-2.2184-2-2.45v-2.05h-1v2.05c-1.1411.2316-2%201.2405-2%202.45s.8589%202.2184%202%202.45zm2-9.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5%201.5-.6716%201.5-1.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m12%2048.05v-7.05h1v7.05c1.1411.2316%202%201.2405%202%202.45s-.8589%202.2184-2%202.45v2.05h-1v-2.05c-1.1411-.2316-2-1.2405-2-2.45s.8589-2.2184%202-2.45zm2%202.45c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm5%204.5h1v-7.05c1.1411-.2316%202-1.2405%202-2.45s-.8589-2.2184-2-2.45v-2.05h-1v2.05c-1.1411.2316-2%201.2405-2%202.45s.8589%202.2184%202%202.45zm2-9.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5%201.5-.6716%201.5-1.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m12%2080.05v-7.05h1v7.05c1.1411.2316%202%201.2405%202%202.45s-.8589%202.2184-2%202.45v2.05h-1v-2.05c-1.1411-.2316-2-1.2405-2-2.45s.8589-2.2184%202-2.45zm2%202.45c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm5%204.5h1v-7.05c1.1411-.2316%202-1.2405%202-2.45s-.8589-2.2184-2-2.45v-2.05h-1v2.05c-1.1411.2316-2%201.2405-2%202.45s.8589%202.2184%202%202.45zm2-9.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5%201.5-.6716%201.5-1.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--angle{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m12%2012v7.5.5h.5%207.5v-1h-3c0-2.2091-1.7909-4-4-4v-3zm1%204v3h3c0-1.6569-1.3431-3-3-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m12%2044v7.5.5h.5%207.5v-1h-3c0-2.2091-1.7909-4-4-4v-3zm1%204v3h3c0-1.6569-1.3431-3-3-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m12%2076v7.5.5h.5%207.5v-1h-3c0-2.2091-1.7909-4-4-4v-3zm1%204v3h3c0-1.6569-1.3431-3-3-3z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--break{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m13.0002%209v3h1v-3zm9.1031.89644c-1.1617-1.16176-3.0453-1.16176-4.2071.00002l-2.7499%202.74994.7071.7071%202.7499-2.7499c.7712-.77128%202.0217-.77129%202.7929%200%20.7712.7712.7713%202.0216%200%202.7928l-2.7499%202.75.7071.7071%202.7499-2.75c1.1618-1.1617%201.1618-3.0453%200-4.20706zm-12.20691%2012.20706c-1.16176-1.1617-1.16177-3.0453-.00001-4.2071l2.75002-2.75.7071.7071-2.75%202.75c-.77124.7713-.77124%202.0217%200%202.7929.7712.7713%202.0216.7713%202.7929%200l2.75-2.75.7071.7071-2.75%202.75c-1.1618%201.1618-3.0454%201.1618-4.20711%200zm13.10341-3.1035h-3v-1h3zm-3.9994%201v3h-1v-3zm-7.0006-7h-3.00004v1h3.00004z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%20opacity%3D%22.9%22%2F%3E%3Cpath%20d%3D%22m13.0002%2041v3h1v-3zm9.1031.8964c-1.1617-1.1617-3.0453-1.1617-4.2071.0001l-2.7499%202.7499.7071.7071%202.7499-2.7499c.7712-.7713%202.0217-.7713%202.7929%200%20.7712.7712.7713%202.0216%200%202.7928l-2.7499%202.75.7071.7071%202.7499-2.75c1.1618-1.1617%201.1618-3.0453%200-4.2071zm-12.20691%2012.2071c-1.16176-1.1617-1.16177-3.0453-.00001-4.2071l2.75002-2.75.7071.7071-2.75%202.75c-.77124.7713-.77124%202.0217%200%202.7929.7712.7713%202.0216.7713%202.7929%200l2.75-2.75.7071.7071-2.75%202.75c-1.1618%201.1618-3.0454%201.1618-4.20711%200zm13.10341-3.1035h-3v-1h3zm-3.9994%201v3h-1v-3zm-7.0006-7h-3.00004v1h3.00004z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%20opacity%3D%22.9%22%2F%3E%3Cpath%20d%3D%22m13.0002%2073v3h1v-3zm9.1031.8965c-1.1617-1.1618-3.0453-1.1618-4.2071%200l-2.7499%202.7499.7071.7071%202.7499-2.7499c.7712-.7713%202.0217-.7713%202.7929%200%20.7712.7712.7713%202.0216%200%202.7928l-2.7499%202.75.7071.7071%202.7499-2.7499c1.1618-1.1618%201.1618-3.0454%200-4.2071zm-12.20691%2012.207c-1.16176-1.1617-1.16177-3.0453-.00001-4.2071l2.75002-2.75.7071.7071-2.75%202.75c-.77124.7713-.77124%202.0217%200%202.7929.7712.7713%202.0216.7713%202.7929%200l2.75-2.75.7071.7071-2.75%202.75c-1.1618%201.1618-3.0454%201.1618-4.20711%200zm13.10341-3.1035h-3v-1h3zm-3.9994%201v3h-1v-3zm-7.0006-7h-3.00004v1h3.00004z%22%20fill%3D%22%2318a0fb%22%20opacity%3D%22.9%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--close{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2015.293%204.6465-4.6464.7071.7071-4.6465%204.6464%204.6465%204.6465-.7071.7071-4.6465-4.6464-4.6464%204.6464-.7071-.7071%204.6464-4.6465-4.6464-4.6463.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2047.293%204.6465-4.6464.7071.7071-4.6465%204.6464%204.6465%204.6465-.7071.7071-4.6465-4.6464-4.6464%204.6464-.7071-.7071%204.6464-4.6465-4.6464-4.6463.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2079.293%204.6465-4.6464.7071.7071-4.6465%204.6464%204.6465%204.6465-.7071.7071-4.6465-4.6464-4.6464%204.6464-.7071-.7071%204.6464-4.6465-4.6464-4.6463.7071-.7071z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--ellipses{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m11.5%2016c0%20.8284-.6716%201.5-1.5%201.5-.82843%200-1.5-.6716-1.5-1.5s.67157-1.5%201.5-1.5c.8284%200%201.5.6716%201.5%201.5zm6%200c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm4.5%201.5c.8284%200%201.5-.6716%201.5-1.5s-.6716-1.5-1.5-1.5-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m11.5%2048c0%20.8284-.6716%201.5-1.5%201.5-.82843%200-1.5-.6716-1.5-1.5s.67157-1.5%201.5-1.5c.8284%200%201.5.6716%201.5%201.5zm6%200c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm4.5%201.5c.8284%200%201.5-.6716%201.5-1.5s-.6716-1.5-1.5-1.5-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m11.5%2080c0%20.8284-.6716%201.5-1.5%201.5-.82843%200-1.5-.6716-1.5-1.5s.67157-1.5%201.5-1.5c.8284%200%201.5.6716%201.5%201.5zm6%200c0%20.8284-.6716%201.5-1.5%201.5s-1.5-.6716-1.5-1.5.6716-1.5%201.5-1.5%201.5.6716%201.5%201.5zm4.5%201.5c.8284%200%201.5-.6716%201.5-1.5s-.6716-1.5-1.5-1.5-1.5.6716-1.5%201.5.6716%201.5%201.5%201.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--eyedropper{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22m22.4473%209.6c-.8-.8-2-.8-2.8%200l-2.8001%202.8-.8-.7c-.4-.4-1-.4-1.4%200s-.4%201%200%201.4l.7.7-5.79995%205.8c-.4.4-1%201.9%200%202.9.99995%201%202.49995.4%202.89995%200l5.8-5.8.7001.7c.4.4%201%20.4%201.4%200s.4-1%200-1.4l-.7-.7%202.8-2.8c.8-.9.8-2.1%200-2.9zm-10.9001%2011.9h-1v-1l5.8-5.8%201%201c-.1%200-5.8%205.8-5.8%205.8z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m22.4473%2041.6c-.8-.8-2-.8-2.8%200l-2.8001%202.8-.8-.7c-.4-.4-1-.4-1.4%200s-.4%201%200%201.4l.7.7-5.79995%205.8c-.4.4-1%201.9%200%202.9.99995%201%202.49995.4%202.89995%200l5.8-5.8.7001.7c.4.4%201%20.4%201.4%200s.4-1%200-1.4l-.7-.7%202.8-2.8c.8-.9.8-2.1%200-2.9zm-10.9001%2011.9h-1v-1l5.8-5.8%201%201c-.1%200-5.8%205.8-5.8%205.8z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m22.4473%2073.6c-.8-.8-2-.8-2.8%200l-2.8001%202.8-.8-.7c-.4-.4-1-.4-1.4%200s-.4%201%200%201.4l.7.7-5.79995%205.8c-.4.4-1%201.9%200%202.9.99995%201%202.49995.4%202.89995%200l5.8-5.8.7001.7c.4.4%201%20.4%201.4%200s.4-1%200-1.4l-.7-.7%202.8-2.8c.8-.9.8-2.1%200-2.9zm-10.9001%2011.9h-1v-1l5.8-5.8%201%201c-.1%200-5.8%205.8-5.8%205.8z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fsvg%3E\a")}.icon--hidden{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m21.5085%2015.8012c.5554-.5276%201.0351-1.134%201.421-1.8012h-1.1842c-1.2655%201.8142-3.3673%203-5.7454%203-2.3782%200-4.48-1.1858-5.7454-3h-1.18428c.38597.6673.86567%201.2737%201.42108%201.8013l-1.59482%201.5949.70712.7071%201.6573-1.6574c.7108.5234%201.5112.9321%202.3742%201.1988l-.6171%202.2213.9636.2676.6262-2.2543c.452.0793.9172.1207%201.3921.1207.4748%200%20.9399-.0414%201.392-.1207l.6261%202.2543.9636-.2676-.617-2.2213c.863-.2666%201.6635-.6754%202.3743-1.1989l1.6576%201.6575.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m21.5085%2047.8012c.5554-.5276%201.0351-1.134%201.421-1.8012h-1.1842c-1.2655%201.8142-3.3673%203-5.7454%203-2.3782%200-4.48-1.1858-5.7454-3h-1.18428c.38597.6673.86567%201.2737%201.42108%201.8013l-1.59482%201.5949.70712.7071%201.6573-1.6574c.7108.5234%201.5112.9321%202.3742%201.1988l-.6171%202.2213.9636.2676.6262-2.2543c.452.0793.9172.1207%201.3921.1207.4748%200%20.9399-.0414%201.392-.1207l.6261%202.2543.9636-.2676-.617-2.2213c.863-.2666%201.6635-.6754%202.3743-1.1989l1.6576%201.6575.7071-.7071z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m21.5085%2079.8012c.5554-.5276%201.0351-1.134%201.421-1.8012h-1.1842c-1.2655%201.8142-3.3673%203-5.7454%203-2.3782%200-4.48-1.1858-5.7454-3h-1.18428c.38597.6673.86567%201.2737%201.42108%201.8013l-1.59482%201.5949.70712.7071%201.6573-1.6574c.7108.5234%201.5112.9321%202.3742%201.1988l-.6171%202.2213.9636.2676.6262-2.2543c.452.0793.9172.1207%201.3921.1207.4748%200%20.9399-.0414%201.392-.1207l.6261%202.2543.9636-.2676-.617-2.2213c.863-.2666%201.6635-.6754%202.3743-1.1989l1.6576%201.6575.7071-.7071z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--hyperlink{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m13.5%2018c1.9593%200%203.6262-1.2522%204.2439-3h1.0491c-.653%202.3085-2.7754%204-5.293%204-3.0376%200-5.5-2.4624-5.5-5.5s2.4624-5.5%205.5-5.5c2.5176%200%204.64%201.6915%205.293%204h-1.0491c-.6177-1.7478-2.2846-3-4.2439-3-2.4853%200-4.5%202.0147-4.5%204.5s2.0147%204.5%204.5%204.5zm5%205c2.4853%200%204.5-2.0147%204.5-4.5s-2.0147-4.5-4.5-4.5c-1.9593%200-3.6262%201.2522-4.2439%203h-1.0491c.653-2.3085%202.7754-4%205.293-4%203.0376%200%205.5%202.4624%205.5%205.5s-2.4624%205.5-5.5%205.5c-2.5176%200-4.64-1.6915-5.293-4h1.0491c.6177%201.7478%202.2846%203%204.2439%203z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m13.5%2050c1.9593%200%203.6262-1.2522%204.2439-3h1.0491c-.653%202.3085-2.7754%204-5.293%204-3.0376%200-5.5-2.4624-5.5-5.5s2.4624-5.5%205.5-5.5c2.5176%200%204.64%201.6915%205.293%204h-1.0491c-.6177-1.7478-2.2846-3-4.2439-3-2.4853%200-4.5%202.0147-4.5%204.5s2.0147%204.5%204.5%204.5zm5%205c2.4853%200%204.5-2.0147%204.5-4.5s-2.0147-4.5-4.5-4.5c-1.9593%200-3.6262%201.2522-4.2439%203h-1.0491c.653-2.3085%202.7754-4%205.293-4%203.0376%200%205.5%202.4624%205.5%205.5s-2.4624%205.5-5.5%205.5c-2.5176%200-4.64-1.6915-5.293-4h1.0491c.6177%201.7478%202.2846%203%204.2439%203z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m13.5%2082c1.9593%200%203.6262-1.2522%204.2439-3h1.0491c-.653%202.3085-2.7754%204-5.293%204-3.0376%200-5.5-2.4624-5.5-5.5s2.4624-5.5%205.5-5.5c2.5176%200%204.64%201.6915%205.293%204h-1.0491c-.6177-1.7478-2.2846-3-4.2439-3-2.4853%200-4.5%202.0147-4.5%204.5s2.0147%204.5%204.5%204.5zm5%205c2.4853%200%204.5-2.0147%204.5-4.5s-2.0147-4.5-4.5-4.5c-1.9593%200-3.6262%201.2522-4.2439%203h-1.0491c.653-2.3085%202.7754-4%205.293-4%203.0376%200%205.5%202.4624%205.5%205.5s-2.4624%205.5-5.5%205.5c-2.5176%200-4.64-1.6915-5.293-4h1.0491c.6177%201.7478%202.2846%203%204.2439%203z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--link-broken{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m18%2014v-2c0-1.1046-.8954-2-2-2s-2%20.8954-2%202v2h-1v-2c0-1.6569%201.3431-3%203-3s3%201.3431%203%203v2zm1%204h-1v2c0%201.1046-.8954%202-2%202s-2-.8954-2-2v-2h-1v2c0%201.6569%201.3431%203%203%203s3-1.3431%203-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m18%2046v-2c0-1.1046-.8954-2-2-2s-2%20.8954-2%202v2h-1v-2c0-1.6569%201.3431-3%203-3s3%201.3431%203%203v2zm1%204h-1v2c0%201.1046-.8954%202-2%202s-2-.8954-2-2v-2h-1v2c0%201.6569%201.3431%203%203%203s3-1.3431%203-3z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m18%2078v-2c0-1.1046-.8954-2-2-2s-2%20.8954-2%202v2h-1v-2c0-1.6569%201.3431-3%203-3s3%201.3431%203%203v2zm1%204h-1v2c0%201.1046-.8954%202-2%202s-2-.8954-2-2v-2h-1v2c0%201.6569%201.3431%203%203%203s3-1.3431%203-3z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--link{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2010c1.1046%200%202%20.8954%202%202v2h1v-2c0-1.6569-1.3431-3-3-3s-3%201.3431-3%203v2h1v-2c0-1.1046.8954-2%202-2zm2%208h1v2c0%201.6569-1.3431%203-3%203s-3-1.3431-3-3v-2h1v2c0%201.1046.8954%202%202%202s2-.8954%202-2zm-2.5-5v6h1v-6z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2042c1.1046%200%202%20.8954%202%202v2h1v-2c0-1.6569-1.3431-3-3-3s-3%201.3431-3%203v2h1v-2c0-1.1046.8954-2%202-2zm2%208h1v2c0%201.6569-1.3431%203-3%203s-3-1.3431-3-3v-2h1v2c0%201.1046.8954%202%202%202s2-.8954%202-2zm-2.5-5v6h1v-6z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2074c1.1046%200%202%20.8954%202%202v2h1v-2c0-1.6569-1.3431-3-3-3s-3%201.3431-3%203v2h1v-2c0-1.1046.8954-2%202-2zm2%208h1v2c0%201.6569-1.3431%203-3%203s-3-1.3431-3-3v-2h1v2c0%201.1046.8954%202%202%202s2-.8954%202-2zm-2.5-5v6h1v-6z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--lock{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m17.5%2013.5v1.5h-3v-1.5c0-.8284.6716-1.5%201.5-1.5s1.5.6716%201.5%201.5zm-4%201.5v-1.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m17.5%2045.5v1.5h-3v-1.5c0-.8284.6716-1.5%201.5-1.5s1.5.6716%201.5%201.5zm-4%201.5v-1.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m17.5%2077.5v1.5h-3v-1.5c0-.8284.6716-1.5%201.5-1.5s1.5.6716%201.5%201.5zm-4%201.5v-1.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--minus{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m21.5%2016.5h-11v-1h11z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m21.5%2048.5h-11v-1h11z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m21.5%2080.5h-11v-1h11z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--play{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m13%2010.0979.765.4781%208%205%20.6784.424-.6784.424-8%205-.765.4781v-.9021-10zm1%201.8042v8.1958l6.5566-4.0979z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m13%2042.0979.765.4781%208%205%20.6784.424-.6784.424-8%205-.765.4781v-.9021-10zm1%201.8042v8.1958l6.5566-4.0979z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m13%2074.0979.765.4781%208%205%20.6784.424-.6784.424-8%205-.765.4781v-.9021-10zm1%201.8042v8.1958l6.5566-4.0979z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--plus{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m15.5%2015.5v-5h1v5h5v1h-5v5h-1v-5h-5v-1z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m15.5%2047.5v-5h1v5h5v1h-5v5h-1v-5h-5v-1z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m15.5%2079.5v-5h1v5h5v1h-5v5h-1v-5h-5v-1z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--recent{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m23%2016c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-9-4v4.5.5h.5%204.5v-1h-4v-4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m23%2048c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-9-4v4.5.5h.5%204.5v-1h-4v-4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m23%2080c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-9-4v4.5.5h.5%204.5v-1h-4v-4z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--recent{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2023.9999c4.4183%200%208-3.5817%208-8s-3.5817-8.00002-8-8.00002-8%203.58172-8%208.00002%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2055.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2087.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--resolve-filled{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16%2023.9999c4.4183%200%208-3.5817%208-8s-3.5817-8.00002-8-8.00002-8%203.58172-8%208.00002%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16%2055.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16%2087.9999c4.4183%200%208-3.5817%208-8s-3.5817-8-8-8-8%203.5817-8%208%203.5817%208%208%208zm-.0889-5.1346%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--resolve{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m23%2015.9999c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7.00002%207-7.00002%207%203.13402%207%207.00002zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8.00002%208-8.00002%208%203.58172%208%208.00002zm-8.0889%202.8654%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m23%2047.9999c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-8.0889%202.8654%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m23%2079.9999c0%203.866-3.134%207-7%207s-7-3.134-7-7%203.134-7%207-7%207%203.134%207%207zm1%200c0%204.4183-3.5817%208-8%208s-8-3.5817-8-8%203.5817-8%208-8%208%203.5817%208%208zm-8.0889%202.8654%204-4.4999-.8222-.7308-3.6125%204.0639-2.5875-2.5874-.7778.7778%203%202.9999.4125.4124z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--search{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m20%2015c0%202.7614-2.2386%205-5%205s-5-2.2386-5-5%202.2386-5%205-5%205%202.2386%205%205zm-1.1256%204.5815c-1.0453.8849-2.3975%201.4185-3.8744%201.4185-3.3137%200-6-2.6863-6-6s2.6863-6%206-6%206%202.6863%206%206c0%201.4769-.5336%202.8291-1.4185%203.8744l4.2721%204.272-.7072.7072z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m20%2047c0%202.7614-2.2386%205-5%205s-5-2.2386-5-5%202.2386-5%205-5%205%202.2386%205%205zm-1.1256%204.5815c-1.0453.8849-2.3975%201.4185-3.8744%201.4185-3.3137%200-6-2.6863-6-6s2.6863-6%206-6%206%202.6863%206%206c0%201.4769-.5336%202.8291-1.4185%203.8744l4.2721%204.272-.7072.7072z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m20%2079c0%202.7614-2.2386%205-5%205s-5-2.2386-5-5%202.2386-5%205-5%205%202.2386%205%205zm-1.1256%204.5815c-1.0453.8849-2.3975%201.4185-3.8744%201.4185-3.3137%200-6-2.6863-6-6s2.6863-6%206-6%206%202.6863%206%206c0%201.4769-.5336%202.8291-1.4185%203.8744l4.2721%204.272-.7072.7072z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--trash{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m15%209.5c-.5523%200-1%20.44772-1%201h4c0-.55228-.4477-1-1-1zm4%201c0-1.10457-.8954-2-2-2h-2c-1.1046%200-2%20.89543-2%202h-1.5-1.5v1h1v10c0%201.1046.8954%202%202%202h6c1.1046%200%202-.8954%202-2v-10h1v-1h-1.5zm1%201h-1.5-5-1.5v10c0%20.5523.4477%201%201%201h6c.5523%200%201-.4477%201-1zm-6%207v-4h1v4zm3%200v-4h1v4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m15%2041.5c-.5523%200-1%20.4477-1%201h4c0-.5523-.4477-1-1-1zm4%201c0-1.1046-.8954-2-2-2h-2c-1.1046%200-2%20.8954-2%202h-1.5-1.5v1h1v10c0%201.1046.8954%202%202%202h6c1.1046%200%202-.8954%202-2v-10h1v-1h-1.5zm1%201h-1.5-5-1.5v10c0%20.5523.4477%201%201%201h6c.5523%200%201-.4477%201-1zm-6%207v-4h1v4zm3%200v-4h1v4z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m15%2073.5c-.5523%200-1%20.4477-1%201h4c0-.5523-.4477-1-1-1zm4%201c0-1.1046-.8954-2-2-2h-2c-1.1046%200-2%20.8954-2%202h-1.5-1.5v1h1v10c0%201.1046.8954%202%202%202h6c1.1046%200%202-.8954%202-2v-10h1v-1h-1.5zm1%201h-1.5-5-1.5v10c0%20.5523.4477%201%201%201h6c.5523%200%201-.4477%201-1zm-6%207v-4h1v4zm3%200v-4h1v4z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--unlock{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m18%2014v1h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5h4.5v-2.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h-1v-1.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m18%2046v1h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5h4.5v-2.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h-1v-1.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m18%2078v1h.5c.2761%200%20.5.2239.5.5v5c0%20.2761-.2239.5-.5.5h-6c-.2761%200-.5-.2239-.5-.5v-5c0-.2761.2239-.5.5-.5h4.5v-2.5c0-1.3807%201.1193-2.5%202.5-2.5s2.5%201.1193%202.5%202.5v1.5h-1v-1.5c0-.8284-.6716-1.5-1.5-1.5s-1.5.6716-1.5%201.5z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.icon--visible{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2296%22%20viewBox%3D%220%200%2032%2096%22%20width%3D%2232%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20clip-rule%3D%22evenodd%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22m16.0001%2019c-2.2999%200-4.3222-1.1942-5.4784-3%201.1562-1.8058%203.1785-3%205.4784-3%202.2998%200%204.3221%201.1942%205.4783%203-1.1562%201.8058-3.1785%203-5.4783%203zm0-7c2.878%200%205.3774%201.6211%206.6349%204-1.2575%202.3789-3.7569%204-6.6349%204-2.8781%200-5.3775-1.6211-6.63499-4%201.25749-2.3789%203.75689-4%206.63499-4zm.0003%206c1.1045%200%202-.8954%202-2s-.8955-2-2-2c-1.1046%200-2%20.8954-2%202s.8954%202%202%202z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.8%22%2F%3E%3Cpath%20d%3D%22m16.0001%2051c-2.2999%200-4.3222-1.1942-5.4784-3%201.1562-1.8058%203.1785-3%205.4784-3%202.2998%200%204.3221%201.1942%205.4783%203-1.1562%201.8058-3.1785%203-5.4783%203zm0-7c2.878%200%205.3774%201.6211%206.6349%204-1.2575%202.3789-3.7569%204-6.6349%204-2.8781%200-5.3775-1.6211-6.63499-4%201.25749-2.3789%203.75689-4%206.63499-4zm.0003%206c1.1045%200%202-.8954%202-2s-.8955-2-2-2c-1.1046%200-2%20.8954-2%202s.8954%202%202%202z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%2F%3E%3Cpath%20d%3D%22m16.0001%2083c-2.2999%200-4.3222-1.1942-5.4784-3%201.1562-1.8058%203.1785-3%205.4784-3%202.2998%200%204.3221%201.1942%205.4783%203-1.1562%201.8058-3.1785%203-5.4783%203zm0-7c2.878%200%205.3774%201.6211%206.6349%204-1.2575%202.3789-3.7569%204-6.6349%204-2.8781%200-5.3775-1.6211-6.63499-4%201.25749-2.3789%203.75689-4%206.63499-4zm.0003%206c1.1045%200%202-.8954%202-2s-.8955-2-2-2c-1.1046%200-2%20.8954-2%202s.8954%202%202%202z%22%20fill%3D%22%2318a0fb%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\a")}.label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:32px;padding:8px 4px 8px 8px;color:rgba(0,0,0,.3);background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.section-title{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:32px;padding:8px 4px 8px 8px;color:rgba(0,0,0,.8);background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:11px;letter-spacing:.005em}.type{margin:0;padding:0}.type--11-pos{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.type--11-pos-medium{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.005em}.type--11-pos-bold{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:11px;letter-spacing:.005em}.type--12-pos{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:12px;letter-spacing:0}.type--12-pos-medium{color:rgba(0,0,0,.8);font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:12px;letter-spacing:0}.type--12-pos-bold{font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:12px;letter-spacing:0}.type--11-neg{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.01em}.type--11-neg-medium{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.01em}.type--11-neg-bold{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:11px;letter-spacing:.01em}.type--12-neg{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:12px;letter-spacing:.005em}.type--12-neg-medium{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:12px;letter-spacing:.005em}.type--12-neg-bold{color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:600;font-size:12px;letter-spacing:.005em}.button{display:inline-block;-ms-flex-negative:0;flex-shrink:0;margin:1px 0 1px 0;padding:5px 16px 5px 16px;border:2px solid transparent;border-radius:6px;outline:0}.button--primary{color:#fff;background-color:#18a0fb;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.01em}.button--primary:active,.button--primary:focus{border:2px solid rgba(0,0,0,.3)}.button--primary:disabled{background-color:rgba(0,0,0,.3)}.button--primary-destructive{color:#fff;background-color:#f24822}.button--primary-destructive:active,.button--primary-destructive:focus{border:2px solid rgba(0,0,0,.3)}.button--primary-destructive:disabled{opacity:.4}.button--secondary{color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.8);background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.005em}.button--secondary:active,.button--secondary:focus{padding:4px 23px 4px 23px;border:2px solid #18a0fb}.button--secondary:disabled{color:rgba(0,0,0,.3);border:1px solid rgba(0,0,0,.3)}.button--secondary-destructive{color:#f24822;border:1px solid #f24822;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:500;font-size:11px;letter-spacing:.005em}.button--secondary-destructive:active,.button--secondary-destructive:focus{padding:4px 23px 4px 23px;border:2px solid #f24822}.button--secondary-destructive:disabled{opacity:.4}.input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:30px;margin:1px 0 1px 0;padding:8px 4px 8px 7px;color:rgba(0,0,0,.8);border:1px solid transparent;border-radius:2px;outline:0;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.input:hover{color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.1)}.input:active,.input:focus{padding:8px 4px 8px 6px;color:#000;border:2px solid #18a0fb;border-radius:2px}.input::-moz-selection{color:#000;background-color:rgba(24,145,251,.3)}.input::selection{color:#000;background-color:rgba(24,145,251,.3)}.input::-webkit-input-placeholder{color:rgba(0,0,0,.3)}.input:-ms-input-placeholder{color:rgba(0,0,0,.3)}.input::-ms-input-placeholder{color:rgba(0,0,0,.3)}.input::placeholder{color:rgba(0,0,0,.3)}.input:disabled{color:rgba(0,0,0,.3)}.input-icon{position:relative;width:100%}.input-icon__icon{position:absolute;top:-1px;left:0;width:32px;height:32px}.input-icon__input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:30px;margin:1px 0 1px 0;padding:8px 4px 8px 0;text-indent:32px;color:rgba(0,0,0,.8);border:1px solid transparent;border-radius:2px;outline:0;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.input-icon__input:hover{color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.1)}.input-icon__input:active,.input-icon__input:focus{margin-left:-1px;padding:8px 4px 8px 0;color:#000;border:2px solid #18a0fb;border-radius:2px}.input-icon__input::-moz-selection{color:#000;background-color:rgba(24,145,251,.3)}.input-icon__input::selection{color:#000;background-color:rgba(24,145,251,.3)}.input-icon__input::-webkit-input-placeholder{color:rgba(0,0,0,.3)}.input-icon__input:-ms-input-placeholder{color:rgba(0,0,0,.3)}.input-icon__input::-ms-input-placeholder{color:rgba(0,0,0,.3)}.input-icon__input::placeholder{color:rgba(0,0,0,.3)}.input-icon__input:disabled{color:rgba(0,0,0,.3)}.textarea{display:-webkit-box;display:-ms-flexbox;display:flex;overflow:hidden;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:62px;margin:1px 8px 1px 8px;padding:7px 4px 7px 7px;resize:none;color:rgba(0,0,0,.8);border:1px solid rgba(0,0,0,.1);border-radius:2px;outline:0;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.textarea:active,.textarea:focus{padding:6px 4px 6px 6px;color:#000;border:2px solid #18a0fb;border-radius:2px}.textarea::-moz-selection{color:#000;background-color:rgba(24,145,251,.3)}.textarea::selection{color:#000;background-color:rgba(24,145,251,.3)}.textarea::-webkit-input-placeholder{color:rgba(0,0,0,.3)}.textarea:-ms-input-placeholder{color:rgba(0,0,0,.3)}.textarea::-ms-input-placeholder{color:rgba(0,0,0,.3)}.textarea::placeholder{color:rgba(0,0,0,.3)}.textarea:disabled{color:rgba(0,0,0,.3)}.textarea:disabled:focus{border:1px solid rgba(0,0,0,.1)}.select-dropdown{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2;-ms-flex-wrap:nowrap;flex-wrap:nowrap;width:100%;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.select-dropdown:last-child{margin-right:0}.select-dropdown__button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:30px;margin:1px 0 1px 0!important;padding:0 8px 0 8px;text-align:left;cursor:pointer;color:rgba(0,0,0,.8);border:1px solid transparent;border-radius:2px;background-color:#fff;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.select-dropdown__button span:after{display:inline-block;width:7px;height:5px;margin-top:6px;margin-left:6px;content:'';background-color:transparent;background-image:url(data:image/svg+xml;utf8,%3Csvg%20fill%3D%22none%22%20height%3D%225%22%20viewBox%3D%220%200%207%205%22%20width%3D%227%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m3%203.70711-3-3.000003.707107-.707107%202.646443%202.64645%202.64645-2.64645.70711.707107-3%203.000003-.35356.35355z%22%20fill%3D%22%23000%22%20fill-opacity%3D%22.3%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E)}.select-dropdown__button:hover{padding:0 8px 0 8px;border:1px solid rgba(0,0,0,.1)}.select-dropdown__button:hover .chevron-down{opacity:1}.select-dropdown__button:hover span:after{opacity:0}.select-dropdown__button .chevron-down{position:absolute;top:1px;right:0;width:30px;height:30px;opacity:0;background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2230%22%20viewBox%3D%220%200%2030%2030%22%20width%3D%2230%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m15%2016.7071-3-3%20.7071-.7071%202.6465%202.6464%202.6464-2.6464.7071.7071-3%203-.3535.3536z%22%20fill%3D%22%23000%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E\a");background-repeat:no-repeat;background-position:0 0}.select-dropdown__button--active,.select-dropdown__button:focus{width:100%;padding:0 7px 0 7px;border:2px solid #18a0fb;outline:0}.select-dropdown__button--active .chevron-down,.select-dropdown__button:focus .chevron-down{opacity:1}.select-dropdown__button--active span:after,.select-dropdown__button:focus span:after{opacity:0}.select-dropdown__list{position:absolute;z-index:2;top:31px;right:0;left:0;display:block;overflow:auto;width:100%;margin:0;padding:0;list-style-type:none;pointer-events:none;opacity:0;-webkit-box-shadow:0 5px 17px rgba(0,0,0,.2),0 2px 7px rgba(0,0,0,.15);box-shadow:0 5px 17px rgba(0,0,0,.2),0 2px 7px rgba(0,0,0,.15)}.select-dropdown__list:before{display:block;height:8px;content:'';border-top-left-radius:2px;border-top-right-radius:2px;background-color:#222}.select-dropdown__list:after{display:block;height:8px;content:'';border-bottom-right-radius:2px;border-bottom-left-radius:2px;background-color:#222}.select-dropdown__list.active{pointer-events:auto;opacity:1}.select-dropdown__list-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;height:24px;padding:0 16px 0 32px;list-style-type:none;text-align:left;cursor:pointer;color:#fff;background-color:#222;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:12px;letter-spacing:.005em}.select-dropdown__list-item:hover{color:#fff;background-color:#18a0fb}.select-dropdown__list-item--selected{background-image:url("data:image/svg+xml;utf8,\a %3Csvg%20fill%3D%22none%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%20width%3D%2216%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m13.2069%205.20724-5.50002%205.49996-.70711.7072-.70711-.7072-3-2.99996%201.41422-1.41421%202.29289%202.29289%204.79293-4.79289z%22%20fill%3D%22%23fff%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E\a");background-repeat:no-repeat;background-position:8px 4px}.select-dropdown__list-item--initial{background-color:#18a0fb}.select-dropdown__divider{margin:0;padding:8px 0 8px 0;background-color:#222}.select-dropdown__line{display:block;height:1px;background-color:rgba(255,255,255,.2)}.switch{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-item-align:1;align-self:1;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;cursor:default}.switch__container{position:relative;width:24px;height:12px;margin:10px 16px 10px 8px}.switch__label{font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.switch__checkbox{width:0;height:0;opacity:0}.switch__checkbox:checked+.switch__slider{background-color:#000}.switch__checkbox:focus+.switch__slider{-webkit-box-shadow:0 0 1px #2196f3;box-shadow:0 0 1px #2196f3}.switch__checkbox:checked+.switch__slider:before{-webkit-transform:translateX(12px);transform:translateX(12px)}.switch__slider{position:absolute;top:0;right:0;bottom:0;left:0;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;-webkit-transition:background-color 0 .2s;transition:background-color 0 .2s;border:1px solid #000;border-radius:12px;background-color:#fff}.switch__slider::before{position:absolute;top:-1px;left:-1px;width:10px;height:10px;content:'';-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;-webkit-transition:background-color 0 .2s;transition:background-color 0 .2s;border:1px solid #000;border-radius:50%;background-color:#fff}.checkbox{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;height:28px;cursor:default}.checkbox__container{position:relative;width:32px;height:32px}.checkbox__label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-top:4px;font-family:Inter,sans-serif;line-height:16px;font-weight:400;font-size:11px;letter-spacing:.005em}.checkbox__box{position:absolute;width:0;height:0;opacity:0}.checkbox__box:checked~.checkbox__mark{border:1px solid #18a0fb;background-color:#18a0fb}.checkbox__box:checked~.checkbox__mark:after{display:block}.checkbox__mark{position:absolute;top:10px;left:10px;width:12px;height:12px;border:1px solid #000;border-radius:2px;background-color:#fff}.checkbox__mark:after{position:absolute;width:12px;height:12px;content:'';background-image:url(data:image/svg+xml;utf8,%3Csvg%20fill%3D%22none%22%20height%3D%227%22%20viewBox%3D%220%200%208%207%22%20width%3D%228%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20clip-rule%3D%22evenodd%22%20d%3D%22m1.17647%201.88236%201.88235%201.88236%203.76471-3.76472%201.17647%201.17648-4.94118%204.9412-3.05882-3.05884z%22%20fill%3D%22%23fff%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E);background-repeat:no-repeat;background-position:1px 2px}.divider{display:block;width:100%;height:1px;margin:8px 0 8px 0;padding:0;background-color:#e5e5e5} 3 | 4 | /* NEW FIGMA UI PARTIALS */ 5 | .icon--distribute-horizontal-spacing { 6 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='none' height='32' width='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23000'%3E%3Cpath d='M11 22.5v-13h-1v13zM22 9.5v13h-1v-13zM17 12.5v7h-2v-7z'/%3E%3C/g%3E%3C/svg%3E"); 7 | } 8 | 9 | .icon--distribute-vertical-spacing { 10 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='none' height='32' width='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23000'%3E%3Cpath d='M9.5 10h13v1h-13zM12.5 15h7v2h-7zM22.5 21h-13v1h13z'/%3E%3C/g%3E%3C/svg%3E"); 11 | } 12 | 13 | .icon--frame { 14 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg fill='none' height='32' width='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath clip-rule='evenodd' d='M11 24v-3H8v-1h3v-8H8v-1h3V8h1v3h8V8h1v3h3v1h-3v8h3v1h-3v3h-1v-3h-8v3zm9-4v-8h-8v8z' fill='%23000' fill-rule='evenodd'/%3E%3C/svg%3E"); 15 | } 16 | -------------------------------------------------------------------------------- /src/ui/components/display/DisplayComponent.css: -------------------------------------------------------------------------------- 1 | c-display[hidden] { display: block!important; height: 0; padding: 0 16px; opacity: 0.4; border: none; pointer-events: none; } 2 | 3 | c-display { display: block; padding: 8px 16px 16px; background: var(--color-decorator-soft); border-bottom: solid 1px var(--color-decorator-regular); transition: all 0.25s ease-out; } 4 | c-display picture { position: relative; width: 48px; height: 48px; border-radius: 8px; margin-right: 16px; float: left; overflow: hidden; } 5 | c-display picture img { width: 100%; } 6 | c-display picture:after { content: ""; position: absolute; left: 0; top: 0; right: 0; bottom: 0; border: solid 1px var(--color-decorator-regular); border-radius: 8px; } 7 | 8 | c-display section { overflow: hidden; } 9 | c-display em { display: block; font: var(--type-small-normal); color: var(--color-text-tertiary); margin: 0 0 8px; } 10 | c-display h1 { font: var(--type-large-bold); line-height: 16px; color: var(--color-text-primary); margin-bottom: 4px; } 11 | c-display p { font: var(--type-medium-normal); color: var(--color-text-secondary); margin-bottom: 8px; } 12 | c-display a { font: var(--type-medium-medium); color: var(--color-interactive-positive); } 13 | -------------------------------------------------------------------------------- /src/ui/components/display/DisplayComponent.js: -------------------------------------------------------------------------------- 1 | import './DisplayComponent.css' 2 | import Element from 'src/ui/Element' 3 | import DisplayNetwork from 'src/utils/DisplayNetwork' 4 | import Tracking from 'src/utils/Tracking' 5 | 6 | const PLUGIN_NAME = 'super_tidy' 7 | 8 | class DisplayComponent extends Element { 9 | 10 | beforeMount() { 11 | // avoid display without proper data 12 | if (this.attrs.lastshowndate == 'undefined') return 13 | 14 | DisplayNetwork.getAvailableAd(this.attrs.lastshowndate, this.attrs.lastshownimpression) 15 | .then(ad => { 16 | // if we have an available ad, then render and display it 17 | if (!!ad) { 18 | ad.link = ad.link + '&utm_campaign='+PLUGIN_NAME 19 | this.data.ad = ad 20 | this.showDisplay() 21 | } 22 | }) 23 | } 24 | 25 | showDisplay() { 26 | this.removeAttribute('hidden') 27 | Tracking.track('displayImpression', { campaign: this.data.ad.tracking }) 28 | parent.postMessage({ pluginMessage: { type: 'displayImpression' } }, '*') 29 | } 30 | 31 | bind() { 32 | this.addEventListener('click', e => { 33 | if (e.target.getAttribute('data-trigger') == 'cta') { 34 | Tracking.track('displayClick', { campaign: this.data.ad.tracking }) 35 | } 36 | }) 37 | } 38 | 39 | render() { 40 | if (!this.data.ad) return 41 | 42 | let ad = this.data.ad 43 | return ` 44 | Sponsored 45 | 46 | 47 | ${ad.headline} 48 | ${ad.description} 49 | ${this.data.ad.cta} 50 | 51 | ` 52 | } 53 | } 54 | 55 | customElements.define('c-display', DisplayComponent) 56 | -------------------------------------------------------------------------------- /src/ui/components/select/SelectComponent.css: -------------------------------------------------------------------------------- 1 | c-select { 2 | position: relative; 3 | display: block; 4 | -webkit-box-sizing: border-box; 5 | box-sizing: border-box; 6 | width: 100%; 7 | cursor: default; 8 | } 9 | 10 | .select-menu[disabled] { 11 | opacity: 0.3; 12 | } 13 | 14 | c-select button { 15 | font: var(--type-small-normal); 16 | position: relative; 17 | display: flex; 18 | justify-content: flex-start; 19 | align-items: center; 20 | width: 100%; 21 | height: 30px; 22 | margin: 1px 0 1px 0; 23 | padding: 6px 0 6px 8px; 24 | cursor: default; 25 | color: rgba(0, 0, 0, 0.8); 26 | border-radius: 2px; 27 | } 28 | 29 | c-select button:hover { box-shadow: 0 0 0 1px var(--color-separator) inset; } 30 | c-select button:focus, c-select button:active { 31 | box-shadow: 0 0 0 2px var(--color-interactive-positive) inset; 32 | outline: none; 33 | } 34 | c-select button .c-icon { pointer-events: none; } 35 | c-select button .c-icon.default path { fill: var(--color-text-tertiary); } 36 | c-select button .c-icon.hover { opacity: 0; position: absolute; right: 4px; top: 7px; } 37 | c-select button .c-icon.hover path { fill: var(--color-text-primary); } 38 | c-select:hover button .c-icon.default { opacity: 0; } 39 | c-select:hover button .c-icon.hover { opacity: 1; } 40 | 41 | c-select[reverse] button { flex-direction: row-reverse; padding: 6px 8px 6px 0; } 42 | c-select[reverse] button .c-icon.default { margin: 0 4px 0 0; } 43 | c-select[reverse] button .c-icon.hover { right: auto; left: 4px; } 44 | 45 | c-select ul { 46 | position: absolute; 47 | z-index: 2; 48 | display: flex; 49 | flex-direction: column; 50 | width: 100%; 51 | margin: 0; 52 | padding: 8px 0 8px 0; 53 | border-radius: 2px; 54 | background-color: var(--color-text-primary); 55 | box-shadow: 0 5px 17px rgba(0, 0, 0, 0.2), 0 2px 7px rgba(0, 0, 0, 0.15); 56 | } 57 | 58 | c-select li { 59 | font: var(--type-medium-normal); 60 | display: flex; 61 | align-items: center; 62 | height: 24px; 63 | padding: 0 8px; 64 | color: #FFFFFF; 65 | } 66 | c-select li:hover { background-color: var(--color-interactive-positive); } 67 | 68 | c-select li.separator { pointer-events: none; height: 1px; margin: 8px 0; background-color: rgba(255, 255, 255, 0.2); } 69 | c-select li .c-icon { display: inline-block; opacity: 0; margin-right: 8px; pointer-events: none; } 70 | c-select li .c-icon path { fill: #FFFFFF; stroke: #FFFFFF; } 71 | c-select li[selected] .c-icon { opacity: 1; } 72 | -------------------------------------------------------------------------------- /src/ui/components/select/SelectComponent.js: -------------------------------------------------------------------------------- 1 | import './SelectComponent.css' 2 | import Element from 'src/ui/Element' 3 | import 'src/ui/icons/IconShow' 4 | import 'src/ui/icons/IconCheck' 5 | 6 | class SelectComponent extends Element { 7 | 8 | toggleList() { 9 | let list = this.find('[data-select=list]'); 10 | (list.hasAttribute('hidden')) ? list.removeAttribute('hidden') : list.setAttribute('hidden', '') 11 | } 12 | 13 | selectItem(item) { 14 | let value = '' 15 | let group = item.getAttribute('data-group') 16 | let isToggle = (item.hasAttribute('data-toggle')) 17 | 18 | if (!isToggle) { 19 | let list = this.findAll(`[data-select=item][data-group=${group}]`) 20 | value = item.getAttribute('data-value') 21 | list.forEach(node => node.removeAttribute('selected')) 22 | item.setAttribute('selected', '') 23 | if (!this.attrs.label) this.find('[data-select=label]').innerHTML = item.getAttribute('data-label') 24 | } else { 25 | let values = JSON.parse(item.getAttribute('data-toggle')) 26 | let isSelected = (item.hasAttribute('selected')); 27 | value = (isSelected) ? `${values[0]}` : `${values[1]}`; 28 | (isSelected) ? item.removeAttribute('selected') : item.setAttribute('selected', ''); 29 | } 30 | this.toggleList() 31 | this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: value, group: group } })) 32 | } 33 | 34 | onClick(e) { 35 | if (e.target.getAttribute('data-trigger') == 'open') { 36 | this.toggleList() 37 | } 38 | 39 | if (e.target.getAttribute('data-select') == 'item') { 40 | this.selectItem(e.target) 41 | } 42 | } 43 | 44 | renderItem(item) { 45 | let isSelected = (item.hasAttribute('selected')) 46 | let hasGroup = (item.hasAttribute('group')) 47 | let hasToggle = (item.hasAttribute('toggle')) 48 | let toggleValues = (item.getAttribute('toggle')) 49 | let groupName = item.getAttribute('group') 50 | let valueAttr = (hasToggle) ? `data-toggle="${toggleValues}"` : `data-value="${item.value}"`; 51 | let putSeparator = (hasGroup && this.lastGroup != '' && this.lastGroup != groupName) 52 | if (hasGroup) this.lastGroup = groupName 53 | 54 | return ` 55 | ${(putSeparator) ? `` : ''} 56 | 57 | 58 | ${item.innerText} 59 | 60 | ` 61 | } 62 | 63 | render() { 64 | this.lastGroup = '' 65 | let defaults = Array.from(this.findAll('option')) 66 | let defaultSelection = defaults.reduce((buffer, item) => { 67 | if (item.hasAttribute('selected')) buffer += item.innerText 68 | return buffer 69 | }, '') 70 | 71 | return` 72 | 73 | ${ this.attrs.label || defaultSelection || 'Select an option'} 74 | 75 | 76 | 77 | 78 | ${defaults.reduce((buffer, item) => { 79 | buffer += this.renderItem(item) 80 | return buffer 81 | }, '')} 82 | 83 | ` 84 | } 85 | } 86 | 87 | customElements.define('c-select', SelectComponent) 88 | -------------------------------------------------------------------------------- /src/ui/components/toolbar/ToolbarComponent.css: -------------------------------------------------------------------------------- 1 | c-toolbar { 2 | position: relative; 3 | z-index: 5; 4 | display: block; 5 | padding: 0 16px 0 8px; 6 | overflow: hidden; 7 | border-bottom: solid 1px var(--color-separator); 8 | } 9 | c-toolbar nav, c-toolbar header { overflow: hidden; } 10 | 11 | c-toolbar nav ul { display: flex; flex-direction: row; float: left; } 12 | c-toolbar nav li a { 13 | display: block; 14 | padding: 12px 8px; 15 | font: var(--type-medium-bold); 16 | color: var(--color-text-tertiary); 17 | transition: 0.05s ease-in color; 18 | } 19 | c-toolbar nav .active a, c-toolbar nav li a:hover { color: var(--color-text-primary); text-decoration: none; } 20 | -------------------------------------------------------------------------------- /src/ui/components/toolbar/ToolbarComponent.js: -------------------------------------------------------------------------------- 1 | import './ToolbarComponent.css' 2 | import Router from 'src/utils/Router' 3 | import Tracking from 'src/utils/Tracking' 4 | import Element from 'src/ui/Element' 5 | 6 | class ToolbarComponent extends Element { 7 | 8 | beforeMount() { 9 | this.data.currentView = Router.url 10 | } 11 | 12 | onClick(e) { 13 | Tracking.track('clickToolbarLink', { url: event.target.getAttribute('href') }) 14 | } 15 | 16 | bind() { 17 | super.bind() 18 | Router.on('change:url', url => this.data.currentView = url) 19 | } 20 | 21 | render() { 22 | let currentView = this.data.currentView 23 | let isIndex = (currentView === Router.routes.index || currentView === '' || typeof currentView === 'undefined') 24 | let isPreferences = (currentView === Router.routes.preferences) 25 | 26 | return` 27 | 28 | 29 | Actions 30 | Preferences 31 | 32 | 33 | ` 34 | } 35 | } 36 | 37 | customElements.define('c-toolbar', ToolbarComponent) 38 | -------------------------------------------------------------------------------- /src/ui/icons/IconCheck.js: -------------------------------------------------------------------------------- 1 | import Element from 'src/ui/Element' 2 | 3 | class IconCheck extends Element { 4 | render() { 5 | return` 6 | 7 | 8 | 9 | ` 10 | } 11 | } 12 | 13 | customElements.define('i-check', IconCheck) 14 | -------------------------------------------------------------------------------- /src/ui/icons/IconShow.js: -------------------------------------------------------------------------------- 1 | import Element from 'src/ui/Element' 2 | 3 | class IconShow extends Element { 4 | render() { 5 | return` 6 | 7 | 8 | 9 | 10 | ` 11 | } 12 | } 13 | 14 | customElements.define('i-show', IconShow) 15 | -------------------------------------------------------------------------------- /src/ui/views/form/FormView.css: -------------------------------------------------------------------------------- 1 | /* PLUGIN UI */ 2 | * { border: 0; padding: 0; margin: 0; } 3 | .hidden { display: none!important; } 4 | form { padding: 16px; } 5 | fieldset { margin-bottom: 0px; } 6 | p.type { margin-bottom: 16px; } 7 | p.type--11-pos-bold { margin-bottom: 8px; } 8 | 9 | .extra-options { padding-left: 40px; margin-bottom: 24px; } 10 | .extra-options input { width: 128px; } 11 | 12 | .button { margin-top: 16px; } 13 | 14 | v-form .empty-selection { 15 | text-align: center; 16 | padding: 0 24px; 17 | text-align: center; 18 | padding: 0 24px; 19 | display: flex; 20 | flex-direction: column; 21 | height: 100%; 22 | justify-content: center; 23 | } 24 | v-form .empty-selection h1 { margin-bottom: 8px; } 25 | 26 | 27 | 28 | .show-preferences { margin: -4px; } 29 | .show-preferences a { display: block; border-radius: 4px; padding: 8px; border: solid 2px transparent; } 30 | .show-preferences a:hover { background: var(--color-decorator-soft); text-decoration: none; } 31 | .show-preferences a:active { border-color: var(--color-interactive-positive); } 32 | .show-preferences .icon { display: block; float: left; margin-right: 12px; } 33 | .show-preferences h1, .show-preferences p { overflow: hidden; } 34 | .show-preferences h1 { font: var(--type-small-bold); color: var(--color-text-primary); } 35 | .show-preferences p { font: var(--type-small-normal); color: var(--color-text-secondary); margin: 0; } 36 | -------------------------------------------------------------------------------- /src/ui/views/form/FormView.js: -------------------------------------------------------------------------------- 1 | import './FormView.css' 2 | 3 | import Element from 'src/ui/Element' 4 | import Tracking from "src/utils/Tracking" 5 | import Router from 'src/utils/Router' 6 | 7 | class FormView extends Element { 8 | 9 | handleEmptyState(selection) { 10 | if (selection.length == 0) { 11 | this.find('[data-select=empty]').removeAttribute('hidden') 12 | this.find('[data-select=form]').setAttribute('hidden', '') 13 | } else { 14 | this.find('[data-select=empty]').setAttribute('hidden', '') 15 | this.find('[data-select=form]').removeAttribute('hidden') 16 | } 17 | } 18 | 19 | bind() { 20 | window.addEventListener('message', e => { 21 | let msg = event.data.pluginMessage 22 | if (msg.type == 'selection') { 23 | let selection = event.data.pluginMessage.selection 24 | this.handleEmptyState(selection) 25 | } 26 | }) 27 | 28 | // PLUGIN UI CONTROLS 29 | var form = document.getElementById('actions') 30 | var renaming_check = document.getElementById('renaming_check') 31 | var reorder_check = document.getElementById('reorder_check') 32 | var tidy_check = document.getElementById('tidy_check') 33 | var pager_check = document.getElementById('pager_check') 34 | var tidy = document.getElementById('tidy') 35 | 36 | function applySuperTidy() { 37 | var renamingEnabled = renaming_check.checked; 38 | var reorderEnabled = reorder_check.checked; 39 | var tidyEnabled = tidy_check.checked; 40 | var pagerEnabled = pager_check.checked; 41 | var options = { 42 | renaming: renamingEnabled, 43 | reorder: reorderEnabled, 44 | tidy: tidyEnabled, 45 | pager: pagerEnabled 46 | } 47 | 48 | Tracking.track('clickApply', options) 49 | parent.postMessage({ pluginMessage: { type: 'tidy', options: options } }, '*') 50 | } 51 | 52 | 53 | form.onsubmit = (e) => { 54 | applySuperTidy() 55 | e.preventDefault() 56 | } 57 | } 58 | 59 | render() { 60 | return ` 61 | 62 | Empty selection 63 | 64 | Select some layers first to start using Super Tidy. 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Renaming 76 | 77 | Rename your layers based on their position on the canvas. 000, 001, ... 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Reorder 91 | 92 | Reorder your layers on the sidebar based on their position on the canvas. 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | Tidy 106 | 107 | Align the layers on the canvas to a fixed spacing grid. 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Pager 121 | 122 | Creates a pagination for the given selection using the position in the canvas of each frame. The page number is written inside a layer with the name {current} by default. 123 | 124 | 125 | 126 | 127 | 128 | Run 129 | 130 | ` 131 | } 132 | } 133 | 134 | customElements.define('v-form', FormView) 135 | -------------------------------------------------------------------------------- /src/ui/views/preferences/PreferencesView.css: -------------------------------------------------------------------------------- 1 | v-preferences {} 2 | v-preferences form strong { font: var(--type-small-bold); color: var(--color-text-primary); display: block; margin-bottom: 4px; } 3 | v-preferences form p { font: var(--type-small-normal); color: var(--color-text-secondary); display: block; margin-bottom: 4px; } 4 | v-preferences label, v-preferences .fake-label { margin-bottom: 16px; display: block; } 5 | v-preferences label .input-icon { width: 88px; } 6 | 7 | v-preferences fieldset { overflow: hidden; padding: 2px; } 8 | v-preferences fieldset label { float: left } 9 | v-preferences .button.button--primary { margin-top: 0; } 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/ui/views/preferences/PreferencesView.js: -------------------------------------------------------------------------------- 1 | import './PreferencesView.css' 2 | 3 | import Element from 'src/ui/Element' 4 | import Tracking from "src/utils/Tracking" 5 | import Router from 'src/utils/Router' 6 | 7 | import 'src/ui/components/select/SelectComponent' 8 | 9 | class PreferencesView extends Element { 10 | 11 | savePreferences() { 12 | let x_spacing = this.find('#x_spacing').value 13 | let y_spacing = this.find('#y_spacing').value 14 | let starting_name = this.find('#starting_name').value 15 | let pager_variable = this.find('#pager_variable').value 16 | let wrap_instances = this.find('#wrap_instances').checked 17 | let rename_trategy = this.find('#rename_strategy [selected]').getAttribute('data-value') 18 | 19 | let preferences = { 20 | spacing: { x: parseInt(x_spacing), y: parseInt(y_spacing) }, 21 | start_name: starting_name, 22 | wrap_instances: wrap_instances, 23 | rename_strategy: rename_trategy, 24 | pager_variable: pager_variable 25 | } 26 | 27 | Tracking.track('clickSavePreferences', preferences) 28 | parent.postMessage({ pluginMessage: { type: 'preferences', preferences: preferences } }, '*') 29 | Router.navigate(Router.routes.index) 30 | } 31 | 32 | bind(e) { 33 | this.find('#preferences').addEventListener('submit', e => { 34 | this.savePreferences() 35 | e.preventDefault() 36 | }) 37 | } 38 | 39 | render() { 40 | return ` 41 | 42 | 43 | Grid spacing 44 | Spacing between frames applied when running the Tidy action. 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Wrap instances with frames 67 | When using the Tidy action, all instances will be wrapped with a frame of the same dimensions. 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | Pager variable 80 | When using the Pager action, all text layers named with the defined variable will be replaced with a number based on the parent frame order. 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | Starting frame number 92 | Renames your frames starting from the given number when using the Rename action. 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Rename strategy 104 | Merges or replaces your frame names with numbers based on their position on the canvas. Applied with the Rename action. 105 | 106 | 107 | Replace 108 | 109 | 110 | Merge 111 | 112 | 113 | 114 | 115 | Save 116 | 117 | 118 | ` 119 | } 120 | } 121 | 122 | customElements.define('v-preferences', PreferencesView) 123 | -------------------------------------------------------------------------------- /src/utils/DisplayNetwork.js: -------------------------------------------------------------------------------- 1 | let singleton = null 2 | const DAILY_INTERVAL = 86400000 3 | const WEEKLY_INTERVAL = DAILY_INTERVAL*7 4 | const MONTHLY_INTERVAL = WEEKLY_INTERVAL*4 5 | 6 | class DisplayNetwork { 7 | 8 | constructor() { 9 | this.root = 'https://figma-plugins-display-network.netlify.app/api.json' 10 | this.timeStampNow = Date.now() 11 | 12 | if (!singleton) singleton = this 13 | return singleton 14 | } 15 | 16 | getAds() { 17 | return fetch(this.root, { method: 'GET' }) 18 | } 19 | 20 | getAvailableAd(lastShownDate, lastShownImpression) { 21 | return new Promise((resolve, reject) => { 22 | this.getAds().then(response => { 23 | response.json().then(ads => { 24 | 25 | let availableAds = ads.reduce((prev, current) => { 26 | if (this.checkImpressionAvailability(parseInt(lastShownDate), parseInt(lastShownImpression), current)) { 27 | prev.push(current) 28 | } 29 | return prev 30 | }, []) 31 | 32 | resolve(availableAds[this.getRandomInt(0, availableAds.length)]) 33 | }) 34 | }).catch(error => console.log('Error getAvailableAd', error)) 35 | }) 36 | } 37 | 38 | getRandomInt(min, max) { 39 | min = Math.ceil(min); 40 | max = Math.floor(max); 41 | return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive 42 | } 43 | 44 | getAdInterval(ad) { 45 | if (WP_ENV == 'development') return 10000 46 | 47 | return parseInt(ad.impression_net_interval) 48 | } 49 | 50 | checkImpressionAvailability(lastShownDate, lastShownImpression, ad) { 51 | 52 | let adInterval = this.getAdInterval(ad) 53 | let availableTimeWindow = (!lastShownDate || lastShownDate + adInterval < this.timeStampNow) 54 | 55 | // Check if the ad is optimised for impression rather than time 56 | if (ad.impression_count) { 57 | let availableImpression = (lastShownImpression < parseInt(ad.impression_count)) 58 | 59 | if (availableTimeWindow && availableImpression || !availableTimeWindow && availableImpression) { 60 | // meanwhile there is impressions left, spend the impression 61 | return true 62 | } else 63 | if (availableTimeWindow && !availableImpression) { 64 | // when impressions limit is reached and time window is valid, reset the impression 65 | parent.postMessage({ pluginMessage: { type: 'resetImpression' } }, '*') 66 | return false 67 | } else { 68 | return false 69 | } 70 | } else { 71 | return availableTimeWindow 72 | } 73 | 74 | } 75 | } 76 | 77 | export default new DisplayNetwork() 78 | -------------------------------------------------------------------------------- /src/utils/Router.js: -------------------------------------------------------------------------------- 1 | import LEOObject from 'leo/object' 2 | 3 | let singleton = null 4 | class Router extends LEOObject { 5 | constructor() { 6 | super() 7 | this.url = window.location.hash 8 | this.routes = {} 9 | this.bind() 10 | 11 | if (!singleton) singleton = this 12 | return singleton 13 | } 14 | 15 | get root() { return '' } 16 | 17 | setup(routes) { 18 | this.routes = routes 19 | } 20 | 21 | updateURLBar(url) { 22 | window.location.hash = url 23 | } 24 | 25 | reload() { 26 | this.trigger('change:url', this.url) 27 | } 28 | 29 | navigate(url) { 30 | this.updateURLBar(url) 31 | } 32 | 33 | back() { 34 | window.history.back() 35 | } 36 | 37 | bind() { 38 | window.addEventListener('hashchange', (e) => this.url = window.location.hash) 39 | } 40 | } 41 | 42 | export default new Router() 43 | -------------------------------------------------------------------------------- /src/utils/Tracking.js: -------------------------------------------------------------------------------- 1 | import UAParser from 'ua-parser-js' 2 | 3 | const UUIDKey = 'UUID' 4 | let singleton = null 5 | 6 | class Tracking { 7 | constructor() { 8 | this.root = 'https://api.amplitude.com/httpapi' 9 | this.apiKey = '' 10 | this.userId = '' 11 | this.userProps = {} 12 | this.UUID = '' 13 | this.UA = '' 14 | this.hasSetup = false 15 | this.parser = new UAParser() 16 | 17 | if (!singleton) singleton = this 18 | return singleton 19 | } 20 | 21 | createUUID(a) { 22 | // See: https://github.com/amplitude/Amplitude-Javascript/blob/master/src/uuid.js 23 | var uuid = function(a) { 24 | return a // if the placeholder was passed, return 25 | ? ( // a random number from 0 to 15 26 | a ^ // unless b is 8, 27 | Math.random() // in which case 28 | * 16 // a random number from 29 | >> a / 4 // 8 to 11 30 | ).toString(16) // in hexadecimal 31 | : ( // or otherwise a concatenated string: 32 | [1e7] + // 10000000 + 33 | -1e3 + // -1000 + 34 | -4e3 + // -4000 + 35 | -8e3 + // -80000000 + 36 | -1e11 // -100000000000, 37 | ).replace( // replacing 38 | /[018]/g, // zeroes, ones, and eights with 39 | uuid // random hex digits 40 | ); 41 | } 42 | return uuid() 43 | } 44 | 45 | setup(apiKey, UUID) { 46 | this.apiKey = apiKey 47 | this.UUID = UUID 48 | this.UA = this.parser.getResult() 49 | this.hasSetup = true 50 | } 51 | 52 | track(event, props) { 53 | if (!this.hasSetup) return console.log('Missing Tracking.init(API_KEY, UUID) before Tracking.track()') 54 | if (WP_ENV == 'development') return console.log(event, props || {}) 55 | 56 | let evtObj = { 57 | user_id: this.userId, 58 | device_id: this.UUID, 59 | event_type: event, 60 | os_name: this.UA.os.name, 61 | os_version: this.UA.os.version, 62 | platform: `${this.UA.browser.name} ${this.UA.browser.major}`, 63 | language: navigator.language, 64 | user_properties: this.userProps, 65 | event_properties: props, 66 | time: Math.floor(Date.now()) 67 | } 68 | 69 | var data = new FormData() 70 | data.append( "api_key", this.apiKey) 71 | data.append( "event", JSON.stringify(evtObj)) 72 | 73 | fetch(this.root, { 74 | method: 'POST', 75 | body: data 76 | }) 77 | } 78 | 79 | } 80 | 81 | export default new Tracking() 82 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const secrets = require('./secrets.json') 2 | const webpack = require('webpack') 3 | const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | const path = require('path') 6 | 7 | module.exports = (env, argv) => ({ 8 | mode: argv.mode === 'production' ? 'production' : 'development', 9 | 10 | // This is necessary because Figma's 'eval' works differently than normal eval 11 | devtool: argv.mode === 'production' ? false : 'inline-source-map', 12 | 13 | entry: { 14 | ui: './src/App.js', // The entry point for your UI code 15 | core: './src/Core.js', // The entry point for your plugin code 16 | }, 17 | 18 | module: { 19 | rules: [ 20 | // Enables including CSS by doing "import './file.css'" in your TypeScript code 21 | { test: /\.css$/, loader: [{ loader: 'style-loader' }, { loader: 'css-loader' }] }, 22 | 23 | { test: /\.(png|jpg|gif|webp)$/, loader: [{ loader: 'url-loader' }] }, 24 | 25 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } 26 | ] 27 | }, 28 | 29 | // Webpack tries these extensions for you if you omit the extension like "import './file'" 30 | resolve: { 31 | extensions: ['.js'], 32 | alias: { 33 | src: path.resolve(__dirname, 'src/'), 34 | leo: path.resolve(__dirname, 'node_modules/@basiclines/leo/dist/'), 35 | } 36 | }, 37 | 38 | output: { 39 | filename: '[name].js', 40 | path: path.resolve(__dirname, 'dist'), // Compile into a folder called "dist" 41 | }, 42 | 43 | // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it 44 | plugins: [ 45 | new webpack.DefinePlugin({ 46 | 'WP_ENV': JSON.stringify(process.env.NODE_ENV), 47 | 'WP_AMPLITUDE_KEY': JSON.stringify(secrets.AMPLITUDE_KEY) 48 | }), 49 | new HtmlWebpackPlugin({ 50 | templateContent: ``, 51 | filename: 'ui.html', 52 | inlineSource: '.(js)$', 53 | chunks: ['ui'], 54 | }), 55 | new HtmlWebpackInlineSourcePlugin() 56 | ] 57 | }) 58 | --------------------------------------------------------------------------------
${ad.description}
64 | Select some layers first to start using Super Tidy. 65 |
77 | Rename your layers based on their position on the canvas. 000, 001, ... 78 |
92 | Reorder your layers on the sidebar based on their position on the canvas. 93 |
107 | Align the layers on the canvas to a fixed spacing grid. 108 |
122 | Creates a pagination for the given selection using the position in the canvas of each frame. The page number is written inside a layer with the name {current} by default. 123 |
Spacing between frames applied when running the Tidy action.
When using the Tidy action, all instances will be wrapped with a frame of the same dimensions.
When using the Pager action, all text layers named with the defined variable will be replaced with a number based on the parent frame order.
Renames your frames starting from the given number when using the Rename action.
Merges or replaces your frame names with numbers based on their position on the canvas. Applied with the Rename action.