Commit 704a87ac authored by leadream's avatar leadream

my first plugin is born

parents
node_modules
{
"git.ignoreLimitWarning": true
}
\ No newline at end of file
# Figma Icon Automation Plugin
Figma Icon Automation is a plugin that helps you synchronize your icons' SVG code to a Github Repository, Then the Github Action will convert them to React code and update to NPM automatically.
\ No newline at end of file
export const base = 'https://api.github.com'
export const getContent = (filePath, githubData) => {
return fetch(`${base}/repos/${githubData.owner}/${githubData.name}/contents/${filePath}`, {
headers: {
'content-type': 'application/json'
}
})
.then(response => response.json())
.then(res => (
res.sha ?
{sha: res.sha, contents: JSON.parse(window.atob(res.content))} :
{}
))
}
export const getCommit = (githubData) => {
return fetch(`${base}/repos/${githubData.owner}/${githubData.name}/commits/refs/heads/master`, {
headers: {
'content-type': 'application/json'
}
})
.then(response => response.json())
}
export const createBranch = (sha, githubData) => {
const branchName = `figma-update-${(new Date()).getTime()}`
const body = { ref: `refs/heads/${branchName}`, sha }
return fetch(`${base}/repos/${githubData.owner}/${githubData.name}/git/refs`, {
headers: {
'content-type': 'application/json',
'Authorization': `token ${githubData.githubToken}`
},
body: JSON.stringify(body),
method: 'POST'
})
.then(response => response.json())
}
export const updatePackage = (message, sha, contents, branch, githubData) => {
const content = window.btoa(JSON.stringify(contents, null, 2))
const body = JSON.stringify({ branch, sha, content, message })
return fetch(`${base}/repos/${githubData.owner}/${githubData.name}/contents/package.json`, {
headers: {
'content-type': 'application/json',
'Authorization': `token ${githubData.githubToken}`
},
body,
method: 'PUT'
})
.then(response => response.json())
}
export const createPullRequest = (title, content, branchName, githubData) => {
const body = {
title,
body: content,
head: `${githubData.owner}:${branchName}`,
base: "master"
}
return fetch(`${base}/repos/${githubData.owner}/${githubData.name}/pulls`, {
headers: {
'content-type': 'application/json',
'Authorization': `token ${githubData.githubToken}`
},
body: JSON.stringify(body),
method: 'POST'
})
.then(response => response.json())
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./src/code.ts");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./src/code.ts":
/*!*********************!*\
!*** ./src/code.ts ***!
\*********************/
/*! no static exports found */
/***/ (function(module, exports) {
figma.showUI(__html__, { width: 320, height: 320 });
// get github settings
function getGithubSettings() {
return figma.clientStorage.getAsync('githubData');
}
// set github settings
function setGithubSettings(data) {
figma.clientStorage.setAsync('githubData', data);
}
// send github data to UI
function init() {
getGithubSettings()
.then(githubData => {
figma.ui.postMessage({ type: 'githubDataGot', githubData });
});
}
figma.ui.onmessage = msg => {
switch (msg.type) {
case 'setGithubData':
setGithubSettings(msg.githubData);
break;
case 'cancel':
figma.closePlugin();
break;
}
};
init();
/***/ })
/******/ });
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vc3JjL2NvZGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtRQUFBO1FBQ0E7O1FBRUE7UUFDQTs7UUFFQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTs7UUFFQTtRQUNBOztRQUVBO1FBQ0E7O1FBRUE7UUFDQTtRQUNBOzs7UUFHQTtRQUNBOztRQUVBO1FBQ0E7O1FBRUE7UUFDQTtRQUNBO1FBQ0EsMENBQTBDLGdDQUFnQztRQUMxRTtRQUNBOztRQUVBO1FBQ0E7UUFDQTtRQUNBLHdEQUF3RCxrQkFBa0I7UUFDMUU7UUFDQSxpREFBaUQsY0FBYztRQUMvRDs7UUFFQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0EseUNBQXlDLGlDQUFpQztRQUMxRSxnSEFBZ0gsbUJBQW1CLEVBQUU7UUFDckk7UUFDQTs7UUFFQTtRQUNBO1FBQ0E7UUFDQSwyQkFBMkIsMEJBQTBCLEVBQUU7UUFDdkQsaUNBQWlDLGVBQWU7UUFDaEQ7UUFDQTtRQUNBOztRQUVBO1FBQ0Esc0RBQXNELCtEQUErRDs7UUFFckg7UUFDQTs7O1FBR0E7UUFDQTs7Ozs7Ozs7Ozs7O0FDbEZBLHdCQUF3QiwwQkFBMEI7QUFDbEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOEJBQThCLG9DQUFvQztBQUNsRSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImNvZGUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHsgZW51bWVyYWJsZTogdHJ1ZSwgZ2V0OiBnZXR0ZXIgfSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0aWYodHlwZW9mIFN5bWJvbCAhPT0gJ3VuZGVmaW5lZCcgJiYgU3ltYm9sLnRvU3RyaW5nVGFnKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFN5bWJvbC50b1N0cmluZ1RhZywgeyB2YWx1ZTogJ01vZHVsZScgfSk7XG4gXHRcdH1cbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGNyZWF0ZSBhIGZha2UgbmFtZXNwYWNlIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDE6IHZhbHVlIGlzIGEgbW9kdWxlIGlkLCByZXF1aXJlIGl0XG4gXHQvLyBtb2RlICYgMjogbWVyZ2UgYWxsIHByb3BlcnRpZXMgb2YgdmFsdWUgaW50byB0aGUgbnNcbiBcdC8vIG1vZGUgJiA0OiByZXR1cm4gdmFsdWUgd2hlbiBhbHJlYWR5IG5zIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDh8MTogYmVoYXZlIGxpa2UgcmVxdWlyZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy50ID0gZnVuY3Rpb24odmFsdWUsIG1vZGUpIHtcbiBcdFx0aWYobW9kZSAmIDEpIHZhbHVlID0gX193ZWJwYWNrX3JlcXVpcmVfXyh2YWx1ZSk7XG4gXHRcdGlmKG1vZGUgJiA4KSByZXR1cm4gdmFsdWU7XG4gXHRcdGlmKChtb2RlICYgNCkgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyAmJiB2YWx1ZSAmJiB2YWx1ZS5fX2VzTW9kdWxlKSByZXR1cm4gdmFsdWU7XG4gXHRcdHZhciBucyA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18ucihucyk7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShucywgJ2RlZmF1bHQnLCB7IGVudW1lcmFibGU6IHRydWUsIHZhbHVlOiB2YWx1ZSB9KTtcbiBcdFx0aWYobW9kZSAmIDIgJiYgdHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSBmb3IodmFyIGtleSBpbiB2YWx1ZSkgX193ZWJwYWNrX3JlcXVpcmVfXy5kKG5zLCBrZXksIGZ1bmN0aW9uKGtleSkgeyByZXR1cm4gdmFsdWVba2V5XTsgfS5iaW5kKG51bGwsIGtleSkpO1xuIFx0XHRyZXR1cm4gbnM7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gXCIuL3NyYy9jb2RlLnRzXCIpO1xuIiwiZmlnbWEuc2hvd1VJKF9faHRtbF9fLCB7IHdpZHRoOiAzMjAsIGhlaWdodDogMzIwIH0pO1xuLy8gZ2V0IGdpdGh1YiBzZXR0aW5nc1xuZnVuY3Rpb24gZ2V0R2l0aHViU2V0dGluZ3MoKSB7XG4gICAgcmV0dXJuIGZpZ21hLmNsaWVudFN0b3JhZ2UuZ2V0QXN5bmMoJ2dpdGh1YkRhdGEnKTtcbn1cbi8vIHNldCBnaXRodWIgc2V0dGluZ3NcbmZ1bmN0aW9uIHNldEdpdGh1YlNldHRpbmdzKGRhdGEpIHtcbiAgICBmaWdtYS5jbGllbnRTdG9yYWdlLnNldEFzeW5jKCdnaXRodWJEYXRhJywgZGF0YSk7XG59XG4vLyBzZW5kIGdpdGh1YiBkYXRhIHRvIFVJXG5mdW5jdGlvbiBpbml0KCkge1xuICAgIGdldEdpdGh1YlNldHRpbmdzKClcbiAgICAgICAgLnRoZW4oZ2l0aHViRGF0YSA9PiB7XG4gICAgICAgIGZpZ21hLnVpLnBvc3RNZXNzYWdlKHsgdHlwZTogJ2dpdGh1YkRhdGFHb3QnLCBnaXRodWJEYXRhIH0pO1xuICAgIH0pO1xufVxuZmlnbWEudWkub25tZXNzYWdlID0gbXNnID0+IHtcbiAgICBzd2l0Y2ggKG1zZy50eXBlKSB7XG4gICAgICAgIGNhc2UgJ3NldEdpdGh1YkRhdGEnOlxuICAgICAgICAgICAgc2V0R2l0aHViU2V0dGluZ3MobXNnLmdpdGh1YkRhdGEpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJ2NhbmNlbCc6XG4gICAgICAgICAgICBmaWdtYS5jbG9zZVBsdWdpbigpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgfVxufTtcbmluaXQoKTtcbiJdLCJzb3VyY2VSb290IjoiIn0=
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
// Figma Plugin API version 1, update 1
// Global variable with Figma's plugin API.
declare const figma: PluginAPI
declare const __html__: string
interface PluginAPI {
readonly apiVersion: "1.0.0"
readonly command: string
readonly viewport: ViewportAPI
closePlugin(message?: string): void
notify(message: string, options?: NotificationOptions): NotificationHandler
showUI(html: string, options?: ShowUIOptions): void
readonly ui: UIAPI
readonly clientStorage: ClientStorageAPI
getNodeById(id: string): BaseNode | null
getStyleById(id: string): BaseStyle | null
readonly root: DocumentNode
currentPage: PageNode
readonly mixed: symbol
createRectangle(): RectangleNode
createLine(): LineNode
createEllipse(): EllipseNode
createPolygon(): PolygonNode
createStar(): StarNode
createVector(): VectorNode
createText(): TextNode
createFrame(): FrameNode
createComponent(): ComponentNode
createPage(): PageNode
createSlice(): SliceNode
/**
* [DEPRECATED]: This API often fails to create a valid boolean operation. Use figma.union, figma.subtract, figma.intersect and figma.exclude instead.
*/
createBooleanOperation(): BooleanOperationNode
createPaintStyle(): PaintStyle
createTextStyle(): TextStyle
createEffectStyle(): EffectStyle
createGridStyle(): GridStyle
// The styles are returned in the same order as displayed in the UI. Only
// local styles are returned. Never styles from team library.
getLocalPaintStyles(): PaintStyle[]
getLocalTextStyles(): TextStyle[]
getLocalEffectStyles(): EffectStyle[]
getLocalGridStyles(): GridStyle[]
importComponentByKeyAsync(key: string): Promise<ComponentNode>
importStyleByKeyAsync(key: string): Promise<BaseStyle>
listAvailableFontsAsync(): Promise<Font[]>
loadFontAsync(fontName: FontName): Promise<void>
readonly hasMissingFont: boolean
createNodeFromSvg(svg: string): FrameNode
createImage(data: Uint8Array): Image
getImageByHash(hash: string): Image
group(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): FrameNode
flatten(nodes: ReadonlyArray<BaseNode>, parent?: BaseNode & ChildrenMixin, index?: number): VectorNode
union(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
subtract(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
intersect(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
exclude(nodes: ReadonlyArray<BaseNode>, parent: BaseNode & ChildrenMixin, index?: number): BooleanOperationNode
}
interface ClientStorageAPI {
getAsync(key: string): Promise<any | undefined>
setAsync(key: string, value: any): Promise<void>
}
interface NotificationOptions {
timeout?: number,
}
interface NotificationHandler {
cancel: () => void,
}
interface ShowUIOptions {
visible?: boolean,
width?: number,
height?: number,
}
interface UIPostMessageOptions {
origin?: string,
}
interface OnMessageProperties {
origin: string,
}
interface UIAPI {
show(): void
hide(): void
resize(width: number, height: number): void
close(): void
postMessage(pluginMessage: any, options?: UIPostMessageOptions): void
onmessage: ((pluginMessage: any, props: OnMessageProperties) => void) | undefined
}
interface ViewportAPI {
center: { x: number, y: number }
zoom: number
scrollAndZoomIntoView(nodes: ReadonlyArray<BaseNode>)
}
////////////////////////////////////////////////////////////////////////////////
// Datatypes
type Transform = [
[number, number, number],
[number, number, number]
]
interface Vector {
readonly x: number
readonly y: number
}
interface RGB {
readonly r: number
readonly g: number
readonly b: number
}
interface RGBA {
readonly r: number
readonly g: number
readonly b: number
readonly a: number
}
interface FontName {
readonly family: string
readonly style: string
}
type TextCase = "ORIGINAL" | "UPPER" | "LOWER" | "TITLE"
type TextDecoration = "NONE" | "UNDERLINE" | "STRIKETHROUGH"
interface ArcData {
readonly startingAngle: number
readonly endingAngle: number
readonly innerRadius: number
}
interface ShadowEffect {
readonly type: "DROP_SHADOW" | "INNER_SHADOW"
readonly color: RGBA
readonly offset: Vector
readonly radius: number
readonly visible: boolean
readonly blendMode: BlendMode
}
interface BlurEffect {
readonly type: "LAYER_BLUR" | "BACKGROUND_BLUR"
readonly radius: number
readonly visible: boolean
}
type Effect = ShadowEffect | BlurEffect
type ConstraintType = "MIN" | "CENTER" | "MAX" | "STRETCH" | "SCALE"
interface Constraints {
readonly horizontal: ConstraintType
readonly vertical: ConstraintType
}
interface ColorStop {
readonly position: number
readonly color: RGBA
}
interface ImageFilters {
readonly exposure?: number
readonly contrast?: number
readonly saturation?: number
readonly temperature?: number
readonly tint?: number
readonly highlights?: number
readonly shadows?: number
}
interface SolidPaint {
readonly type: "SOLID"
readonly color: RGB
readonly visible?: boolean
readonly opacity?: number
readonly blendMode?: BlendMode
}
interface GradientPaint {
readonly type: "GRADIENT_LINEAR" | "GRADIENT_RADIAL" | "GRADIENT_ANGULAR" | "GRADIENT_DIAMOND"
readonly gradientTransform: Transform
readonly gradientStops: ReadonlyArray<ColorStop>
readonly visible?: boolean
readonly opacity?: number
readonly blendMode?: BlendMode
}
interface ImagePaint {
readonly type: "IMAGE"
readonly scaleMode: "FILL" | "FIT" | "CROP" | "TILE"
readonly imageHash: string | null
readonly imageTransform?: Transform // setting for "CROP"
readonly scalingFactor?: number // setting for "TILE"
readonly filters?: ImageFilters
readonly visible?: boolean
readonly opacity?: number
readonly blendMode?: BlendMode
}
type Paint = SolidPaint | GradientPaint | ImagePaint
interface Guide {
readonly axis: "X" | "Y"
readonly offset: number
}
interface RowsColsLayoutGrid {
readonly pattern: "ROWS" | "COLUMNS"
readonly alignment: "MIN" | "MAX" | "STRETCH" | "CENTER"
readonly gutterSize: number
readonly count: number // Infinity when "Auto" is set in the UI
readonly sectionSize?: number // Not set for alignment: "STRETCH"
readonly offset?: number // Not set for alignment: "CENTER"
readonly visible?: boolean
readonly color?: RGBA
}
interface GridLayoutGrid {
readonly pattern: "GRID"
readonly sectionSize: number
readonly visible?: boolean
readonly color?: RGBA
}
type LayoutGrid = RowsColsLayoutGrid | GridLayoutGrid
interface ExportSettingsConstraints {
type: "SCALE" | "WIDTH" | "HEIGHT"
value: number
}
interface ExportSettingsImage {
format: "JPG" | "PNG"
contentsOnly?: boolean // defaults to true
suffix?: string
constraint?: ExportSettingsConstraints
}
interface ExportSettingsSVG {
format: "SVG"
contentsOnly?: boolean // defaults to true
suffix?: string
svgOutlineText?: boolean // defaults to true
svgIdAttribute?: boolean // defaults to false
svgSimplifyStroke?: boolean // defaults to true
}
interface ExportSettingsPDF {
format: "PDF"
contentsOnly?: boolean // defaults to true
suffix?: string
}
type ExportSettings = ExportSettingsImage | ExportSettingsSVG | ExportSettingsPDF
type WindingRule = "NONZERO" | "EVENODD"
interface VectorVertex {
readonly x: number
readonly y: number
readonly strokeCap?: StrokeCap
readonly strokeJoin?: StrokeJoin
readonly cornerRadius?: number
readonly handleMirroring?: HandleMirroring
}
interface VectorSegment {
readonly start: number
readonly end: number
readonly tangentStart?: Vector // Defaults to { x: 0, y: 0 }
readonly tangentEnd?: Vector // Defaults to { x: 0, y: 0 }
}
interface VectorRegion {
readonly windingRule: WindingRule
readonly loops: ReadonlyArray<ReadonlyArray<number>>
}
interface VectorNetwork {
readonly vertices: ReadonlyArray<VectorVertex>
readonly segments: ReadonlyArray<VectorSegment>
readonly regions?: ReadonlyArray<VectorRegion> // Defaults to []
}
interface VectorPath {
readonly windingRule: WindingRule | "NONE"
readonly data: string
}
type VectorPaths = ReadonlyArray<VectorPath>
interface LetterSpacing {
readonly value: number
readonly unit: "PIXELS" | "PERCENT"
}
type LineHeight = {
readonly value: number
readonly unit: "PIXELS" | "PERCENT"
} | {
readonly unit: "AUTO"
}
type BlendMode =
"PASS_THROUGH" |
"NORMAL" |
"DARKEN" |
"MULTIPLY" |
"LINEAR_BURN" |
"COLOR_BURN" |
"LIGHTEN" |
"SCREEN" |
"LINEAR_DODGE" |
"COLOR_DODGE" |
"OVERLAY" |
"SOFT_LIGHT" |
"HARD_LIGHT" |
"DIFFERENCE" |
"EXCLUSION" |
"HUE" |
"SATURATION" |
"COLOR" |
"LUMINOSITY"
interface Font {
fontName: FontName
}
////////////////////////////////////////////////////////////////////////////////
// Mixins
interface BaseNodeMixin {
readonly id: string
readonly parent: (BaseNode & ChildrenMixin) | null
name: string // Note: setting this also sets \`autoRename\` to false on TextNodes
readonly removed: boolean
toString(): string
remove(): void
getPluginData(key: string): string
setPluginData(key: string, value: string): void
// Namespace is a string that must be at least 3 alphanumeric characters, and should
// be a name related to your plugin. Other plugins will be able to read this data.
getSharedPluginData(namespace: string, key: string): string
setSharedPluginData(namespace: string, key: string, value: string): void
}
interface SceneNodeMixin {
visible: boolean
locked: boolean
}
interface ChildrenMixin {
readonly children: ReadonlyArray<SceneNode>
appendChild(child: SceneNode): void
insertChild(index: number, child: SceneNode): void
findAll(callback?: (node: SceneNode) => boolean): SceneNode[]
findOne(callback: (node: SceneNode) => boolean): SceneNode | null
}
interface ConstraintMixin {
constraints: Constraints
}
interface LayoutMixin {
readonly absoluteTransform: Transform
relativeTransform: Transform
x: number
y: number
rotation: number // In degrees
readonly width: number
readonly height: number
resize(width: number, height: number): void
resizeWithoutConstraints(width: number, height: number): void
}
interface BlendMixin {
opacity: number
blendMode: BlendMode
isMask: boolean
effects: ReadonlyArray<Effect>
effectStyleId: string
}
interface FrameMixin {
backgrounds: ReadonlyArray<Paint>
layoutGrids: ReadonlyArray<LayoutGrid>
clipsContent: boolean
guides: ReadonlyArray<Guide>
gridStyleId: string
backgroundStyleId: string
}
type StrokeCap = "NONE" | "ROUND" | "SQUARE" | "ARROW_LINES" | "ARROW_EQUILATERAL"
type StrokeJoin = "MITER" | "BEVEL" | "ROUND"
type HandleMirroring = "NONE" | "ANGLE" | "ANGLE_AND_LENGTH"
interface GeometryMixin {
fills: ReadonlyArray<Paint> | symbol
strokes: ReadonlyArray<Paint>
strokeWeight: number
strokeAlign: "CENTER" | "INSIDE" | "OUTSIDE"
strokeCap: StrokeCap | symbol
strokeJoin: StrokeJoin | symbol
dashPattern: ReadonlyArray<number>
fillStyleId: string | symbol
strokeStyleId: string
}
interface CornerMixin {
cornerRadius: number | symbol
cornerSmoothing: number
}
interface ExportMixin {
exportSettings: ReadonlyArray<ExportSettings>
exportAsync(settings?: ExportSettings): Promise<Uint8Array> // Defaults to PNG format
}
interface DefaultShapeMixin extends
BaseNodeMixin, SceneNodeMixin,
BlendMixin, GeometryMixin, LayoutMixin, ExportMixin {
}
interface DefaultContainerMixin extends
BaseNodeMixin, SceneNodeMixin,
ChildrenMixin, FrameMixin,
BlendMixin, ConstraintMixin, LayoutMixin, ExportMixin {
}
////////////////////////////////////////////////////////////////////////////////
// Nodes
interface DocumentNode extends BaseNodeMixin {
readonly type: "DOCUMENT"
readonly children: ReadonlyArray<PageNode>
appendChild(child: PageNode): void
insertChild(index: number, child: PageNode): void
findAll(callback?: (node: (PageNode | SceneNode)) => boolean): Array<PageNode | SceneNode>
findOne(callback: (node: (PageNode | SceneNode)) => boolean): PageNode | SceneNode | null
}
interface PageNode extends BaseNodeMixin, ChildrenMixin, ExportMixin {
readonly type: "PAGE"
clone(): PageNode
guides: ReadonlyArray<Guide>
selection: ReadonlyArray<SceneNode>
backgrounds: ReadonlyArray<Paint>
}
interface FrameNode extends DefaultContainerMixin {
readonly type: "FRAME" | "GROUP"
clone(): FrameNode
}
interface SliceNode extends BaseNodeMixin, SceneNodeMixin, LayoutMixin, ExportMixin {
readonly type: "SLICE"
clone(): SliceNode
}
interface RectangleNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
readonly type: "RECTANGLE"
clone(): RectangleNode
topLeftRadius: number
topRightRadius: number
bottomLeftRadius: number
bottomRightRadius: number
}
interface LineNode extends DefaultShapeMixin, ConstraintMixin {
readonly type: "LINE"
clone(): LineNode
}
interface EllipseNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
readonly type: "ELLIPSE"
clone(): EllipseNode
arcData: ArcData
}
interface PolygonNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
readonly type: "POLYGON"
clone(): PolygonNode
pointCount: number
}
interface StarNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
readonly type: "STAR"
clone(): StarNode
pointCount: number
innerRadius: number
}
interface VectorNode extends DefaultShapeMixin, ConstraintMixin, CornerMixin {
readonly type: "VECTOR"
clone(): VectorNode
vectorNetwork: VectorNetwork
vectorPaths: VectorPaths
handleMirroring: HandleMirroring | symbol
}
interface TextNode extends DefaultShapeMixin, ConstraintMixin {
readonly type: "TEXT"
clone(): TextNode
characters: string
readonly hasMissingFont: boolean
textAlignHorizontal: "LEFT" | "CENTER" | "RIGHT" | "JUSTIFIED"
textAlignVertical: "TOP" | "CENTER" | "BOTTOM"
textAutoResize: "NONE" | "WIDTH_AND_HEIGHT" | "HEIGHT"
paragraphIndent: number
paragraphSpacing: number
autoRename: boolean
textStyleId: string | symbol
fontSize: number | symbol
fontName: FontName | symbol
textCase: TextCase | symbol
textDecoration: TextDecoration | symbol
letterSpacing: LetterSpacing | symbol
lineHeight: LineHeight | symbol
getRangeFontSize(start: number, end: number): number | symbol
setRangeFontSize(start: number, end: number, value: number): void
getRangeFontName(start: number, end: number): FontName | symbol
setRangeFontName(start: number, end: number, value: FontName): void
getRangeTextCase(start: number, end: number): TextCase | symbol
setRangeTextCase(start: number, end: number, value: TextCase): void
getRangeTextDecoration(start: number, end: number): TextDecoration | symbol
setRangeTextDecoration(start: number, end: number, value: TextDecoration): void
getRangeLetterSpacing(start: number, end: number): LetterSpacing | symbol
setRangeLetterSpacing(start: number, end: number, value: LetterSpacing): void
getRangeLineHeight(start: number, end: number): LineHeight | symbol
setRangeLineHeight(start: number, end: number, value: LineHeight): void
getRangeFills(start: number, end: number): Paint[] | symbol
setRangeFills(start: number, end: number, value: Paint[]): void
getRangeTextStyleId(start: number, end: number): string | symbol
setRangeTextStyleId(start: number, end: number, value: string): void
getRangeFillStyleId(start: number, end: number): string | symbol
setRangeFillStyleId(start: number, end: number, value: string): void
}
interface ComponentNode extends DefaultContainerMixin {
readonly type: "COMPONENT"
clone(): ComponentNode
createInstance(): InstanceNode
description: string
readonly remote: boolean
readonly key: string // The key to use with "importComponentByKeyAsync"
}
interface InstanceNode extends DefaultContainerMixin {
readonly type: "INSTANCE"
clone(): InstanceNode
masterComponent: ComponentNode
}
interface BooleanOperationNode extends DefaultShapeMixin, ChildrenMixin, CornerMixin {
readonly type: "BOOLEAN_OPERATION"
clone(): BooleanOperationNode
booleanOperation: "UNION" | "INTERSECT" | "SUBTRACT" | "EXCLUDE"
}
type BaseNode =
DocumentNode |
PageNode |
SceneNode
type SceneNode =
SliceNode |
FrameNode |
ComponentNode |
InstanceNode |
BooleanOperationNode |
VectorNode |
StarNode |
LineNode |
EllipseNode |
PolygonNode |
RectangleNode |
TextNode
type NodeType =
"DOCUMENT" |
"PAGE" |
"SLICE" |
"FRAME" |
"GROUP" |
"COMPONENT" |
"INSTANCE" |
"BOOLEAN_OPERATION" |
"VECTOR" |
"STAR" |
"LINE" |
"ELLIPSE" |
"POLYGON" |
"RECTANGLE" |
"TEXT"
////////////////////////////////////////////////////////////////////////////////
// Styles
type StyleType = "PAINT" | "TEXT" | "EFFECT" | "GRID"
interface BaseStyle {
readonly id: string
readonly type: StyleType
name: string
description: string
remote: boolean
readonly key: string // The key to use with "importStyleByKeyAsync"
remove(): void
}
interface PaintStyle extends BaseStyle {
type: "PAINT"
paints: ReadonlyArray<Paint>
}
interface TextStyle extends BaseStyle {
type: "TEXT"
fontSize: number
textDecoration: TextDecoration
fontName: FontName
letterSpacing: LetterSpacing
lineHeight: LineHeight
paragraphIndent: number
paragraphSpacing: number
textCase: TextCase
}
interface EffectStyle extends BaseStyle {
type: "EFFECT"
effects: ReadonlyArray<Effect>
}
interface GridStyle extends BaseStyle {
type: "GRID"
layoutGrids: ReadonlyArray<LayoutGrid>
}
////////////////////////////////////////////////////////////////////////////////
// Other
interface Image {
readonly hash: string
getBytesAsync(): Promise<Uint8Array>
}
\ No newline at end of file
{
"name": "icon-automation",
"id": "739395588962138807",
"api": "1.0.0",
"main": "dist/code.js",
"ui": "dist/ui.html"
}
\ No newline at end of file
{
"name": "icon-automation",
"version": "1.0.0",
"description": "A figma plugin to push icon svg code to Github automatically.",
"main": "index.js",
"author": "Jun",
"license": "MIT",
"scripts": {
"dev": "npx webpack --mode=development --watch",
"build": "npx webpack --mode=production"
},
"dependencies": {
"css-loader": "^3.2.0",
"html-webpack-inline-source-plugin": "^0.0.10",
"html-webpack-plugin": "^3.2.0",
"parse-github-url": "^1.0.2",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"style-loader": "^1.0.0",
"ts-loader": "^6.0.4",
"typescript": "^3.5.3",
"url-loader": "^2.1.0",
"webpack": "^4.39.1",
"webpack-cli": "^3.3.6"
},
"devDependencies": {
"@types/react": "^16.9.0",
"@types/react-dom": "^16.8.5"
}
}
figma.showUI(__html__, { width: 320, height: 320 })
// get github settings
function getGithubSettings () {
return figma.clientStorage.getAsync('githubData')
}
// set github settings
function setGithubSettings (data) {
figma.clientStorage.setAsync('githubData', data)
}
// send github data to UI
function init () {
getGithubSettings()
.then(githubData => {
figma.ui.postMessage({ type: 'githubDataGot', githubData })
})
}
figma.ui.onmessage = msg => {
switch (msg.type) {
case 'setGithubData':
setGithubSettings(msg.githubData)
break
case 'cancel':
figma.closePlugin()
break
}
}
init()
import * as React from 'react'
import { validateGithubURL } from '../../utils/helper'
declare function require(path: string): any
export interface Props {
onGithubSet?: (data) => void;
githubData: {owner?: string, name?: string, githubToken?: string};
visible: boolean;
settingSwitch: boolean;
}
export default class Settings extends React.Component<Props> {
state = {
githubRepo: '',
githubToken: '',
warning: ''
}
handleChange = e => {
this.setState({[e.target.name]: e.target.value})
}
handleSubmit = e => {
const { onGithubSet } = this.props
const { githubRepo, githubToken } = this.state
const repo = validateGithubURL(githubRepo)
if (!repo) {
this.setState({warning: 'Github Repository is required.'})
} else if (!repo.owner || !repo.name) {
this.setState({warning: 'Github Repository URL is invalid.'})
} else if (!githubToken) {
this.setState({warning: 'Github Token is required.'})
} else {
const githubData = {
owner:repo.owner,
name: repo.name,
githubToken: `${githubToken}`
}
parent.postMessage({ pluginMessage: { type: 'setGithubData', githubData } }, '*')
onGithubSet(githubData)
}
}
componentDidUpdate (prevPorps) {
const { githubData, settingSwitch } = this.props
if ((!prevPorps.githubData && githubData) || (prevPorps.settingSwitch !== settingSwitch)) {
this.setState({
githubRepo: `https://github.com/${githubData.owner}/${githubData.name}`,
githubToken: githubData.githubToken,
})
}
}
render () {
const { visible } = this.props
const { githubRepo, githubToken, warning } = this.state
return (
<div className={!visible ? 'hide' : ''}>
<div className="onboarding-tip">
<div className="onboarding-tip__icon">
<div className="icon icon--smiley"></div>
</div>
<div className="onboarding-tip__msg">Hi, Welcome here. This plugin helps you convert icons to react component and publish to NPM. It should be used with Github and NPM. Please read the docs before using.<br/><br/><a href="https://github.com/leadream/figma-icon-automation" target="_blank">Docs here</a></div>
</div>
{
warning &&
<div className="form-item">
<div className="type type--pos-medium-normal alert alert-warning">{ warning }</div>
</div>
}
<div className="form-item">
<input
name="githubRepo"
className="input"
placeholder="Github Repository URL"
onChange={this.handleChange}
value={githubRepo}
/>
</div>
<div className="form-item">
<input
name="githubToken"
className="input"
placeholder="Github Token"
onChange={this.handleChange}
value={githubToken}
/>
</div>
<div className="form-item">
<button className='button button--primary button-block' onClick={this.handleSubmit}>Go</button>
</div>
<div className="setting-footer form-item type type--pos-medium-normal">
developed by <a href="https://github.com/leadream" target="_blank">Jun</a>
</div>
</div>
)
}
}
import * as React from 'react'
import { getContent, getCommit, updatePackage, createPullRequest, createBranch } from '../../api/github'
import { versionValue } from '../../utils/helper'
declare function require(path: string): any
export interface Props {
onSucceed: () => void;
githubData: {owner?: string, name?: string, githubToken?: string};
visible: boolean;
}
export default class Settings extends React.Component<Props> {
state = {
isPushing: false,
version: '',
message: '',
versionTip: '',
messageTip: '',
sha: '',
contents: { version: '0.0.0' },
currentVersion: '',
currentVersionTip: '',
resultTip: '',
prUrl: ''
}
getVersion = async (githubData) => {
const { contents, sha } = await getContent('package.json', githubData)
const currentVersion = contents.version
this.setState({
sha,
contents,
currentVersion,
currentVersionTip: `The current version is ${currentVersion}`
})
}
createBranch = async () => {
const { githubData } = this.props
const { sha } = await getCommit(githubData)
const { ref } = await createBranch(sha, githubData)
return { branchName: ref.replace('refs/heads/', '') }
}
changeVersion = async (branch) => {
const { githubData } = this.props
const { version, message, contents, sha } = this.state
contents.version = version
await updatePackage(message, sha, contents, branch, githubData)
}
createCommitAndPR = async (branchName) => {
const { githubData } = this.props
const { version, message } = this.state
return await createPullRequest(
`[figma]:update to ${version}`,
message,
branchName,
githubData
)
}
handleChange = e => {
const { name, value } = e.target
this.setState({[name]: value})
}
validate = (callback) => {
const { version, message, currentVersion } = this.state
// TODO: should validate async
// this.getVersion(this.props.githubData)
// .then(() => {
// const { currentVersion } = this.state
// currentVersion
// })
if (!version) {
this.setState({versionTip: 'Version is required.'})
return
} else if (!/^[0-9]\d?(\.(0|[1-9]\d?)){2}$/.test(version)) {
this.setState({versionTip: 'Version should be like 1.17.2.'})
return
} else if (versionValue(version) - versionValue(currentVersion) <= 0) {
this.setState({versionTip: 'Should be bigger than current version.'})
return
}
this.setState({
versionTip: ''
})
if (!message) {
this.setState({messageTip: 'Commit message is required.'})
return
}
this.setState({
messageTip: ''
})
callback && callback()
}
handleSubmit = () => {
this.validate(() => {
this.setState({isPushing: true})
this.createBranch()
.then(({branchName}) => {
this.changeVersion(branchName)
.then(() => {
this.createCommitAndPR(branchName)
.then(({html_url}) => {
this.props.onSucceed()
this.setState({
version: '',
message: '',
isPushing: false,
resultTip: 'Pushing successfully! You can now go to Github and merge this PR. Then your icons will be published to NPM automatically.',
prUrl: html_url
})
})
})
})
})
}
onCancel = () => {
parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*')
}
componentDidUpdate (prevProps) {
if (!prevProps.githubData && this.props.githubData) {
this.getVersion(this.props.githubData)
}
}
render () {
const { visible } = this.props
const { isPushing, version, message, versionTip, messageTip, currentVersionTip, resultTip, prUrl } = this.state
return (
<div className={'updator ' + (!visible ? 'hide' : '')}>
<div className="form-item">
{
!resultTip &&
<p className="type type--pos-medium-normal">Please fill the version and commit message below.</p>
}
{
(currentVersionTip && !resultTip) &&
<div className="type type--pos-medium-bold">{currentVersionTip}</div>
}
{
resultTip &&
<div className="type type--pos-medium-bold alert alert-success">
<h3>Congratulations!</h3>
{resultTip}
Click <a href={prUrl} target="_blank">here</a> to open the PR link.
</div>
}
</div>
<div className={'form-item '+(resultTip ? 'hide' : '')}>
<input
name="version"
className="input"
placeholder="The new version, such as 1.17.2"
onChange={this.handleChange}
value={version}
/>
{
versionTip &&
<div className="type type--pos-medium-normal help-tip">{versionTip}</div>
}
</div>
<div className={'form-item '+(resultTip ? 'hide' : '')}>
<textarea
rows={2}
name="message"
className="textarea"
placeholder="what has been changed?"
onChange={this.handleChange}
value={message}
/>
{
messageTip &&
<div className="type type--pos-medium-normal help-tip">{messageTip}</div>
}
</div>
<div className={'form-item '+(resultTip ? 'hide' : '')}>
<button
onClick={this.handleSubmit}
className='button button--primary'
style={{marginRight: '8px'}}
disabled={isPushing}
>{isPushing ? 'pushing…' : 'push to Github'}</button>
{
!isPushing &&
<button onClick={this.onCancel} className='button button--secondary'>close</button>
}
</div>
{
resultTip &&
<button onClick={this.onCancel} className='button button--secondary'>close</button>
}
</div>
)
}
}
\ No newline at end of file
/* these styles are just used for presentation of components */
body {
margin: 20px;
font-size: 20px;
font-family: Arial,x-locale-body,sans-serif;
}
h1, h2, h3 {
box-sizing: border-box;
margin-top: 12px;
margin-bottom: 12px;
font-family: sans-serif;
}
h1 {
font-size: 24px;
}
h2 {
font-size: 16px;
font-weight: 500;
}
h3 {
font-size: 14px;
font-weight: normal;
}
hr {
margin: 48px 0 48px 0;
border-top: 1px solid #cccccc;
border-bottom: none;
outline: none;
}
ul{
margin: 0;
padding-left: 16px;
}
textarea{
width: 100%!important;
margin: 0!important;
}
a{
color: #18a0fb
}
.container{
padding-top: 20px;
}
.bar-adjust{
display: flex;
position: fixed;
top: 0;
left: 0;
right: 0;
height: 32px;
border-bottom: 1px solid #F3F3F3;
background: #FFF;
}
.adjust-item{
padding: 2px 10px;
line-height: 30px;
cursor: pointer;
color: #999;
border-bottom: 1px solid transparent;
}
.adjust-item.active{
color: #333;
border-color: #333;
}
.setting-footer{
padding: 10px 0;
text-align: center;
color: #999;
}
.form-item{
margin-bottom: 8px;
}
.form-item textarea{
margin-bottom: 8px!important;
}
.form-item .help-tip{
padding: 5px;
color: #e85007;
}
.form-item button:not(.button-block){
margin-right: 8px!important;
}
.button-block {
width: 100%;
}
.alert{
padding: 5px 10px;
border-radius: 2px;
color: #666;
background-color: #F8F8F8;
border: 1px solid #CCC;
}
.alert-warning {
color: #F90;
background-color: rgba(255, 153, 0, 0.05);
border: 1px solid rgba(255, 153, 0, 0.24);
}
.alert-success {
color: #09cf83;
background-color: #d8f9ec;
border: 1px solid rgb(197, 245, 226);
}
.hide {
display: none;
}
<div id="react-page"></div>
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import Settings from './components/Settings'
import Updator from './components/Updator'
import '../assets/ds.css'
import './style.css'
declare function require(path: string): any
class App extends React.Component {
state = {
updatorVisible: false,
githubData: null,
settingSwitch: false,
isDone: false
}
onSucceed = () => {
this.setState({ isDone: true })
}
toggleView = (githubData?) => {
const { updatorVisible } = this.state
this.setState({updatorVisible: !updatorVisible})
if (githubData===true) {
const { settingSwitch } = this.state
this.setState({settingSwitch: !settingSwitch})
} else if (githubData) {
this.setState({
githubData: githubData
})
}
}
componentDidMount () {
// 所有的消息接收集中在这里
window.onmessage = async (event) => {
const msg = event.data.pluginMessage
switch (msg.type) {
case 'githubDataGot':
if (msg.githubData) {
this.setState({
updatorVisible: true,
githubData: msg.githubData
})
}
break
}
}
}
render() {
const { updatorVisible, githubData, settingSwitch, isDone } = this.state
return (
<div className="container">
<div className={'bar-adjust '+ ((githubData&&!isDone) ? '' : 'hide')}>
<div
className={'adjust-item type type--pos-medium-bold '+(updatorVisible ? '' : 'active')}
onClick={e => this.toggleView()}
>
Setting
</div>
<div
className={'adjust-item type type--pos-medium-bold '+(updatorVisible ? 'active' : '')}
onClick={e => this.toggleView(true)}
>
Publish
</div>
</div>
<Settings
visible={!updatorVisible}
githubData={githubData}
onGithubSet={this.toggleView}
settingSwitch={settingSwitch}
/>
<Updator
onSucceed={this.onSucceed}
visible={updatorVisible}
githubData={githubData}/>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('react-page'))
{
"compilerOptions": {
"target": "es6",
"jsx": "react"
}
}
import gh from 'parse-github-url'
export const Uint8ArrayToString = fileData => {
var dataString = "";
for (var i = 0; i < fileData.length; i++) {
dataString += String.fromCharCode(fileData[i]);
}
return dataString
}
export const formattedSelections = selections => {
const iconsPromise = selections
.map(async c => {
let svgCode = await c.exportAsync({format: 'SVG'})
svgCode = Uint8ArrayToString(svgCode)
return {id: c.id, name: c.name, code: svgCode}
})
return Promise.all(iconsPromise)
}
export const validateGithubURL = url => {
return gh(url)
}
export const versionValue = (versions) => {
return versions
.split('.')
.map(n => n - 0)
.reduce((accumulator, currentValue, index) => {
return accumulator + currentValue*Math.pow(100, 2 - index)
}, 0)
}
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
module.exports = (env, argv) => ({
mode: argv.mode === 'production' ? 'production' : 'development',
// This is necessary because Figma's 'eval' works differently than normal eval
devtool: argv.mode === 'production' ? false : 'inline-source-map',
entry: {
ui: './src/ui.tsx', // The entry point for your UI code
code: './src/code.ts', // The entry point for your plugin code
},
module: {
rules: [
// Converts TypeScript code to JavaScript
{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },
// Enables including CSS by doing "import './file.css'" in your TypeScript code
{ test: /\.css$/, loader: [{ loader: 'style-loader' }, { loader: 'css-loader' }] },
// Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI
{ test: /\.(png|jpg|gif|webp|svg)$/, loader: [{ loader: 'url-loader' }] },
],
},
// Webpack tries these extensions for you if you omit the extension like "import './file'"
resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js'] },
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'), // Compile into a folder called "dist"
},
// Tells Webpack to generate "ui.html" and to inline "ui.ts" into it
plugins: [
new HtmlWebpackPlugin({
template: './src/ui.html',
filename: 'ui.html',
inlineSource: '.(js)$',
chunks: ['ui'],
}),
new HtmlWebpackInlineSourcePlugin(),
],
})
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment