From 3c68f9f7cf640414dac01ff8d5a97feac67b17ba Mon Sep 17 00:00:00 2001 From: Christian Heine Date: Sun, 23 Aug 2020 16:37:14 +0800 Subject: [PATCH] Add option to SVG export to clipboard --- .gitignore | 1 + src/manifest.json | 7 ++ src/plugin.js | 230 ++++++++++++++++++++++++++++------------------ 3 files changed, 149 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index e9f4a5f..54ca2b4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .DS_Store SVGO Compressor.sketchplugin src/svgo-plugins.js +.idea diff --git a/src/manifest.json b/src/manifest.json index 551bd84..92f0667 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -15,6 +15,13 @@ "Export": "compress" } } + }, + { + "name": "Copy optimized SVG code", + "identifier": "svgCopyCompressed", + "script": "./plugin.js", + "handler":"svgCopyCompressed", + "shortcut": "cmd ctrl shift s" } ] } diff --git a/src/plugin.js b/src/plugin.js index 399271c..b2aec5d 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,144 +1,196 @@ -import getConfig, { svgoJSONFilePath } from './getConfig' -import path from '@skpm/path' -import fs from '@skpm/fs' -import dialog from '@skpm/dialog' -import {spawnSync} from '@skpm/child_process' -import svgo from 'svgo' -import svgoPlugins from './svgo-plugins' -import UI from 'sketch/ui' +import getConfig, { svgoJSONFilePath } from "./getConfig"; +import path from "@skpm/path"; +import fs from "@skpm/fs"; +import dialog from "@skpm/dialog"; +import { spawnSync } from "@skpm/child_process"; +import svgo from "svgo"; +import svgoPlugins from "./svgo-plugins"; +import UI from "sketch/ui"; +import DOM from "sketch/dom"; export function openSettings() { + // Plugin was run from the menu, so let's open the settings window - const response = dialog.showMessageBox({ - buttons: ['Edit SVGO Settings…', 'Reset SVGO Settings', 'Cancel'], + const response = dialog.showMessageBoxSync({ + buttons: ["Edit SVGO Settings…", "Reset SVGO Settings", "Cancel"], message: "About SVGO Compressor", detail: "This Plugin uses SVGO to compress SVG assets exported from Sketch.\n\nIt works automatically whenever you export to SVG, so you don’t need to do anything special. Just work on your design as always, and enjoy smaller & cleaner SVG files.\n\nIf for some reason you’re not happy with the default options, you can edit the svgo.json file in the Application Support folder for Sketch.\n" - }) - + }); + if (response === 0) { // open the config - spawnSync("/usr/bin/open", [svgoJSONFilePath]) + spawnSync("/usr/bin/open", [svgoJSONFilePath]); + UI.alert("SVGO Compressor", "Opening config file in default editor..."); } else if (response === 1) { // reset config - fs.writeFileSync(svgoJSONFilePath, JSON.stringify(require('./defaultConfig'), null, ' '), 'utf8') + fs.writeFileSync(svgoJSONFilePath, JSON.stringify(require("./defaultConfig"), null, " "), "utf8"); + UI.message("SVGO Compressor configuration has been reset"); } } -export function compress(context) { - const svgoJSON = getConfig() - if (typeof svgoJSON.enabled !== 'undefined' && !svgoJSON.enabled) { - return +export function svgCopyCompressed(context) { + const svgCompressor = setupSVGO(); + + const document = DOM.getSelectedDocument(); + const selectedLayers = document.selectedLayers; + const selectedCount = selectedLayers.length; + + if (selectedCount === 0) { + return UI.alert("SVGO Compressor", "No layers selected"); + } + if (selectedCount > 1) { + return UI.alert("SVGO Compressor", "Please select exactly one layer"); } + + const buffer = DOM.export(selectedLayers.layers[0], { + formats: "svg", + output: false + }); + + const svgString = buffer.toString(); + + return svgCompressor.optimize(svgString).then(result => { + + const pasteBoard = NSPasteboard.generalPasteboard(); + pasteBoard.declareTypes_owner([NSPasteboardTypeString], null); + pasteBoard.setString_forType(result.data, NSPasteboardTypeString); + + UI.message("Copied optimized SVG to clipboard"); + + }) + .catch((err) => { + log(err); + UI.message(err.message); + }); +} - const floatPrecision = typeof svgoJSON.floatPrecision !== 'undefined' +function setupSVGO() { + const svgoJSON = getConfig(); + + if (typeof svgoJSON.enabled !== "undefined" && !svgoJSON.enabled) { + return; + } + + const floatPrecision = typeof svgoJSON.floatPrecision !== "undefined" ? Number(svgoJSON.floatPrecision) : undefined; - - const parsedSVGOPlugins = [] + + const parsedSVGOPlugins = []; svgoJSON.plugins.forEach(item => { - if (typeof item.enabled !== 'undefined' && !item.enabled) { - return + if (typeof item.enabled !== "undefined" && !item.enabled) { + return; } - let plugin = svgoPlugins[item.name] + let plugin = svgoPlugins[item.name]; if (item.path) { try { - const loadedPlugin = coscript.require(path.join(String(MSPluginManager.mainPluginsFolderURL().path()), item.path)) - + const loadedPlugin = coscript.require(path.join(String(MSPluginManager.mainPluginsFolderURL().path()), item.path)); + // loadedPlugin is an NSDictionary so if we try to set something on it, // it will crash. Instead we move the values to a proper JS object. var keys = Object.keys(loadedPlugin); plugin = {}; - Object.keys(loadedPlugin).forEach((k) => { plugin[k] = loadedPlugin[k] }) + Object.keys(loadedPlugin).forEach((k) => { plugin[k] = loadedPlugin[k]; }); if (loadedPlugin.params) { - plugin.params = {} - Object.keys(loadedPlugin.params).forEach((k) => { plugin.params[k] = loadedPlugin.params[k] }) + plugin.params = {}; + Object.keys(loadedPlugin.params).forEach((k) => { plugin.params[k] = loadedPlugin.params[k]; }); } } catch (err) { - log(err) + log(err); } } if (!plugin) { - log('Plugin not found: ' + (item.name || item.path)) - return + log("Plugin not found: " + (item.name || item.path)); + return; } - if (svgoJSON.debug) log('Enabled plugin: ' + (item.name || item.path)) - plugin.pluginName = item.name - plugin.active = true + if (svgoJSON.debug) log("Enabled plugin: " + (item.name || item.path)); + plugin.pluginName = item.name; + plugin.active = true; if (plugin.params) { // Plugin supports params - + // Set floatPrecision across all the plugins - if (floatPrecision && 'floatPrecision' in plugin.params) { - plugin.params.floatPrecision = floatPrecision + if (floatPrecision && "floatPrecision" in plugin.params) { + plugin.params.floatPrecision = floatPrecision; } - if (svgoJSON.debug) log('—› default params: ' + JSON.stringify(plugin.params, null, 2)) + if (svgoJSON.debug) log("—› default params: " + JSON.stringify(plugin.params, null, 2)); } if (item.params != null) { - if (typeof plugin.params === 'undefined') { - plugin.params = {} + if (typeof plugin.params === "undefined") { + plugin.params = {}; } for (var attrname in item.params) { - plugin.params[attrname] = item.params[attrname] + plugin.params[attrname] = item.params[attrname]; } - if (svgoJSON.debug) log('—› resulting params: ' + JSON.stringify(plugin.params, null, 2)) + if (svgoJSON.debug) log("—› resulting params: " + JSON.stringify(plugin.params, null, 2)); } - parsedSVGOPlugins.push([plugin]) - }) + parsedSVGOPlugins.push([plugin]); + }); + + if (typeof svgoJSON.full === "undefined") { svgoJSON.full = true; } + if (typeof svgoJSON.multipass === "undefined") { svgoJSON.multipass = true; } + if (typeof svgoJSON.pretty === "undefined") { svgoJSON.pretty = true; } + if (typeof svgoJSON.indent === "undefined") { svgoJSON.indent = 2; } + + const svgCompressor = new svgo({ + full: svgoJSON.full, + js2svg: { + pretty: svgoJSON.pretty, + indent: svgoJSON.indent + }, + plugins: parsedSVGOPlugins, + multipass: svgoJSON.multipass + }); + + if (svgoJSON.debug) log("Let‘s go…"); + + return svgCompressor; +} - var exports = context.actionContext.exports - var filesToCompress = [] - for (var i=0; i < exports.length; i++) { - var currentExport = exports[i] - if (currentExport.request.format() == 'svg') { - filesToCompress.push(currentExport.path) +export function compress(context) { + + var exports = context.actionContext.exports; + var filesToCompress = []; + for (var i = 0; i < exports.length; i++) { + var currentExport = exports[i]; + if (currentExport.request.format() == "svg") { + filesToCompress.push(currentExport.path); } } - + if (filesToCompress.length > 0) { - if (svgoJSON.debug) log('Let‘s go…') - let originalTotalSize = 0 - let compressedTotalSize = 0 - if (typeof svgoJSON.full === 'undefined') { svgoJSON.full = true } - if (typeof svgoJSON.multipass === 'undefined') { svgoJSON.multipass = true } - if (typeof svgoJSON.pretty === 'undefined') { svgoJSON.pretty = true } - if (typeof svgoJSON.indent === 'undefined') { svgoJSON.indent = 2 } - const svgCompressor = new svgo({ - full: svgoJSON.full, - js2svg: { - pretty: svgoJSON.pretty, - indent: svgoJSON.indent - }, - plugins: parsedSVGOPlugins, - multipass: svgoJSON.multipass - }) + var svgCompressor = setupSVGO(); + + let originalTotalSize = 0; + let compressedTotalSize = 0; + Promise.all(filesToCompress.map(currentFile => { - const svgString = fs.readFileSync(currentFile, 'utf8') - originalTotalSize += svgString.length - + const svgString = fs.readFileSync(currentFile, "utf8"); + originalTotalSize += svgString.length; + // Hacks for plugins svgCompressor.config.plugins.forEach(([plugin]) => { // cleanupIDs if (plugin.pluginName == "cleanupIDs") { - const parts = currentFile.split('/') - var prefix = parts[parts.length - 1].replace('.svg', '').replace(/\s+/g, '-').toLowerCase() + "-" - if (svgoJSON.debug) log('Setting cleanupIDs prefix to: ' + prefix) - plugin.params['prefix'] = prefix + const parts = currentFile.split("/"); + var prefix = parts[parts.length - 1].replace(".svg", "").replace(/\s+/g, "-").toLowerCase() + "-"; + if (svgoJSON.debug) log("Setting cleanupIDs prefix to: " + prefix); + plugin.params["prefix"] = prefix; } - }) - + }); + return svgCompressor.optimize(svgString).then(result => { - compressedTotalSize += result.data.length - fs.writeFileSync(currentFile, result.data, 'utf8') - }) + compressedTotalSize += result.data.length; + fs.writeFileSync(currentFile, result.data, "utf8"); + }); })).then(() => { - var compressionRatio = (100 - ((compressedTotalSize * 100) / originalTotalSize)).toFixed(2) - var msg = filesToCompress.length + " SVG files compressed by " + compressionRatio + "%, from " + originalTotalSize + " bytes to " + compressedTotalSize + " bytes." - log(msg) - UI.message(msg) - }) - .catch((err) => { - log(err) - UI.message(err.message) - }) + var compressionRatio = (100 - ((compressedTotalSize * 100) / originalTotalSize)).toFixed(2); + var msg = filesToCompress.length + " SVG files compressed by " + compressionRatio + "%, from " + originalTotalSize + " bytes to " + compressedTotalSize + " bytes."; + log(msg); + UI.message(msg); + }) + .catch((err) => { + log(err); + UI.message(err.message); + }); } }