this.handleClose(this.props.name)} />
54 | );
55 | }
56 | }
57 |
58 | export default connect(
59 | State,
60 | Dispatch
61 | )(Close);
62 |
--------------------------------------------------------------------------------
/panel/components/Iconfont.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Icon = styled.div`
4 | font-size: 1.5rem;
5 | width: 100%;
6 | height: 3rem;
7 | line-height: 3rem;
8 | text-align: center;
9 | transition: all 0.1s ease-out;
10 | `;
11 |
12 | const More = styled.div`
13 | font-size: 1.5rem;
14 | position: absolute;
15 | let: 0;
16 | top: 0;
17 | width: 100%;
18 | height: 3rem;
19 | line-height: 3rem;
20 | text-align: center;
21 | opacity: 0.5;
22 | `;
23 |
24 | const Title = styled.div`
25 | text-align: center;
26 | margin-top: -0.5rem;
27 | font-size: 0.7rem;
28 | font-weight: 500;
29 | opacity: 0.7;
30 | `;
31 |
32 | const View = styled.div`
33 | margin-top: 0.2rem;
34 | width: 100%;
35 | display: flex;
36 | flex-direction: column;
37 | align-items: center;
38 | cursor: pointer;
39 | &:active {
40 | ${Icon} {
41 | transform: scale(0.9);
42 | }
43 | }
44 | `;
45 |
46 | export default ({ type, more, title = '标题', ...other }) => {
47 | return (
48 |
49 |
50 | {more ? : null}
51 |
52 | {title}
53 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/panel/components/ListView.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Component } from 'react';
3 | import { connect } from 'dva';
4 |
5 | /// /////////////////////////////////////////////
6 | // styled
7 | /// /////////////////////////////////////////////
8 |
9 | const List = styled.div`
10 | padding: 1rem;
11 | overflow: hidden;
12 | overflow-y: auto;
13 | `;
14 |
15 | /// /////////////////////////////////////////////
16 | // connect
17 | /// /////////////////////////////////////////////
18 |
19 | const State = state => {
20 | return {
21 | theme: state.store.theme,
22 | };
23 | };
24 |
25 | /// /////////////////////////////////////////////
26 | // component
27 | /// /////////////////////////////////////////////
28 |
29 | class ListView extends Component {
30 | render() {
31 | const { width = 'auto', border, theme, flex, grey, children } = this.props;
32 | const style = {
33 | width: width,
34 | };
35 | if (border)
36 | style.borderRight = theme === 'black' ? '1px solid rgba(0, 0, 0, 0.1)' : '1px solid #eee';
37 |
38 | if (flex) style.flex = 1;
39 |
40 | if (grey) style.background = '#f5f5f5';
41 | return {children}
;
42 | }
43 | }
44 |
45 | export default connect(State)(ListView);
46 |
--------------------------------------------------------------------------------
/panel/components/Loading.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Spin } from 'antd';
3 | /// /////////////////////////////////////////////
4 | // styled
5 | /// /////////////////////////////////////////////
6 |
7 | const View = styled.div`
8 | width: 100%;
9 | height: 100vh;
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | `;
14 |
15 | /// /////////////////////////////////////////////
16 | // component
17 | /// /////////////////////////////////////////////
18 |
19 | const Loading = () => (
20 |
21 |
22 |
23 | );
24 |
25 | export default Loading;
26 |
--------------------------------------------------------------------------------
/panel/components/Title.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import { Component } from 'react';
3 | import { connect } from 'dva';
4 |
5 | /// /////////////////////////////////////////////
6 | // styled
7 | /// /////////////////////////////////////////////
8 |
9 | const Text = styled.div`
10 | width: 100%;
11 | height: 48px;
12 | line-height: 48px;
13 | display: flex;
14 | font-size: 1.2rem;
15 | align-items: center;
16 | padding: 0 1rem;
17 | font-weight: bolder;
18 | color: rgba(100, 100, 100, 0.4);
19 | `;
20 |
21 | const Switch = styled.span`
22 | display: inline-block;
23 | margin-right: 1rem;
24 | cursor: pointer;
25 | ${props =>
26 | props.active
27 | ? css`
28 | border-bottom: 3px solid #2a72ff;
29 | color: rgba(100, 100, 100, 1);
30 | `
31 | : null}
32 | `;
33 |
34 | /// /////////////////////////////////////////////
35 | // connect
36 | /// /////////////////////////////////////////////
37 |
38 | const State = state => {
39 | return {
40 | theme: state.store.theme,
41 | };
42 | };
43 |
44 | /// /////////////////////////////////////////////
45 | // component
46 | /// /////////////////////////////////////////////
47 |
48 | class TitleClass extends Component {
49 | render() {
50 | const style = {
51 | boxShadow:
52 | this.props.theme === 'black'
53 | ? '0 4px 12px rgba(0, 0, 0, 0.2)'
54 | : '0 4px 8px rgba(0, 0, 0, 0.05)',
55 | };
56 | return (
57 |
58 | {this.props.children}
59 |
60 | );
61 | }
62 | }
63 |
64 | const Title = connect(State)(TitleClass);
65 | Title.Switch = Switch;
66 | export default Title;
67 |
--------------------------------------------------------------------------------
/panel/components/View.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import QueueAnim from 'rc-queue-anim';
3 |
4 | /// /////////////////////////////////////////////
5 | // styled
6 | /// /////////////////////////////////////////////
7 |
8 | const View = styled(QueueAnim)`
9 | width: 368px;
10 | height: calc(100vh - 50px);
11 | overflow: hidden;
12 | `;
13 |
14 | /// /////////////////////////////////////////////
15 | // component
16 | /// /////////////////////////////////////////////
17 |
18 | export default ({ children, width = 48, padding, inner }) => {
19 | const style = {
20 | width: width + 'px',
21 | padding: padding ? '1rem' : 0,
22 | display: inner ? 'flex' : 'block',
23 | };
24 | return (
25 |
26 | {children}
27 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/panel/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Iconfont } from './Iconfont';
2 | export { default as Title } from './Title';
3 | export { default as Close } from './Close';
4 | export { default as View } from './View';
5 | export { default as ListView } from './ListView';
6 | export { default as Cell } from './Cell';
7 | export { default as ButtonGroup } from './ButtonGroup';
8 | export { default as Loading } from './Loading';
9 | export { default as Check } from './Check';
10 |
--------------------------------------------------------------------------------
/panel/data/color.json:
--------------------------------------------------------------------------------
1 | [{"key":1,"name":"灰阶","colors":[{"key":1,"name":"灰0","type":"Color","color":"#333"},{"key":6,"name":"灰+5","type":"Color","color":"#f7f7f7"},{"key":5,"name":"灰+4","type":"Color","color":"#eee"},{"key":3,"name":"灰+2","type":"Color","color":"#999"},{"key":2,"name":"灰+1","type":"Color","color":"#666"},{"key":4,"name":"灰+3","type":"Color","color":"#ccc"}]},{"key":11,"name":"尊享蓝","colors":[{"key":5,"name":"尊享蓝+4","type":"Color","color":"#c9cee0"},{"key":2,"name":"尊享蓝+1","type":"Color","color":"#4753a2"},{"key":0,"name":"尊享蓝-1","type":"Color","color":"#0a0f22"},{"key":6,"name":"尊享蓝-渐变-1","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#0c155e","position":0},{"color":"#090e22","position":1}]}},{"key":3,"name":"尊享蓝+2","type":"Color","color":"#8c99cf"},{"key":1,"name":"尊享蓝0","type":"Color","color":"#0b1358"},{"key":4,"name":"尊享蓝+3","type":"Color","color":"#b4bde0"},{"key":7,"name":"尊享蓝-渐变0","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#162d8d","position":0},{"color":"#0c1566","position":1}]}}]},{"key":10,"name":"图标色","colors":[{"key":1,"name":"图标红-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ff7028","position":0},{"color":"#ff3b28","position":1}]}},{"key":2,"name":"图标橙-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffa0294c","position":0},{"color":"#ff6619","position":1}]}},{"key":6,"name":"图标黄-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffb5204c","position":0},{"color":"#ffa322","position":1}]}},{"key":10,"name":"图标蓝-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#26a5ff4c","position":0},{"color":"#266eff","position":1}]}},{"key":0,"name":"图标白","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#fff","position":0},{"color":"#ffffffb2","position":1}]}},{"key":11,"name":"图标紫-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#a375ff","position":0},{"color":"#8c75ff","position":1}]}},{"key":5,"name":" 图标黄-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffb521","position":0},{"color":"#ffa322","position":1}]}},{"key":8,"name":"图标青-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#2bcaff4c","position":0},{"color":"#2bacff","position":1}]}},{"key":7,"name":"图标青-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#2bcaff","position":0},{"color":"#2bacff","position":1}]}},{"key":4,"name":"图标橙-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ff70294c","position":0},{"color":"#ff3b28","position":1}]}},{"key":12,"name":"图标紫-副","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#a274ff4c","position":0},{"color":"#8c75ff","position":1}]}},{"key":3,"name":"图标红-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ff8000","position":0},{"color":"#ff4f00","position":1}]}},{"key":9,"name":"图标蓝-主","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#26a4ff","position":0},{"color":"#266eff","position":1}]}}]},{"key":5,"name":"绿","colors":[{"key":2,"name":"绿0","type":"Color","color":"#24a69b"},{"key":5,"name":"绿+3","type":"Color","color":"#a7dbd7"},{"key":6,"name":"绿+4","type":"Color","color":"#d3edeb"},{"key":4,"name":"绿+2","type":"Color","color":"#7bc9c3"},{"key":3,"name":"绿+1","type":"Color","color":"#4fb7af"},{"key":1,"name":"绿-1","type":"Color","color":"#1b837c"}]},{"key":9,"name":"渐变","colors":[{"key":3,"name":"黄","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#ffd91a","position":0},{"color":"#ffb31a","position":1}]}},{"key":5,"name":"蓝","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#2693ff","position":0},{"color":"#266eff","position":1}]}},{"key":1,"name":"红","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#f25535","position":0},{"color":"#eb332f","position":1}]}},{"key":4,"name":"青","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#0cbeff","position":0},{"color":"#0c9eff","position":1}]}},{"key":2,"name":"橙","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#fc8116","position":0},{"color":"#ff5e0e","position":1}]}},{"key":6,"name":"紫","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#6a00ff","position":0},{"color":"#5800ff","position":1}]}}]},{"key":7,"name":"蓝","colors":[{"key":5,"name":"蓝+3","type":"Color","color":"#a8c5ff"},{"key":2,"name":"蓝0","type":"Color","color":"#266eff"},{"key":1,"name":"蓝-1","type":"Color","color":"#1d58cc"},{"key":4,"name":"蓝+2","type":"Color","color":"#7ca8ff"},{"key":6,"name":"蓝+4","type":"Color","color":"#d3e2ff"},{"key":3,"name":"蓝+1","type":"Color","color":"#518bff"}]},{"key":3,"name":"橙","colors":[{"key":2,"name":"橙0","type":"Color","color":"#ff6619"},{"key":6,"name":"橙+4","type":"Color","color":"#ffe3d6"},{"key":5,"name":"橙+3","type":"Color","color":"#ffc8ad"},{"key":1,"name":"橙-1","type":"Color","color":"#cc5f28"},{"key":4,"name":"橙+2","type":"Color","color":"#ffad84"},{"key":3,"name":"橙+1","type":"Color","color":"#ff925b"}]},{"key":0,"name":"平台色","colors":[{"key":3,"name":"分割线颜色","type":"Color","color":"#eee"},{"key":2,"name":"全局背景色","type":"Color","color":"#f7f7f7"},{"key":1,"name":"App品牌色","type":"Color","color":"#266eff"}]},{"key":6,"name":"青","colors":[{"key":5,"name":"青+3","type":"Color","color":"#99d6ff"},{"key":2,"name":"青0","type":"Color","color":"#0c9eff"},{"key":6,"name":"青+4","type":"Color","color":"#ccecff"},{"key":4,"name":"青+2","type":"Color","color":"#66c1ff"},{"key":1,"name":"青-1","type":"Color","color":"#0a7ecc"},{"key":3,"name":"青+1","type":"Color","color":"#3fb2ff"}]},{"key":12,"name":"尊享金","colors":[{"key":6,"name":"尊享金-渐变-1","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#a87952","position":0},{"color":"#6e4326","position":1}]}},{"key":5,"name":"尊享金+3","type":"Color","color":"#eddcc9"},{"key":4,"name":"尊享金+2","type":"Color","color":"#edd3b5"},{"key":1,"name":"尊享金-1","type":"Color","color":"#a87952"},{"key":2,"name":"尊享金0","type":"Color","color":"#e3b07f"},{"key":0,"name":"尊享金-2","type":"Color","color":"#6e4326"},{"key":7,"name":"尊享金-渐变0","type":"Gradient","color":{"gradientType":"Linear","stops":[{"color":"#edd3b5","position":0},{"color":"#e3b07f","position":1}]}},{"key":3,"name":"尊享金+1","type":"Color","color":"#e8c199"}]},{"key":2,"name":"红","colors":[{"key":1,"name":"红-1","type":"Color","color":"#c1442a"},{"key":4,"name":"红+2","type":"Color","color":"#f79985"},{"key":3,"name":"红+1","type":"Color","color":"#f3775d"},{"key":5,"name":"红+3","type":"Color","color":"#f9bbae"},{"key":6,"name":"红+4","type":"Color","color":"#fcddd6"},{"key":2,"name":"红0","type":"Color","color":"#f25534"}]},{"key":8,"name":"紫","colors":[{"key":2,"name":"紫0","type":"Color","color":"#5800ff"},{"key":6,"name":"紫+4","type":"Color","color":"#dcf"},{"key":3,"name":"紫+1","type":"Color","color":"#7933ff"},{"key":5,"name":"紫+3","type":"Color","color":"#bc99ff"},{"key":4,"name":"紫+2","type":"Color","color":"#9b66ff"},{"key":1,"name":"紫-1","type":"Color","color":"#4700cc"}]},{"key":4,"name":"黄","colors":[{"key":6,"name":"黄+4","type":"Color","color":"#fec"},{"key":3,"name":"黄+1","type":"Color","color":"#fb3"},{"key":4,"name":"黄+2","type":"Color","color":"#fc6"},{"key":1,"name":"黄-1","type":"Color","color":"#c80"},{"key":2,"name":"黄0","type":"Color","color":"#fa0"},{"key":5,"name":"黄+3","type":"Color","color":"#fd9"}]}]
--------------------------------------------------------------------------------
/panel/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #root {
4 | height: 100%;
5 | overflow: hidden;
6 | user-select: none;
7 | background: transparent;
8 | }
9 |
10 | body {
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | -ms-text-size-adjust: 100%;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | *:focus {
18 | outline: none;
19 | }
20 |
21 | * {
22 | -webkit-font-smoothing: antialiased;
23 | }
24 |
25 | div {
26 | box-sizing: border-box;
27 | position: relative;
28 | }
29 |
30 | ::-webkit-scrollbar {
31 | background: #f8f8f8;
32 | height: 0.4rem;
33 | width: 0;
34 | }
35 |
36 | ::-webkit-scrollbar-track {
37 | background: transparent;
38 | border-radius: 0;
39 | box-shadow: none;
40 | }
41 |
42 | ::-webkit-scrollbar-thumb {
43 | background-color: #ccc;
44 | border-radius: 0;
45 | box-shadow: none;
46 | transition: background-color 0.5s ease;
47 | }
48 |
49 | ::-webkit-scrollbar-thumb:hover {
50 | background-color: #000;
51 | }
--------------------------------------------------------------------------------
/panel/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 | Anto
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/panel/index.js:
--------------------------------------------------------------------------------
1 | import { message } from 'antd';
2 | import dva from 'dva';
3 | import createLoading from 'dva-loading';
4 | import './index.css';
5 |
6 | // 1. Initialize
7 | const app = dva({
8 | onError(e) {
9 | message.error(e.message, 3);
10 | },
11 | });
12 |
13 | // 2. Models
14 | app.model(require('./models/check').default);
15 | app.model(require('./models/username').default);
16 | app.model(require('./models/store').default);
17 | app.model(require('./models/config').default);
18 | app.model(require('./models/symbols').default);
19 | app.model(require('./models/colors').default);
20 | app.model(require('./models/docs').default);
21 |
22 | // 2. Plugins
23 | app.use(createLoading());
24 |
25 | // 3. Router
26 | app.router(require('./router').default);
27 |
28 | // 4. Start
29 | app.start('#root');
30 |
31 | // 5. Func
32 | document.addEventListener('contextmenu', e => e.preventDefault());
33 |
--------------------------------------------------------------------------------
/panel/models/check.js:
--------------------------------------------------------------------------------
1 | import { request } from '../utils/request';
2 |
3 | export default {
4 | namespace: 'check',
5 | state: false,
6 | reducers: {
7 | save(state, action) {
8 | state = action.payload;
9 | return state;
10 | },
11 | },
12 | effects: {
13 | *get(action, { call, put }) {
14 | let check = false;
15 | try {
16 | const result = yield call(() => request('http://anto.inc.alipay.net/static/check.json'));
17 | console.log(result);
18 | if (result.data && result.data.check) check = true;
19 | } catch (e) {
20 | console.log(e);
21 | }
22 | yield put({
23 | type: 'save',
24 | payload: check,
25 | });
26 | },
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/panel/models/colors.js:
--------------------------------------------------------------------------------
1 | import { request } from '../utils/request';
2 |
3 | export default {
4 | namespace: 'colors',
5 | state: [],
6 | reducers: {
7 | save(state, action) {
8 | state = action.payload;
9 | return state;
10 | },
11 | },
12 | effects: {
13 | *get(action, { call, put, select }) {
14 | const check = yield select(state => state.check);
15 | let url =
16 | 'https://raw.githubusercontent.com/canisminor1990/anto-cloud/master/public/colors/data.json';
17 | if (check) url = 'http://anto.inc.alipay.net/static/colors.json';
18 | const result = yield call(() => request(url));
19 | yield put({
20 | type: 'save',
21 | payload: result.data,
22 | });
23 | },
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/panel/models/config.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export default {
4 | namespace: 'config',
5 |
6 | state: {
7 | symbol: false,
8 | color: false,
9 | line: false,
10 | note: false,
11 | layer: false,
12 | plate: false,
13 | word: false,
14 | dev: false,
15 | setting: false,
16 | },
17 |
18 | reducers: {
19 | updateSuccess(state, action) {
20 | const payload = action.payload;
21 | _.forEach(payload, (value, key) => {
22 | if (value) {
23 | _.forEach(state, (v, k) => {
24 | if (state[k] !== key) state[k] = false;
25 | });
26 | }
27 | });
28 | return { ...state, ...payload };
29 | },
30 | },
31 |
32 | effects: {
33 | *update(action, { put }) {
34 | const payload = action.payload;
35 | console.log('update', payload);
36 | yield put({ type: 'updateSuccess', payload });
37 | },
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/panel/models/docs.js:
--------------------------------------------------------------------------------
1 | import { request } from '../utils/request';
2 |
3 | export default {
4 | namespace: 'docs',
5 | state: [],
6 | reducers: {
7 | save(state, action) {
8 | state = action.payload;
9 | return state;
10 | },
11 | },
12 | effects: {
13 | *get(action, { call, put }) {
14 | const result = yield call(() => request(`http://anto.inc.alipay.net/static/docs.json`));
15 | yield put({
16 | type: 'save',
17 | payload: result.data,
18 | });
19 | },
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/panel/models/store.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | const local = _.defaults(JSON.parse(localStorage.getItem('store')), {
4 | theme: 'black',
5 | mode: '交互',
6 | name: '花名',
7 | devMode: false,
8 | });
9 |
10 | export default {
11 | namespace: 'store',
12 |
13 | state: {
14 | ...local,
15 | },
16 |
17 | reducers: {
18 | updateSuccess(state, action) {
19 | const payload = action.payload;
20 | const newLocal = { ...state, ...payload };
21 | localStorage.setItem('store', JSON.stringify(newLocal));
22 | return newLocal;
23 | },
24 | },
25 |
26 | effects: {
27 | *update(action, { put }) {
28 | const payload = action.payload;
29 | console.log('update', payload);
30 | yield put({ type: 'updateSuccess', payload });
31 | },
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/panel/models/symbols.js:
--------------------------------------------------------------------------------
1 | import { request } from '../utils/request';
2 |
3 | export default {
4 | namespace: 'symbols',
5 | state: {},
6 | reducers: {
7 | save(state, action) {
8 | state = action.payload;
9 | return state;
10 | },
11 | },
12 | effects: {
13 | *get(action, { call, put }) {
14 | const tabs = yield call(() => request('http://100.88.232.163/static/tabs.json'));
15 | const tabsData = tabs.data;
16 | let data = {};
17 | for (let i = 0; i < tabsData.length; i++) {
18 | const libdata = yield call(() =>
19 | request(`http://100.88.232.163/static/${tabsData[i].dirname}/data.json`)
20 | );
21 | data[tabsData[i].dirname] = {
22 | data: libdata.data,
23 | ...tabsData[i],
24 | };
25 | }
26 | yield put({
27 | type: 'save',
28 | payload: data,
29 | });
30 | },
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/panel/models/username.js:
--------------------------------------------------------------------------------
1 | import { request } from '../utils/request';
2 | import _ from 'lodash';
3 |
4 | export default {
5 | namespace: 'username',
6 | state: [],
7 | reducers: {
8 | save(state, action) {
9 | state = action.payload;
10 | return state;
11 | },
12 | },
13 | effects: {
14 | *get(action, { call, put }) {
15 | const name = action.payload;
16 | const names = [];
17 | try {
18 | const result = yield call(() =>
19 | request(`http://anto.inc.alipay.net/api/user/search?keyword=${name}`)
20 | );
21 | _.forEach(result.data.result, value => {
22 | if (value.nick && value.nick !== '') names.push(value.nick);
23 | });
24 | } catch (e) {
25 | console.log(e);
26 | }
27 | yield put({
28 | type: 'save',
29 | payload: names,
30 | });
31 | },
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/panel/router.js:
--------------------------------------------------------------------------------
1 | import { Route, Router } from 'dva/router';
2 | import Panel from './routes';
3 |
4 | export default ({ app, history }) => {
5 | history.listen(() => window.scrollTo(0, 0));
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/panel/routes/Colors.js:
--------------------------------------------------------------------------------
1 | import { hsl } from 'polished';
2 | import { Component } from 'react';
3 | import styled from 'styled-components';
4 | import { connect } from 'dva';
5 | import _ from 'lodash';
6 | import { Slider, Switch } from 'antd';
7 | import { Title, View, Close, Cell, ButtonGroup, Check, Loading } from '../components';
8 | import { PostMessage } from '../utils/PostMessage';
9 |
10 | /// /////////////////////////////////////////////
11 | // styled
12 | /// /////////////////////////////////////////////
13 |
14 | const Panel = styled.div`
15 | padding: 1rem;
16 | width: 100%;
17 | height: calc(100% - 50px);
18 | overflow-y: auto;
19 | `;
20 |
21 | const Icon = styled.div`
22 | width: 2rem;
23 | height: 2rem;
24 | border-radius: 2px;
25 | background: #333;
26 | margin-right: 0.5rem;
27 | `;
28 |
29 | const Sub = styled.div`
30 | display: flex;
31 | align-items: center;
32 | `;
33 | const Point = styled.div`
34 | width: 0.5rem;
35 | height: 0.5rem;
36 | border-radius: 50%;
37 | margin-right: 0.25rem;
38 | `;
39 | const Desc = styled.span`
40 | opacity: 0.5;
41 | + ${Point} {
42 | margin-left: 0.5rem;
43 | }
44 | `;
45 |
46 | const Flex = styled.div`
47 | display: flex;
48 | margin-bottom: 0.5rem;
49 | align-items: center;
50 | `;
51 |
52 | const SliderBox = styled.div`
53 | width: 100%;
54 | display: flex;
55 | height: 4rem;
56 | align-items: center;
57 | margin-bottom: 14rem;
58 | `;
59 |
60 | const ColorBlock = styled.div`
61 | width: 2rem;
62 | height: 2rem;
63 | background: ${props => hsl(props.h, props.s, props.l)};
64 | cursor: pointer;
65 | color: ${props => hsl(props.h, props.s, props.l - 0.5 > 0 ? props.l - 0.5 : 0)};
66 | display: flex;
67 | align-items: center;
68 | justify-content: center;
69 | font-size: 0.7rem;
70 | &:active {
71 | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
72 | transform: scale(1.2);
73 | z-index: 10;
74 | border-radius: 2px;
75 | }
76 | `;
77 |
78 | const Group = styled.div`
79 | width: 8rem;
80 | height: 8rem;
81 | position: fixed;
82 | top: 7rem;
83 | left: 2.8rem;
84 | transform: rotate(${props => props.deg}deg);
85 | `;
86 |
87 | /// /////////////////////////////////////////////
88 | // connect
89 | /// /////////////////////////////////////////////
90 | const State = state => {
91 | return {
92 | check: state.check,
93 | loading: state.loading.global || state.colors.length === 0,
94 | colors: state.colors || {},
95 | };
96 | };
97 |
98 | const Dispatch = dispatch => ({
99 | getColors() {
100 | dispatch({ type: `colors/get` });
101 | },
102 | });
103 |
104 | /// /////////////////////////////////////////////
105 | // component
106 | /// /////////////////////////////////////////////
107 |
108 | class Colors extends Component {
109 | state = {
110 | tab: '色板',
111 | activeColor: null,
112 | border: false,
113 | count: 8,
114 | header: {},
115 | };
116 |
117 | localStorageName = 'dropdown-color';
118 |
119 | componentDidMount() {
120 | this.props.getColors();
121 | let state = localStorage.getItem(this.localStorageName);
122 | if (state) {
123 | this.setState({ header: JSON.parse(state) });
124 | } else {
125 | localStorage.setItem(this.localStorageName, '{}');
126 | }
127 | }
128 |
129 | SwitchTitle = ({ name }) => (
130 | this.setState({ tab: name })}>
131 | {name}
132 |
133 | );
134 |
135 | mapColor = group => {
136 | const List = [];
137 | _.forEach(group, (data, key) => {
138 | const desc = (
139 |
140 | {data.desc}
141 |
142 | );
143 | List.push(
144 | this.handleClick(data.color)}>
145 |
146 |
147 | {data.name}
148 | {desc}
149 |
150 | |
151 | );
152 | });
153 | return List;
154 | };
155 |
156 | ColorCircle = () => {
157 | const List = [];
158 | const Groups = [];
159 |
160 | const alpha = (1 - 0.575) / 5;
161 | for (let i = 0; i < this.state.count; i++) {
162 | let Colors = [];
163 | let angle = 220 + (360 / this.state.count) * i;
164 | if (angle > 360) angle = angle - 360;
165 | List.push(
166 |
167 |
168 |
169 | );
170 | for (let i = 6; i > 0; i--) {
171 | const l = 1 - alpha * i;
172 | Colors.push(
173 | this.handleClick(hsl(angle, 1, l))}
179 | >
180 | {5 - i > 0 ? `+${5 - i}` : 5 - i}
181 |
182 | );
183 | }
184 | const hslColor = hsl(angle, 1, 0.575);
185 | Groups.push(
186 |
187 |
188 | {hslColor}
189 |
190 | );
191 | Groups.push({Colors} );
192 | }
193 |
194 | return (
195 |
196 |
197 | this.setState({ count: e })}
203 | />
204 |
205 | {List}
206 | 色彩列表
207 | {Groups}
208 |
209 | );
210 | };
211 |
212 | ColorView = () => {
213 | const List = [];
214 | _.forEach(this.props.colors, (group, key) => {
215 | const active = !this.state.header[key];
216 | List.push(
217 |
218 | this.handleHeader(key)}>
219 | {key}
220 |
221 |
222 | {this.mapColor(group)}
223 |
224 |
225 | );
226 | });
227 | return List;
228 | };
229 | CheckView = () => (this.props.loading ? : );
230 |
231 | render() {
232 | return (
233 | <>
234 |
235 |
236 |
237 |
238 |
239 |
240 | {this.state.tab === '色板' ? : }
241 |
242 |
243 |
244 | 描边:
245 | this.setState({ border: e })}
249 | />
250 |
251 |
252 |
253 |
254 | >
255 | );
256 | }
257 |
258 | handleHeader = name => {
259 | const newState = this.state.header;
260 | newState[name] = !this.state.header[name];
261 | this.setState({ header: newState });
262 | localStorage.setItem(this.localStorageName, JSON.stringify(newState));
263 | };
264 |
265 | handleClick = color => {
266 | const data = { border: this.state.border, color };
267 | PostMessage('handleColor', JSON.stringify(data));
268 | };
269 | }
270 |
271 | export default connect(
272 | State,
273 | Dispatch
274 | )(Colors);
275 |
--------------------------------------------------------------------------------
/panel/routes/Dev.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { Iconfont, Title, Close, View } from '../components';
3 | import { PostMessage } from '../utils/PostMessage';
4 |
5 | /// /////////////////////////////////////////////
6 | // component
7 | /// /////////////////////////////////////////////
8 |
9 | class Dev extends Component {
10 | render() {
11 | return (
12 | <>
13 | ⚙️
14 |
15 | PostMessage('devNumber', null)}
20 | />
21 | PostMessage('devSymbol', null)}
26 | />
27 | PostMessage('devColor', null)}
32 | />
33 | PostMessage('devTest', null)}
38 | />
39 |
40 |
41 | >
42 | );
43 | }
44 | }
45 |
46 | export default Dev;
47 |
--------------------------------------------------------------------------------
/panel/routes/Docs.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import styled from 'styled-components';
3 | import { Input } from 'antd';
4 | import { connect } from 'dva';
5 | import { Title, View, Close, Cell, Check, Loading } from '../components';
6 | import _ from 'lodash';
7 | import { PostMessage } from '../utils/PostMessage';
8 | import { CopyToClipboard } from 'react-copy-to-clipboard';
9 |
10 | const Search = Input.Search;
11 | /// /////////////////////////////////////////////
12 | // styled
13 | /// /////////////////////////////////////////////
14 |
15 | const Panel = styled.div`
16 | padding: 1rem;
17 | width: 9rem;
18 | height: calc(100% - 50px);
19 | overflow-y: auto;
20 | `;
21 |
22 | const SearchBlock = styled.div`
23 | transform: scale(0.9);
24 | position: fixed;
25 | top: -1px;
26 | right: 0;
27 | width: 15rem;
28 | `;
29 |
30 | const LibraryView = styled.div`
31 | height: 100%;
32 | flex: 1;
33 | padding: 1rem;
34 | background: #eee;
35 | overflow-y: auto;
36 | `;
37 |
38 | const LibGroup = styled.div`
39 | margin-bottom: 2rem;
40 | `;
41 |
42 | const LibHeader = styled.div`
43 | color: #333;
44 | font-size: 1.1rem;
45 | font-weight: 600;
46 | padding-bottom: 0.5rem;
47 | border-bottom: 3px solid #ddd;
48 | `;
49 |
50 | const LibBlock = styled.div`
51 | margin-bottom: 0.5rem;
52 |
53 | padding: 1rem 0;
54 | padding-bottom: 1.5rem;
55 | border-bottom: 1px dashed #ddd;
56 | `;
57 |
58 | const LibTitle = styled.span`
59 | display: block;
60 | font-weight: 600;
61 | color: #2a72ff;
62 | margin-bottom: 0.5rem;
63 | height: 1.5rem;
64 | border-radius: 0.2rem;
65 | line-height: 1.5rem;
66 | padding: 0 0.4rem;
67 | margin-right: 0.3rem;
68 | margin-bottom: 0.3rem;
69 | cursor: pointer;
70 | background: rgba(255, 255, 255, 0.5);
71 | transition: all ease 0.2s;
72 | &:hover {
73 | background: #2a72ff;
74 | color: #fff;
75 | }
76 | &:active {
77 | transform: scale(0.95);
78 | }
79 | `;
80 |
81 | const LibTitleBlock = styled.div`
82 | display: flex;
83 | flex-wrap: wrap;
84 | margin-bottom: 0.5rem;
85 | `;
86 |
87 | const LibContent = styled.div`
88 | display: flex;
89 | flex-wrap: wrap;
90 | margin-top: 0.5rem;
91 | `;
92 |
93 | const LibDesc = styled.div`
94 | font-size: 0.7rem;
95 | color: #666;
96 | text-align: justify;
97 | margin-bottom: 0.3rem;
98 | `;
99 |
100 | const LibTag = styled.span`
101 | display: block;
102 | font-size: 0.8rem;
103 | color: #fff;
104 | border-radius: 0.2rem;
105 | line-height: 1.2rem;
106 | padding: 0 0.3rem;
107 | margin-right: 0.2rem;
108 | margin-bottom: 0.2rem;
109 | `;
110 |
111 | const LibTrue = styled(LibTag)`
112 | background: #24a69b;
113 | cursor: pointer;
114 | transition: all ease 0.2s;
115 | &:hover {
116 | font-weight: 600;
117 | }
118 | &:active {
119 | transform: scale(0.95);
120 | }
121 | `;
122 |
123 | const LibWrong = styled(LibTag)`
124 | background: #f25535;
125 | `;
126 |
127 | /// /////////////////////////////////////////////
128 | // connect
129 | /// /////////////////////////////////////////////
130 | const State = state => {
131 | return {
132 | check: state.check,
133 | loading: state.loading.global || state.docs.length === 0,
134 | docs: state.docs,
135 | };
136 | };
137 |
138 | const Dispatch = dispatch => ({
139 | getDocs() {
140 | dispatch({ type: `docs/get` });
141 | },
142 | });
143 |
144 | /// /////////////////////////////////////////////
145 | // component
146 | /// /////////////////////////////////////////////
147 |
148 | class Docs extends Component {
149 | state = {
150 | activeRoot: 0,
151 | activeGroup: 0,
152 | search: false,
153 | keywords: null,
154 | header: {},
155 | };
156 |
157 | localStorageName = 'dropdown-word';
158 |
159 | componentDidMount() {
160 | this.props.getDocs();
161 | let state = localStorage.getItem(this.localStorageName);
162 | if (state) {
163 | this.setState({ header: JSON.parse(state) });
164 | } else {
165 | localStorage.setItem(this.localStorageName, '{}');
166 | }
167 | }
168 |
169 | mapData = (data, index) => {
170 | const active = !this.state.header[data.title];
171 | return (
172 |
173 | this.handleHeader(data.title)}>
174 | {data.title}
175 |
176 |
177 | {data.group.map((data, i) => this.mapGroup(data, i, index))}
178 |
179 |
180 | );
181 | };
182 |
183 | mapGroup = (data, indexGroup, indexRoot) => {
184 | const active = this.state.activeRoot === indexRoot && this.state.activeGroup === indexGroup;
185 | return (
186 | this.handleGroup(indexRoot, indexGroup)}
190 | >
191 | {indexGroup + 1}.{data.title}
192 |
193 | );
194 | };
195 |
196 | mapLib = (data, index) => {
197 | return (
198 |
199 |
200 | {this.state.activeGroup + 1}.{index + 1} {data.title}
201 |
202 | {data.group.map(this.mapLibGroup)}
203 |
204 | );
205 | };
206 |
207 | mapLibGroup = (data, index) => {
208 | return (
209 |
210 |
211 | {data.title.map((t, i) => (
212 |
213 | this.handleCopy(t)}>{t}
214 |
215 | ))}
216 |
217 | {data.desc.split(/\n/g).map((t, i) => (
218 | {t}
219 | ))}
220 |
221 | ✓
222 | {data.true && data.true.length > 0
223 | ? data.true.map((t, i) => (
224 |
225 | this.handleCopy(t)}>{t}
226 |
227 | ))
228 | : null}
229 |
230 |
231 | ✗
232 | {data.wrong && data.wrong.length > 0
233 | ? data.wrong.map((t, i) => {t} )
234 | : null}
235 |
236 |
237 | );
238 | };
239 |
240 | SearchResult = () => {
241 | let newData = [];
242 | _.forEach(this.props.docs, h1 => {
243 | _.forEach(h1.group, h2 => {
244 | _.forEach(h2.group, h3 => {
245 | newData = newData.concat(h3.group);
246 | });
247 | });
248 | });
249 |
250 | const result1 = [];
251 | const result2 = [];
252 | const result3 = [];
253 | const result4 = [];
254 |
255 | _.forEach(newData, d => {
256 | if (JSON.stringify(d.title).indexOf(this.state.keywords) > -1) result1.push(d);
257 | if (d.true && JSON.stringify(d.true).indexOf(this.state.keywords) > -1) result2.push(d);
258 | if (d.wrong && JSON.stringify(d.wrong).indexOf(this.state.keywords) > -1) result3.push(d);
259 | if (d.desc.indexOf(this.state.keywords) > -1) result4.push(d);
260 | });
261 |
262 | const result = _.uniq([].concat(result1, result2, result3, result4));
263 |
264 | return (
265 |
266 | {result.length > 0 ? result.map(this.mapLibGroup) : '对不起,未找到匹配结果...'}
267 |
268 | );
269 | };
270 |
271 | DocsView = () => (
272 | <>
273 | {this.props.docs.map(this.mapData)}
274 |
275 | {this.state.search ? (
276 |
277 | ) : (
278 | this.props.docs[this.state.activeRoot].group[this.state.activeGroup].group.map(
279 | this.mapLib
280 | )
281 | )}
282 |
283 | >
284 | );
285 | CheckView = () => (this.props.loading ? : );
286 |
287 | render() {
288 | return (
289 | <>
290 |
291 | 话术
292 |
293 |
294 |
295 |
296 |
297 | {this.props.check ? : }
298 |
299 |
300 | >
301 | );
302 | }
303 |
304 | handleHeader = name => {
305 | const newState = this.state.header;
306 | newState[name] = !this.state.header[name];
307 | this.setState({ header: newState });
308 | localStorage.setItem(this.localStorageName, JSON.stringify(newState));
309 | };
310 |
311 | handleGroup = (activeRoot, activeGroup) => {
312 | this.setState({ search: false, activeRoot, activeGroup });
313 | };
314 |
315 | handleSearch = value => {
316 | this.setState({ search: true, keywords: value });
317 | };
318 |
319 | handleCopy = value => {
320 | PostMessage('handleWord', value);
321 | };
322 | }
323 |
324 | export default connect(
325 | State,
326 | Dispatch
327 | )(Docs);
328 |
--------------------------------------------------------------------------------
/panel/routes/Layer.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { Iconfont, Title, Close, View } from '../components';
3 | import { PostMessage } from '../utils/PostMessage';
4 |
5 | /// /////////////////////////////////////////////
6 | // component
7 | /// /////////////////////////////////////////////
8 |
9 | class Layer extends Component {
10 | render() {
11 | return (
12 | <>
13 | 层
14 |
15 | PostMessage('handleLayout', null)}
20 | />
21 | PostMessage('handleSort', null)}
26 | />
27 | PostMessage('handleTopLite', null)}
32 | />
33 | PostMessage('handleBottomLite', null)}
38 | />
39 | PostMessage('handleTop', null)}
44 | />
45 | PostMessage('handleBottom', null)}
50 | />
51 | PostMessage('handleHeight', null)}
56 | />
57 | PostMessage('handleBlender', null)}
62 | />
63 |
64 |
65 | >
66 | );
67 | }
68 | }
69 |
70 | export default Layer;
71 |
--------------------------------------------------------------------------------
/panel/routes/Line.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { Iconfont, Title, Close, View } from '../components';
3 | import { PostMessage } from '../utils/PostMessage';
4 |
5 | /// /////////////////////////////////////////////
6 | // component
7 | /// /////////////////////////////////////////////
8 |
9 | class Line extends Component {
10 | render() {
11 | return (
12 | <>
13 | 线
14 |
15 | PostMessage('handleLine', null)}
20 | />
21 | PostMessage('handleChange', null)}
26 | />
27 | PostMessage('handleDash', null)}
32 | />
33 | PostMessage('setRound', null)}
38 | />
39 | PostMessage('setIf', null)}
44 | />
45 |
46 |
47 | >
48 | );
49 | }
50 | }
51 |
52 | export default Line;
53 |
--------------------------------------------------------------------------------
/panel/routes/Note.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { Iconfont, Title, Close, View } from '../components';
3 | import { PostMessage } from '../utils/PostMessage';
4 |
5 | /// /////////////////////////////////////////////
6 | // component
7 | /// /////////////////////////////////////////////
8 |
9 | class Note extends Component {
10 | render() {
11 | return (
12 | <>
13 | 注
14 |
15 | PostMessage('setHeader', null)}
20 | />
21 | PostMessage('setSubHeader', null)}
26 | />
27 | PostMessage('setText', null)}
32 | />
33 | PostMessage('setBlock', null)}
38 | />
39 | PostMessage('setList', null)}
44 | />
45 | PostMessage('setUl', null)}
50 | />
51 | PostMessage('setPoint', null)}
56 | />
57 | PostMessage('setChangelog', null)}
62 | />
63 |
64 |
65 | >
66 | );
67 | }
68 | }
69 |
70 | export default Note;
71 |
--------------------------------------------------------------------------------
/panel/routes/Plate.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { Iconfont, Title, Close, View } from '../components';
3 | import { PostMessage } from '../utils/PostMessage';
4 |
5 | /// /////////////////////////////////////////////
6 | // component
7 | /// /////////////////////////////////////////////
8 |
9 | class Layer extends Component {
10 | render() {
11 | return (
12 | <>
13 | 板
14 |
15 | PostMessage('handleIgnore', null)}
20 | />
21 | PostMessage('handleTitle', null)}
26 | />
27 | PostMessage('handlePlate', null)}
32 | />
33 | PostMessage('handleExport', null)}
38 | />
39 |
40 |
41 | >
42 | );
43 | }
44 | }
45 |
46 | export default Layer;
47 |
--------------------------------------------------------------------------------
/panel/routes/Setting.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import styled from 'styled-components';
3 | import { Form, Input, Button, Switch, AutoComplete } from 'antd';
4 | import { connect } from 'dva';
5 | import QueueAnim from 'rc-queue-anim';
6 | import { Title, View, ButtonGroup } from '../components';
7 | import { PostMessage } from '../utils/PostMessage';
8 |
9 | const FormItem = Form.Item;
10 |
11 | /// /////////////////////////////////////////////
12 | // styled
13 | /// /////////////////////////////////////////////
14 |
15 | const Version = styled.div`
16 | font-weight: 600;
17 | font-size: 1.2rem;
18 | padding-bottom: 1rem;
19 | margin-bottom: 1rem;
20 | opacity: 0.8;
21 | span {
22 | font-weight: 400;
23 | font-size: 0.8rem;
24 | opacity: 0.5;
25 | }
26 | border-bottom: 1px solid rgba(100, 100, 100, 0.2);
27 | `;
28 |
29 | /// /////////////////////////////////////////////
30 | // connect
31 | /// /////////////////////////////////////////////
32 |
33 | const State = state => {
34 | return {
35 | username: state.username,
36 | store: state.store,
37 | ...state.store,
38 | ...state.config,
39 | };
40 | };
41 |
42 | const Dispatch = dispatch => ({
43 | setConfig(obj) {
44 | dispatch({ type: `config/update`, payload: obj });
45 | },
46 | setStore(obj) {
47 | dispatch({ type: `store/update`, payload: obj });
48 | },
49 | gerUsers(name) {
50 | dispatch({ type: `username/get`, payload: name });
51 | },
52 | });
53 |
54 | /// /////////////////////////////////////////////
55 | // component
56 | /// /////////////////////////////////////////////
57 |
58 | class Setting extends Component {
59 | state = {
60 | name: '',
61 | theme: 'black',
62 | title: 'low',
63 | devMode: false,
64 | };
65 |
66 | version = localStorage.getItem('version');
67 |
68 | componentDidMount() {
69 | this.setState(this.props.store);
70 | }
71 |
72 | handleSearch = value => {
73 | this.props.gerUsers(value);
74 | console.log(this.props.username);
75 | };
76 |
77 | render() {
78 | return (
79 | <>
80 | 设置
81 |
82 |
83 |
84 | ANTO ver{this.version ? this.version : '1.0.0'}
85 |
86 |
87 | this.handleChange(e, 'name')}
92 | placeholder="请输入花名"
93 | />
94 |
95 |
96 |
102 |
103 |
104 |
110 |
111 |
112 |
118 |
119 |
120 | PostMessage('handleYuque', null)}>CanisMinor (倏昱)
121 |
122 |
123 |
124 |
128 | 取消
129 |
130 |
131 | 保存
132 |
133 |
134 |
135 | >
136 | );
137 | }
138 |
139 | handleChange = (e, key) => {
140 | this.setState({ [key]: e });
141 | };
142 |
143 | handleDev = bool => {
144 | this.setState({ devMode: bool });
145 | };
146 |
147 | handleTheme = bool => {
148 | this.setState({ theme: bool ? 'black' : 'white' });
149 | };
150 |
151 | handleTitle = bool => {
152 | this.setState({ title: bool ? 'strong' : 'low' });
153 | };
154 |
155 | handleClose = () => {
156 | this.props.setConfig({ setting: false });
157 | PostMessage('closeSetting', null);
158 | };
159 |
160 | handleSave = () => {
161 | this.props.setStore(this.state);
162 | this.props.setConfig({ setting: false });
163 | PostMessage('closeSetting', this.state);
164 | };
165 | }
166 |
167 | export default connect(
168 | State,
169 | Dispatch
170 | )(Setting);
171 |
--------------------------------------------------------------------------------
/panel/routes/Symbols.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { connect } from 'dva';
3 | import styled from 'styled-components';
4 | import _ from 'lodash';
5 | import { Button, Icon } from 'antd';
6 | import { Title, Close, View, ListView, Cell, Loading, Check } from '../components';
7 | import { PostMessage } from '../utils/PostMessage';
8 | import { join } from 'path';
9 | /// /////////////////////////////////////////////
10 | // styled
11 | /// /////////////////////////////////////////////
12 |
13 | const Cover = styled.div`
14 | width: 2rem;
15 | min-width: 2rem;
16 | height: 2rem;
17 | display: flex;
18 | align-items: center;
19 | justify-content: center;
20 | margin-right: 0.5rem;
21 | background: rgba(100, 100, 100, 0.2);
22 | border-radius: 0.1rem;
23 | > img {
24 | max-width: 100%;
25 | max-height: 100%;
26 | zoom: 0.5;
27 | }
28 | `;
29 |
30 | const ImgTitle = styled.div`
31 | transition: all 0.2s ease-out;
32 | margin-top: 1rem;
33 | width: 100%;
34 | font-size: 0.7rem;
35 | text-align: center;
36 | color: #666;
37 | `;
38 |
39 | const Img = styled.div`
40 | display: block;
41 | margin-bottom: 3rem;
42 | cursor: pointer;
43 | width: 100%;
44 | display: flex;
45 | flex-direction: column;
46 | align-items: center;
47 | img {
48 | transition: all 0.2s ease-out;
49 | zoom: 0.5;
50 | max-width: 100%;
51 | max-height: 6rem;
52 | }
53 | &:hover {
54 | ${ImgTitle} {
55 | color: #2b79ff;
56 | }
57 | img {
58 | transform: scale(1.05);
59 | }
60 | }
61 | &:active {
62 | img {
63 | transform: scale(0.95);
64 | }
65 | }
66 | `;
67 |
68 | const LibraryView = styled.div`
69 | height: 100%;
70 | flex: 1;
71 | padding: 1rem;
72 | background: #eee;
73 | overflow-y: auto;
74 | `;
75 |
76 | const ListBtn = styled.div`
77 | flex: 1;
78 | display: flex;
79 | align-items: center;
80 | justify-content: center;
81 | font-size: 0.8rem;
82 | background: rgba(100, 100, 100, 0.3);
83 | padding: 0.2rem;
84 | margin-bottom: 0.8rem;
85 | border-radius: 0.2rem;
86 |
87 | cursor: pointer;
88 | &:hover {
89 | background: rgba(100, 100, 100, 0.5);
90 | }
91 | `;
92 |
93 | /// /////////////////////////////////////////////
94 | // connect
95 | /// /////////////////////////////////////////////
96 | const State = state => {
97 | return {
98 | check: state.check,
99 | loading: state.loading.global || Object.keys(state.symbols).length === 0,
100 | symbols: state.symbols,
101 | ...state.store,
102 | };
103 | };
104 |
105 | const Dispatch = dispatch => ({
106 | getSymbols() {
107 | dispatch({ type: `symbols/get` });
108 | },
109 | });
110 |
111 | /// /////////////////////////////////////////////
112 | // component
113 | /// /////////////////////////////////////////////
114 |
115 | class Symbols extends Component {
116 | state = {
117 | local: false,
118 | tab: '场景',
119 | activeLocal: false,
120 | activeHeader: {},
121 | };
122 |
123 | localStorageName = 'dropdown-symbols';
124 |
125 | componentDidMount() {
126 | this.props.getSymbols();
127 | if (this.props.mode === '交互') this.setState({ tab: '交互' });
128 | }
129 |
130 | SwitchTitle = ({ name }) => (
131 | this.setState({ tab: name })}>
132 | {name}
133 |
134 | );
135 |
136 | Cell = ({ name, data, type }) => {
137 | const List = [];
138 | _.forEach(data, (value, title) =>
139 | List.push(
140 | this.handleGroup(type, name, title)}>
141 |
142 |
143 |
144 | {title}
145 | |
146 | )
147 | );
148 | return List;
149 | };
150 |
151 | Cells = ({ data, type }) => {
152 | const List = [];
153 | if (!this.state.activeHeader[type]) {
154 | const header = this.state.activeHeader;
155 | header[type] = {};
156 | this.setState({ activeHeader: header });
157 | }
158 | _.forEach(data, (value, key) => {
159 | const active = !this.state.activeHeader[type][key];
160 | List.push(
161 |
162 | this.handleHeader(type, key)}>
163 | {key}
164 |
165 |
166 |
167 |
168 |
169 | );
170 | });
171 | return (
172 |
173 | {this.props.check ? (
174 | this.handleRule(this.state.tab)}>
175 |
176 | 查看规范
177 |
178 | ) : null}
179 | {List}
180 |
181 | );
182 | };
183 |
184 | Library = ({ data, type }) => {
185 | const List = [];
186 | let activeRoot;
187 | let activeGroup;
188 | if (!this.state[type]) {
189 | activeRoot = _.keys(data)[0];
190 | activeGroup = _.keys(data[activeRoot])[0];
191 | this.setState({ [type]: { activeRoot, activeGroup } });
192 | } else {
193 | activeRoot = this.state[type].activeRoot;
194 | activeGroup = this.state[type].activeGroup;
195 | }
196 | const SymbolData = data[activeRoot][activeGroup];
197 | _.forEach(SymbolData, (value, key) => {
198 | const postData = {
199 | id: value.id,
200 | libname: this.props.symbols[type].libname,
201 | type,
202 | };
203 | List.push(
204 | PostMessage('handleSymbol', JSON.stringify(postData))}>
205 |
206 | {this.getName(value.name[2])}
207 |
208 | );
209 | });
210 | return {List} ;
211 | };
212 |
213 | View = ({ data, type }) => (
214 |
215 |
216 |
217 |
218 | );
219 |
220 | MainView = () => {
221 | const List = [];
222 | _.forEach(this.props.symbols, t => {
223 | List.push( );
224 | });
225 | return List;
226 | };
227 |
228 | ChildView = ({ name, type }) => {
229 | return this.state.tab === name ? (
230 |
231 | ) : null;
232 | };
233 |
234 | CheckView = () => (this.props.loading ? : );
235 |
236 | Tabs = () => {
237 | let Tab = [];
238 | _.forEach(this.props.symbols, t => {
239 | Tab.push( );
240 | });
241 | return Tab;
242 | };
243 |
244 | render() {
245 | return (
246 | <>
247 | {this.props.loading ? null : }
248 | {this.props.check ? : }
249 |
250 | >
251 | );
252 | }
253 |
254 | getImg = (type, id) => {
255 | return 'http://' + join('anto.inc.alipay.net/static', type, 'img', id + '.png');
256 | };
257 |
258 | getName = name => {
259 | let newName = name.split('-');
260 | return newName.length === 2 ? newName[1] : name;
261 | };
262 |
263 | handleRule = tab => {
264 | PostMessage('handleRule', tab);
265 | };
266 |
267 | handleHeader = (type, name) => {
268 | const newState = this.state.activeHeader;
269 | newState[type][name] = !this.state.activeHeader[type][name];
270 | this.setState({ activeHeader: newState });
271 | localStorage.setItem(this.localStorageName, JSON.stringify(newState));
272 | };
273 |
274 | handleGroup = (type, activeRoot, activeGroup) => {
275 | this.setState({ [type]: { activeRoot, activeGroup } });
276 | };
277 | }
278 |
279 | export default connect(
280 | State,
281 | Dispatch
282 | )(Symbols);
283 |
--------------------------------------------------------------------------------
/panel/routes/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { connect } from 'dva';
3 | import styled from 'styled-components';
4 | import { Iconfont } from '../components';
5 | import QueueAnim from 'rc-queue-anim';
6 | import { PostMessage } from '../utils/PostMessage';
7 | // Panel
8 | import Symbols from './Symbols';
9 | import Colors from './Colors';
10 | import Line from './Line';
11 | import Note from './Note';
12 | import Layer from './Layer';
13 | import Plate from './Plate';
14 | import Docs from './Docs';
15 | import Dev from './Dev';
16 | import Setting from './Setting';
17 |
18 | /// /////////////////////////////////////////////
19 | // styled
20 | /// /////////////////////////////////////////////
21 |
22 | const View = styled.div`
23 | display: flex;
24 | color: ${props => (props.theme === 'black' ? '#aaa' : '#777')};
25 | background: ${props => (props.theme === 'black' ? '#222' : '#f5f5f5')};
26 | `;
27 |
28 | const SideBar = styled.div`
29 | background: ${props => (props.theme === 'black' ? '#222' : '#f5f5f5')};
30 | width: 48px;
31 | height: 100vh;
32 | overflow: hidden;
33 | position: fixed;
34 | display: flex;
35 | flex-direction: column;
36 | align-items: center;
37 | top: 0;
38 | left: 0;
39 | z-index: 100;
40 | border-right: ${props =>
41 | props.theme === 'black' ? '1px solid rgba(0, 0, 0, 0.1)' : '1px solid #eee'};
42 | z-index: 999;
43 | `;
44 |
45 | const Panel = styled.div`
46 | margin-left: 48px;
47 | width: 100%;
48 | flex: 1;
49 | height: 100vh;
50 | `;
51 |
52 | const Logo = styled.div`
53 | width: 100%;
54 | height: 48px;
55 | background-image: url('logo.png');
56 | background-position: center;
57 | background-repeat: no-repeat;
58 | background-size: 28px auto;
59 | box-shadow: ${props =>
60 | props.theme === 'black' ? '0 4px 12px rgba(0, 0, 0, 0.2)' : '0 4px 8px rgba(0, 0, 0, 0.05)'};
61 | `;
62 |
63 | const Mode = styled.div`
64 | position: fixed;
65 | bottom: 1.8rem;
66 | width: 2.5rem;
67 | color: #fff;
68 | font-size: 0.7rem;
69 | text-align: center;
70 | line-height: 1.5rem;
71 | opacity: 0.4;
72 | transition: all 0.2s ease-out;
73 | cursor: pointer;
74 | border-radius: 1.5rem;
75 | &:hover {
76 | opacity: 1;
77 | }
78 | &:active {
79 | transform: scale(0.9);
80 | }
81 | `;
82 |
83 | const Close = styled.div`
84 | position: fixed;
85 | bottom: 0;
86 | width: 3rem;
87 | height: 2rem;
88 | line-height: 2rem;
89 | text-align: center;
90 | opacity: 0.2;
91 | transition: all 0.2s ease-out;
92 | cursor: pointer;
93 | &:hover {
94 | opacity: 1;
95 | }
96 | &:active {
97 | transform: scale(0.9);
98 | }
99 | `;
100 |
101 | /// /////////////////////////////////////////////
102 | // connect
103 | /// /////////////////////////////////////////////
104 |
105 | const State = state => {
106 | return {
107 | ...state.store,
108 | ...state.config,
109 | };
110 | };
111 |
112 | const Dispatch = dispatch => ({
113 | setConfig(obj) {
114 | dispatch({ type: `config/update`, payload: obj });
115 | },
116 | setStore(obj) {
117 | dispatch({ type: `store/update`, payload: obj });
118 | },
119 | check() {
120 | dispatch({ type: `check/get` });
121 | },
122 | });
123 |
124 | /// /////////////////////////////////////////////
125 | // component
126 | /// /////////////////////////////////////////////
127 |
128 | class WebView extends Component {
129 | state = {
130 | symbol: 370,
131 | color: 220,
132 | setting: 250,
133 | word: 370,
134 | };
135 |
136 | componentDidMount() {
137 | this.props.check();
138 | }
139 |
140 | SideBar = () => (
141 |
142 |
143 |
144 | this.openPanel('symbol', this.state.symbol)}
149 | more
150 | />
151 | this.openPanel('color', this.state.color)}
156 | more
157 | />
158 | this.openPanel('line')}
163 | more
164 | />
165 | this.openPanel('layer')}
170 | more
171 | />
172 | this.openPanel('note')}
177 | more
178 | />
179 | this.openPanel('plate')}
184 | more
185 | />
186 | this.openPanel('word', this.state.word)}
191 | more
192 | />
193 | PostMessage('handleYuque', null)}
198 | />
199 | {this.props.devMode ? (
200 | this.openPanel('dev')}
205 | />
206 | ) : null}
207 | this.openPanel('setting', this.state.setting)}
212 | />
213 |
214 |
219 | {this.props.mode}
220 |
221 | PostMessage('handleClose', null)} />
222 |
223 | );
224 |
225 | render() {
226 | return (
227 |
228 |
229 |
230 | {this.props.symbol ? : null}
231 | {this.props.color ? : null}
232 | {this.props.line ? : null}
233 | {this.props.note ? : null}
234 | {this.props.layer ? : null}
235 | {this.props.plate ? : null}
236 | {this.props.word ? : null}
237 | {this.props.dev ? : null}
238 | {this.props.setting ? : null}
239 |
240 |
241 | );
242 | }
243 |
244 | handleChangeMode = () => {
245 | const preMode = this.props.mode;
246 | const mode = preMode === '视觉' ? '交互' : '视觉';
247 | this.props.setStore({ mode });
248 | PostMessage('changeMode', mode);
249 | };
250 |
251 | openPanel = (name, width = null) => {
252 | PostMessage('openPanel', width);
253 | this.props.setConfig({ [name]: true });
254 | };
255 | }
256 |
257 | export default connect(
258 | State,
259 | Dispatch
260 | )(WebView);
261 |
--------------------------------------------------------------------------------
/panel/utils/PostMessage.js:
--------------------------------------------------------------------------------
1 | export const PostMessage = (name, data) => {
2 | try {
3 | window.postMessage(name, data);
4 | } catch (e) {
5 | console.log(name, data);
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/panel/utils/request.js:
--------------------------------------------------------------------------------
1 | import fetch from 'dva/fetch';
2 |
3 | const parseJSON = response => {
4 | return response.json();
5 | };
6 |
7 | const checkStatus = response => {
8 | if (response.status >= 200 && response.status < 300) return response;
9 | const error = new Error(response.statusText);
10 | error.response = response;
11 | throw error;
12 | };
13 |
14 | export const request = (url, options) => {
15 | return fetch(url, options)
16 | .then(checkStatus)
17 | .then(parseJSON)
18 | .then(data => ({ data }))
19 | .catch(err => ({ err }));
20 | };
21 |
--------------------------------------------------------------------------------
/preview/.webpackrc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | entry : "./src/index.js",
3 | disableCSSModules : true,
4 | ignoreMomentLocale : true,
5 | theme : {
6 | "@primary-color": "#2A72FF",
7 | "@text-color": "#999",
8 | "@heading-color": "#999",
9 | "@border-color-base":"rgba(100,100,100,.3)",
10 | "@input-bg":"rgba(255,255,255,.03)",
11 | "@input-placeholder-color":"rgba(100,100,100,.5)"
12 | },
13 | html : {
14 | "template": "./src/index.ejs"
15 | },
16 | define : {
17 | "$dirname": __dirname,
18 | "$isDev" : process.env.NODE_ENV === "development"
19 | },
20 | extraBabelPlugins : [
21 | 'lodash',
22 | ['import', {libraryName: 'antd', libraryDirectory: 'es', style: true}]
23 | ],
24 | env : {
25 | development: {
26 | extraBabelPlugins: [
27 | "dva-hmr",
28 | ["babel-plugin-styled-components", { displayName: true }]
29 | ]
30 | },
31 | production : {
32 | browserslist : ['iOS >= 8', 'Android >= 4'],
33 | }
34 | }
35 | };
36 |
37 |
--------------------------------------------------------------------------------
/preview/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anto-preview",
3 | "scripts": {
4 | "start": "cross-env ESLINT=none roadhog dev",
5 | "build": "cross-env ESLINT=none roadhog build"
6 | },
7 | "dependencies": {
8 | "antd": "^3.10.9",
9 | "dva": "^2.4.1",
10 | "dva-loading": "^2.0.6",
11 | "lodash": "^4.17.11",
12 | "react": "^16.6.3",
13 | "react-dom": "^16.6.3"
14 | },
15 | "devDependencies": {
16 | "@babel/core": "^7.1.2",
17 | "@babel/preset-env": "^7.1.0",
18 | "@babel/preset-stage-0": "^7.0.0",
19 | "@babel/runtime": "^7.1.2",
20 | "babel-plugin-dva-hmr": "^0.4.1",
21 | "babel-plugin-import": "^1.10.0",
22 | "babel-plugin-lodash": "^3.3.4",
23 | "babel-plugin-transform-decorators-legacy": "^1.3.5",
24 | "cross-env": "^5.2.0",
25 | "moment": "^2.22.2",
26 | "path": "^0.12.7",
27 | "polished": "^2.3.1",
28 | "rc-queue-anim": "^1.6.8",
29 | "redbox-react": "^1.3.2",
30 | "roadhog": "2.5.0-beta.1",
31 | "styled-components": "^4.0.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/preview/public/data.js:
--------------------------------------------------------------------------------
1 | localStorage.setItem(
2 | 'preview',
3 | '{"date":"2018-12-31","author":"花名","pages":[{"path":"preview/Page 1 (视觉) 1231.png","name":"Page 1","mode":"视觉","date":"1231","width":1175,"height":1892},{"path":"preview/a (视觉) 1231.png","name":"a","mode":"视觉","date":"1231","width":1728,"height":1976},{"path":"preview/Page 2 (交互) 1231.png","name":"Page 2","mode":"交互","date":"1231","width":1214,"height":1976}]}'
4 | );
5 |
--------------------------------------------------------------------------------
/preview/public/preview/Page 1 (视觉) 1231.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/preview/public/preview/Page 1 (视觉) 1231.png
--------------------------------------------------------------------------------
/preview/public/preview/Page 2 (交互) 1231.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/preview/public/preview/Page 2 (交互) 1231.png
--------------------------------------------------------------------------------
/preview/public/preview/a (视觉) 1231.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/preview/public/preview/a (视觉) 1231.png
--------------------------------------------------------------------------------
/preview/src/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #root {
4 | height: 100%;
5 | overflow: hidden;
6 | user-select: none;
7 | background: #222;
8 | }
9 |
10 | body {
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | -ms-text-size-adjust: 100%;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | *:focus {
18 | outline: none;
19 | }
20 |
21 | * {
22 | -webkit-font-smoothing: antialiased;
23 | }
24 |
25 | div {
26 | box-sizing: border-box;
27 | position: relative;
28 | }
29 |
30 | .logo {
31 | width: 120px;
32 | height: 50px;
33 | background-size: contain;
34 | background-image:url('data:image/png;base64,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');
35 | }
--------------------------------------------------------------------------------
/preview/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 | Anto
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/preview/src/index.js:
--------------------------------------------------------------------------------
1 | import { message } from 'antd';
2 | import dva from 'dva';
3 | import createLoading from 'dva-loading';
4 | import './index.css';
5 |
6 | // 1. Initialize
7 | const app = dva({
8 | onError(e) {
9 | message.error(e.message, 3);
10 | },
11 | });
12 |
13 | // 2. Models
14 | app.model(require('./models/preview').default);
15 |
16 | // 2. Plugins
17 | app.use(createLoading());
18 |
19 | // 3. Router
20 | app.router(require('./router').default);
21 |
22 | // 4. Start
23 | app.start('#root');
24 |
--------------------------------------------------------------------------------
/preview/src/models/preview.js:
--------------------------------------------------------------------------------
1 | export default {
2 | namespace: 'preview',
3 | state: {},
4 | reducers: {
5 | save(state, action) {
6 | state = action.payload;
7 | return state;
8 | },
9 | },
10 | effects: {
11 | *get(action, { call, put }) {
12 | yield put({
13 | type: 'save',
14 | payload: JSON.parse(localStorage.getItem('preview')),
15 | });
16 | },
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/preview/src/router.js:
--------------------------------------------------------------------------------
1 | import { Route, Router } from 'dva/router';
2 | import App from './routes';
3 |
4 | export default ({ app, history }) => {
5 | history.listen(() => window.scrollTo(0, 0));
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/preview/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { connect } from 'dva';
3 | import styled from 'styled-components';
4 | import { Icon } from 'antd';
5 |
6 | /// /////////////////////////////////////////////
7 | // styled
8 | /// /////////////////////////////////////////////
9 |
10 | const View = styled.div`
11 | width: 100%;
12 | height: 100%;
13 | `;
14 |
15 | const Privew = styled.div`
16 | display: flex;
17 | `;
18 |
19 | const Showcase = styled.div`
20 | width: 100%;
21 | height: 100vh;
22 | `;
23 |
24 | const Case = styled.div`
25 | overflow: auto;
26 | width: 100%;
27 | height: calc(100% - 4rem);
28 | `;
29 |
30 | const ImgCase = styled.div`
31 | > img {
32 | position: absolute;
33 | top: 0;
34 | left: 0;
35 | }
36 | `;
37 |
38 | const Header = styled.div`
39 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
40 | height: 4rem;
41 | padding: 0 0.5rem;
42 | display: flex;
43 | align-items: center;
44 | justify-content: space-between;
45 | z-index: 99;
46 | `;
47 |
48 | const List = styled.div`
49 | border-right: 1px solid rgba(255, 255, 255, 0.03);
50 | height: 100vh;
51 | overflow-x: hidden;
52 | overflow-y: auto;
53 | `;
54 |
55 | const Cell = styled.div`
56 | width: 20rem;
57 | padding: 1rem;
58 | display: flex;
59 | align-items: center;
60 | border-bottom: 1px solid rgba(255, 255, 255, 0.03);
61 | cursor: pointer;
62 | background: ${props => (props.active ? 'rgba(255,255,255,.02)' : '#222')};
63 | &:hover {
64 | background: #1f1f1f;
65 | }
66 | `;
67 |
68 | const Cover = styled.div`
69 | width: 3rem;
70 | height: 3rem;
71 | min-width: 3rem;
72 | background: url("${props => props.path}");
73 | background-size: cover;
74 | background-repeat: no-repeat;
75 | border-radius: .2rem;
76 | box-shadow: 0 4px 8px rgba(0,0,0,.2);
77 | `;
78 |
79 | const Content = styled.div`
80 | margin-left: 1rem;
81 | `;
82 | const Title = styled.div`
83 | display: flex;
84 | font-size: 0.9rem;
85 | align-items: center;
86 | overflow: hidden;
87 | text-overflow: ellipsis;
88 | white-space: nowrap;
89 | -webkit-line-clamp: 1;
90 | width: 14rem;
91 | font-weight: 500;
92 | `;
93 |
94 | const Tag = styled.div`
95 | font-size: 0.7rem;
96 | padding: 0.1rem 0.3rem;
97 | border-radius: 0.2rem;
98 | background: ${props => (props.mode === '交互' ? '#2A72FF' : '#333')};
99 | margin-right: 0.5rem;
100 | color: #fff;
101 | `;
102 |
103 | const Date = styled.div`
104 | color: #555;
105 | font-size: 0.8rem;
106 | margin-top: 0.2rem;
107 | `;
108 |
109 | const Group = styled.div`
110 | display: flex;
111 | align-items: center;
112 | `;
113 |
114 | const Zoom = styled.div`
115 | display: flex;
116 | align-items: center;
117 | margin: 0 3rem 0 1rem;
118 | `;
119 |
120 | const ZoomNum = styled.div`
121 | background: #181818;
122 | height: 2rem;
123 | display: flex;
124 | align-items: center;
125 | justify-content: center;
126 | width: 4rem;
127 | border-radius: 1rem;
128 | margin: 0 0.5rem;
129 | cursor: pointer;
130 | &:active {
131 | background: #111;
132 | }
133 | `;
134 |
135 | const ZoomIcon = styled.div`
136 | background: #2a72ff;
137 | height: 1.5rem;
138 | width: 1.5rem;
139 | color: #fff;
140 | display: flex;
141 | align-items: center;
142 | justify-content: center;
143 | border-radius: 50%;
144 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
145 | cursor: pointer;
146 | &:active {
147 | background: #155cd5;
148 | }
149 | `;
150 |
151 | const PageTitle = styled.div`
152 | font-size: 1.2rem;
153 | color: #fff;
154 | font-weight: 600;
155 | `;
156 |
157 | const Author = styled.div`
158 | background: #333;
159 | padding: 0.5rem 0.8rem;
160 | border: 2px solid #222;
161 | outline: 1px solid #333;
162 | `;
163 |
164 | const Name = styled.span`
165 | color: #fff;
166 | font-weight: 600;
167 | margin-right: 0.5rem;
168 | `;
169 |
170 | const Time = styled.span`
171 | font-size: 0.8rem;
172 | `;
173 |
174 | /// /////////////////////////////////////////////
175 | // connect
176 | /// /////////////////////////////////////////////
177 |
178 | const State = state => {
179 | return {
180 | loading: state.loading.models.preview || !state.preview.date,
181 | preview: state.preview,
182 | };
183 | };
184 |
185 | const Dispatch = dispatch => ({
186 | getPreview() {
187 | dispatch({ type: 'preview/get' });
188 | },
189 | });
190 |
191 | /// /////////////////////////////////////////////
192 | // component
193 | /// /////////////////////////////////////////////
194 |
195 | class App extends Component {
196 | state = {
197 | zoom: 1,
198 | };
199 |
200 | componentDidMount() {
201 | this.props.getPreview();
202 | }
203 |
204 | List = page => {
205 | return (
206 | this.handleClick(page)}
210 | >
211 |
212 |
213 | {page.name}
214 |
215 | {page.date[0]}
216 | {page.date[1]}-{page.date[2]}
217 | {page.date[3]} | {page.mode}
218 |
219 |
220 | |
221 | );
222 | };
223 |
224 | Page = ({ data }) => {
225 | if (!this.state.name) this.setState({ ...data.pages[0] });
226 | return (
227 |
228 |
229 |
237 | {data.pages.map(this.List)}
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 | {Math.floor(this.state.zoom * 100)}%
247 |
248 |
249 |
250 |
251 |
252 | {this.state.name} ({this.state.mode})
253 |
254 |
255 |
256 | {data.author}
257 | {data.date}
258 |
259 |
260 |
261 |
262 |
270 |
271 |
272 |
273 |
274 | );
275 | };
276 |
277 | render() {
278 | return (
279 | {this.props.loading ? 'Loading...' : }
280 | );
281 | }
282 |
283 | handleReset = () => {
284 | this.setState({ zoom: 1 });
285 | };
286 |
287 | handleMinus = () => {
288 | const zoom = this.state.zoom * 0.8;
289 | this.setState({ zoom });
290 | };
291 |
292 | handlePlus = () => {
293 | const zoom = this.state.zoom * 1.2;
294 | this.setState({ zoom });
295 | };
296 |
297 | handleClick = page => {
298 | this.setState({ ...page, zoom: 1 });
299 | };
300 | }
301 |
302 | export default connect(
303 | State,
304 | Dispatch
305 | )(App);
306 |
--------------------------------------------------------------------------------
/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/icon.png
--------------------------------------------------------------------------------
/public/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {font-family: "iconfont";
2 | src: url('iconfont.eot?t=1554361695475'); /* IE9 */
3 | src: url('iconfont.eot?t=1554361695475#iefix') format('embedded-opentype'), /* IE6-IE8 */
4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
5 | url('iconfont.woff?t=1554361695475') format('woff'),
6 | url('iconfont.ttf?t=1554361695475') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
7 | url('iconfont.svg?t=1554361695475#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family: "iconfont" !important;
12 | font-size: 16px;
13 | font-style: normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-main-layer:before {
19 | content: "\e601";
20 | }
21 |
22 | .icon-main-plate:before {
23 | content: "\e602";
24 | }
25 |
26 | .icon-main-note:before {
27 | content: "\e603";
28 | }
29 |
30 | .icon-main-line:before {
31 | content: "\e604";
32 | }
33 |
34 | .icon-main-color:before {
35 | content: "\e605";
36 | }
37 |
38 | .icon-main-symbol:before {
39 | content: "\e606";
40 | }
41 |
42 | .icon-main-yuque:before {
43 | content: "\e607";
44 | }
45 |
46 | .icon-line-note:before {
47 | content: "\e608";
48 | }
49 |
50 | .icon-line-dash:before {
51 | content: "\e609";
52 | }
53 |
54 | .icon-line-change:before {
55 | content: "\e60a";
56 | }
57 |
58 | .icon-line-if:before {
59 | content: "\e60b";
60 | }
61 |
62 | .icon-line-link:before {
63 | content: "\e60c";
64 | }
65 |
66 | .icon-layer-bottom-lite:before {
67 | content: "\e60e";
68 | }
69 |
70 | .icon-layer-sort:before {
71 | content: "\e60f";
72 | }
73 |
74 | .icon-layer-top-lite:before {
75 | content: "\e611";
76 | }
77 |
78 | .icon-layer-layout:before {
79 | content: "\e610";
80 | }
81 |
82 | .icon-layer-height:before {
83 | content: "\e613";
84 | }
85 |
86 | .icon-note-list:before {
87 | content: "\e614";
88 | }
89 |
90 | .icon-note-point:before {
91 | content: "\e615";
92 | }
93 |
94 | .icon-note-changelog:before {
95 | content: "\e617";
96 | }
97 |
98 | .icon-note-text:before {
99 | content: "\e619";
100 | }
101 |
102 | .icon-note-title:before {
103 | content: "\e61a";
104 | }
105 |
106 | .icon-note-block:before {
107 | content: "\e61f";
108 | }
109 |
110 | .icon-plate-ignore:before {
111 | content: "\e616";
112 | }
113 |
114 | .icon-plate-artboard:before {
115 | content: "\e618";
116 | }
117 |
118 | .icon-plate-export:before {
119 | content: "\e61b";
120 | }
121 |
122 | .icon-plate-title:before {
123 | content: "\e61c";
124 | }
125 |
126 | .icon-note-quoter:before {
127 | content: "\e61d";
128 | }
129 |
130 | .icon-note-subtitle:before {
131 | content: "\e61e";
132 | }
133 |
134 | .icon-layer-top:before {
135 | content: "\e612";
136 | }
137 |
138 | .icon-layer-bottom:before {
139 | content: "\e620";
140 | }
141 |
142 | .icon-close:before {
143 | content: "\e621";
144 | }
145 |
146 | .icon-setting:before {
147 | content: "\e622";
148 | }
149 |
150 | .icon-plate-number:before {
151 | content: "\e60d";
152 | }
153 |
154 | .icon-more:before {
155 | content: "\e623";
156 | }
157 |
158 | .icon-main-word:before {
159 | content: "\e624";
160 | }
161 |
162 | .icon-layer-blender:before {
163 | content: "\e625";
164 | }
165 |
166 | .icon-layer-looper:before {
167 | content: "\e626";
168 | }
169 |
170 | .icon-main-config:before {
171 | content: "\e627";
172 | }
173 |
174 |
--------------------------------------------------------------------------------
/public/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/public/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/public/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/public/iconfont/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/iconfont/iconfont.woff2
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/logo.png
--------------------------------------------------------------------------------
/public/minicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/canisminor1990/anto/0081b453586c555549c3ad844008b0864fc28463/public/minicon.png
--------------------------------------------------------------------------------
/scripts/.gitignore:
--------------------------------------------------------------------------------
1 | webhook.json
--------------------------------------------------------------------------------
/scripts/ding.js:
--------------------------------------------------------------------------------
1 | const fetch = require("node-fetch");
2 | const fs = require("fs");
3 | const _ = require("lodash");
4 | const webhook = require("./webhook.json");
5 |
6 | fetch("https://api.github.com/repos/canisminor1990/anto/releases/latest")
7 | .then(res => res.json())
8 | .then(json => {
9 | const { name, body, assets } = json;
10 | const title = `${name} 现已发布 🚀`;
11 | const cover = "https://raw.githubusercontent.com/canisminor1990/anto/master/docs/banner.png";
12 | const feedback = "- 意见反馈:@倏昱 (钉钉群: 21941480)";
13 | const postData = {
14 | actionCard: {
15 | title : title,
16 | text : ` \n\n ## ${title} \n\n ${body} \n\n ${feedback}`,
17 | btnOrientation: "0",
18 | btns : [
19 | {
20 | title : "查看更新说明",
21 | actionURL: "https://www.yuque.com/canisminor/anto/changelog"
22 | },
23 | {
24 | title : "下载插件",
25 | actionURL: assets[0].browser_download_url
26 | }
27 | ]
28 | },
29 | msgtype : "actionCard"
30 | };
31 |
32 | _.forEach(webhook, url => fetchPost(url, JSON.stringify(postData)));
33 | });
34 |
35 | function fetchPost(url, body) {
36 | const config = {
37 | method : "POST",
38 | headers: {
39 | "Content-Type": "application/json"
40 | },
41 | body : body
42 | };
43 | fetch(url, config)
44 | .then((response) => {
45 | console.log(url);
46 | });
47 | }
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/scripts/preview.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 |
4 | const rootDir = "preview/dist";
5 | const css = fs.readFileSync(path.join(rootDir, "index.css"), "utf-8");
6 | const html = fs.readFileSync(path.join(rootDir, "index.html"), "utf-8");
7 | const js = fs.readFileSync(path.join(rootDir, "index.js"), "utf-8");
8 |
9 | const Data = { css, html, js };
10 | fs.writeFileSync("src/preview.json", JSON.stringify(Data));
11 |
12 | console.log(JSON.stringify(Data))
--------------------------------------------------------------------------------
/src/api/create.js:
--------------------------------------------------------------------------------
1 | import sketch from 'sketch/dom';
2 | import _ from 'lodash';
3 | export default class SketchCreate {
4 | group(config) {
5 | return new sketch.Group(
6 | _.defaults(config, {
7 | frame: {
8 | x: -50000,
9 | y: -50000,
10 | width: 100000,
11 | height: 100000,
12 | },
13 | layers: [],
14 | })
15 | );
16 | }
17 |
18 | page(config) {
19 | return new sketch.Page(config);
20 | }
21 |
22 | artboard(config) {
23 | return new sketch.Artboard(config);
24 | }
25 |
26 | shape(config) {
27 | return new sketch.Shape(config);
28 | }
29 |
30 | image(config) {
31 | return new sketch.Image(config);
32 | }
33 |
34 | text(config) {
35 | return new sketch.Text(config);
36 | }
37 |
38 | symbolMaster(artboard) {
39 | return new sketch.SymbolMaster.fromArtboard(artboard);
40 | }
41 |
42 | instance(master) {
43 | return master.createNewInstance();
44 | }
45 |
46 | slice(config) {
47 | return new sketch.Slice(config);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/api/layer.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export default class SketchLayer {
4 | get(layer, name) {
5 | return _.filter(layer.layers, l => l.name === name);
6 | }
7 |
8 | getById(document, id) {
9 | return document.getLayerWithID(id);
10 | }
11 |
12 | deepGet(layer, name) {
13 | let layers = [];
14 | _.forEach(layer.layers, l => {
15 | if (l.name === name) layers.push(l);
16 | if (l.layers) layers.concat(this.deepGet(l.layers, name));
17 | });
18 | return layers;
19 | }
20 |
21 | globalGet(document, name) {
22 | try {
23 | return document.getLayersNamed(name);
24 | } catch (e) {
25 | return [];
26 | }
27 | }
28 |
29 | remove(layer, name) {
30 | _.forEach(layer.layers, l => {
31 | if (l.name === name) l.remove();
32 | });
33 | }
34 |
35 | deepRemove(layer, name) {
36 | _.forEach(layer.layers, l => {
37 | if (l.name === name) l.remove();
38 | if (l.layers) this.deepRemove(l.layers, name);
39 | });
40 | }
41 |
42 | globalRemove(document, name) {
43 | const layers = this.globalGet(document, name);
44 | if (layers.length > 0) _.forEach(layers, l => l.remove());
45 | }
46 |
47 | getArtboard(layer) {
48 | if (layer.parent.type === 'Artboard') return layer.parent;
49 | if (layer.parent.type === 'Page') return false;
50 | return this.getArtboard(layer.parent);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/api/library.js:
--------------------------------------------------------------------------------
1 | import sketch from 'sketch/dom';
2 | import UI from 'sketch/ui';
3 | import _ from 'lodash';
4 |
5 | export default class SketchLibrary {
6 | get all() {
7 | return sketch.Library.getLibraries();
8 | }
9 |
10 | get antoExport() {
11 | return this.get('Anto Export');
12 | }
13 |
14 | get ux() {
15 | return this.get('Anto UX');
16 | }
17 |
18 | get ui() {
19 | return this.get('Anto UI');
20 | }
21 |
22 | get(name) {
23 | const result = _.filter(this.all, lib => lib.name === name);
24 | if (result.length > 0) return result[0];
25 | UI.message(`😥 请检查「${name}」是否存在`);
26 | return false;
27 | }
28 | getSymbols(library) {
29 | return library.getImportableSymbolReferencesForDocument(sketch.getSelectedDocument());
30 | }
31 | getSymbol(library, name) {
32 | const librarySymbols = this.getSymbols(library);
33 | const result = _.filter(librarySymbols, s => s.name === name);
34 | return result.length > 0 ? result[0] : false;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/api/native.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import moment from 'moment';
3 | import { join } from 'path';
4 |
5 | export default class SketchNative {
6 | get context() {
7 | return NSDocumentController.sharedDocumentController();
8 | }
9 |
10 | get document() {
11 | return this.context.currentDocument();
12 | }
13 |
14 | get pages() {
15 | return this.document.pages();
16 | }
17 |
18 | get page() {
19 | return this.document.currentPage();
20 | }
21 |
22 | get selection() {
23 | return this.document.selectedLayers();
24 | }
25 |
26 | get firstSelectLayer() {
27 | return this.selection.layers()[0];
28 | }
29 |
30 | setName(layer, name) {
31 | layer.setName(name);
32 | }
33 |
34 | setLocked(layer, bool = true) {
35 | layer.setIsLocked(bool);
36 | }
37 |
38 | setVisible(layer, bool = true) {
39 | layer.setIsVisible(bool);
40 | }
41 |
42 | setX(layer, x) {
43 | layer.frame().setX(x);
44 | }
45 |
46 | setY(layer, y) {
47 | layer.frame().setY(y);
48 | }
49 |
50 | setWidth(layer, width) {
51 | layer.frame().setWidth(width);
52 | }
53 |
54 | setHeight(layer, height) {
55 | layer.frame().setHeight(height);
56 | }
57 |
58 | moveToFront(layer) {
59 | MSLayerMovement.moveToFront(_.isArray(layer) ? layer : [layer]);
60 | }
61 |
62 | remove(layer) {
63 | layer.parentGroup().removeLayer(layer);
64 | }
65 |
66 | addLayers(group, layer) {
67 | group.addLayers(_.isArray(layer) ? layer : [layer]);
68 | }
69 |
70 | setSeleted(layer) {
71 | layer.select_byExtendingSelection(true, true);
72 | }
73 |
74 | setSlice(layer, name, size = 1, type = 'x') {
75 | const sliceLayer = MSSliceLayer.new();
76 | this.setName(sliceLayer, name);
77 | this.setLocked(sliceLayer);
78 | this.setVisible(sliceLayer, false);
79 | this.setX(sliceLayer, layer.frame.x);
80 | this.setY(sliceLayer, layer.frame.y);
81 | this.setWidth(sliceLayer, layer.frame.width);
82 | this.setHeight(sliceLayer, layer.frame.height);
83 | this.setExport(sliceLayer, size, type);
84 | return sliceLayer;
85 | }
86 |
87 | setExport(layer, size = 1, type = 'x') {
88 | const Slice = layer.exportOptions().addExportFormat();
89 | Slice.setFileFormat('jpg');
90 | Slice.setNamingScheme(0);
91 | if (type === 'w') {
92 | Slice.setVisibleScaleType(1);
93 | Slice.setScale(0);
94 | Slice.setAbsoluteSize(parseInt(size, 10));
95 | } else if (type === 'w') {
96 | Slice.setVisibleScaleType(2);
97 | Slice.setScale(0);
98 | Slice.setAbsoluteSize(parseInt(size, 10));
99 | } else if (type === 'x') {
100 | Slice.setVisibleScaleType(0);
101 | Slice.setScale(parseInt(size, 10));
102 | Slice.setAbsoluteSize(0);
103 | }
104 | }
105 |
106 | exportSlice(slice, path, name) {
107 | this.document.saveArtboardOrSlice_toFile(slice, join(path, name + '.jpg'));
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/api/setting.js:
--------------------------------------------------------------------------------
1 | import Settings from 'sketch/settings';
2 |
3 | export default class SketchSetting {
4 | get(key) {
5 | return Settings.settingForKey(key);
6 | }
7 |
8 | set(key, value) {
9 | return Settings.setSettingForKey(key, value);
10 | }
11 |
12 | getLayer(layer, key) {
13 | return Settings.layerSettingForKey(layer, key);
14 | }
15 |
16 | setLayer(layer, key, value) {
17 | return Settings.setLayerSettingForKey(layer, key, value);
18 | }
19 |
20 | getDocument(document, key) {
21 | return Settings.documentSettingForKey(document, key);
22 | }
23 |
24 | setDocument(document, key, value) {
25 | return Settings.setDocumentSettingForKey(document, key, value);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/api/ui.js:
--------------------------------------------------------------------------------
1 | import UI from 'sketch/ui';
2 |
3 | export default class sketchUI {
4 | message(string) {
5 | console.log(string);
6 | UI.message(string);
7 | }
8 |
9 | success(string) {
10 | this.message(`🔵 ${string}`);
11 | }
12 | warn(string) {
13 | this.message(`😥 ${string}`);
14 | }
15 | error(string) {
16 | this.message(`🥵 ${string}`);
17 | }
18 | alert(title, string) {
19 | UI.alert(title, string);
20 | }
21 | inputPanel(title, defalutValue, func) {
22 | return UI.getInputFromUser(title, defalutValue, func);
23 | }
24 | selectPanel(title, options = []) {
25 | const selection = UI.getSelectionFromUser(title, options);
26 | const ok = selection[2];
27 | const value = options[selection[1]];
28 | return ok ? value : false;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import BrowserWindow from 'sketch-module-web-view';
3 | import _ from 'lodash';
4 | import Options from './options';
5 | import Router from './router';
6 | import Sk from './sketch';
7 |
8 | const isDev = process.env.NODE_ENV === 'development';
9 | const Sketch = new Sk();
10 | const Panel = isDev ? 'http://localhost:8000' : 'index.html';
11 |
12 | export const onRun = context => {
13 | Sketch.setting.set('url', String(context.plugin.url()));
14 |
15 | const browserWindow = new BrowserWindow(Options);
16 | browserWindow.setSize(Options.width, Options.height);
17 |
18 | // 加载完成后显示
19 | browserWindow.once('ready-to-show', () => {
20 | const mode = Sketch.setting.get('panel-mode');
21 | if (!mode) Sketch.setting.set('panel-mode', '交互');
22 | const Positon = Sketch.setting.get('panel-position');
23 | if (_.isArray(Positon)) browserWindow.setPosition(Positon[0], browserWindow.getPosition()[1]);
24 | browserWindow.webContents.executeJavaScript(
25 | `localStorage.setItem("version","${String(context.plugin.version())}")`
26 | );
27 | browserWindow.show();
28 | });
29 |
30 | // 保存移动位置
31 | browserWindow.on('move', () => {
32 | const Positon = browserWindow.getPosition();
33 | Sketch.setting.set('panel-position', [Positon[0], Positon[1]]);
34 | });
35 |
36 | // Webview On
37 | new Router(browserWindow).start();
38 |
39 | // 开始
40 | browserWindow.loadURL(Panel);
41 | };
42 |
43 | export default onRun;
44 |
--------------------------------------------------------------------------------
/src/library.js:
--------------------------------------------------------------------------------
1 | import sketch from 'sketch/dom';
2 | import _ from 'lodash';
3 |
4 | const Library = sketch.Library;
5 |
6 | export const addLibrary = context => {
7 | fetch('http://100.88.232.163/static/tabs.json')
8 | .then(res => res.json())
9 | .then(json => {
10 | let remoteLibrary = ['anto-export.xml'];
11 | let libName = ['Anto Export'];
12 | _.forEach(json, lib => {
13 | remoteLibrary.push(lib.filename + '.xml');
14 | libName.push(lib.libname);
15 | });
16 | _.forEach(remoteLibrary, fileName => {
17 | const url = `http://100.88.232.163/library/${fileName}`;
18 | Library.getRemoteLibraryWithRSS(url, err => {
19 | console.log(err);
20 | });
21 | });
22 | });
23 | };
24 |
25 | export const cleanLibrary = context => {
26 | fetch('http://100.88.232.163/static/tabs.json')
27 | .then(res => res.json())
28 | .then(json => {
29 | let remoteLibrary = ['anto-export.xml'];
30 | let libName = ['Anto Export'];
31 | _.forEach(json, lib => {
32 | remoteLibrary.push(lib.filename + '.xml');
33 | libName.push(lib.libname);
34 | });
35 |
36 | _.forEach(Library.getLibraries(), lib => {
37 | if (_.includes(libName, lib.name)) lib.remove();
38 | });
39 |
40 | _.forEach(remoteLibrary, fileName => {
41 | const url = `http://100.88.232.163/library/${fileName}`;
42 | Library.getRemoteLibraryWithRSS(url, err => {
43 | console.log(err);
44 | });
45 | });
46 | });
47 | };
48 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "Anto",
3 | "identifier" : "sketch.plugin.anto",
4 | "compatibleVersion": 3,
5 | "bundleVersion" : 1,
6 | "icon" : "icon.png",
7 | "commands" : [
8 | {
9 | "script" : "update.js",
10 | "handlers": {
11 | "actions": {
12 | "Startup" : "update"
13 | }
14 | }
15 | },
16 | {
17 | "script" : "library.js",
18 | "handlers": {
19 | "actions": {
20 | "Startup" : "cleanLibrary",
21 | "OpenDocument": "addLibrary",
22 | "Shutdown": "cleanLibrary"
23 | }
24 | }
25 | },
26 | {
27 | "name" : "🔵 Anto",
28 | "icon" : "minicon.png",
29 | "identifier": "AntoPanel",
30 | "script" : "index.js",
31 | "shortcut" : "cmd option a",
32 | "handlers" : {
33 | "actions": {
34 | "run" : "onRun",
35 | "OpenDocument": "onRun"
36 | }
37 | }
38 | }
39 | ],
40 | "menu" : {
41 | "title" : "Anto",
42 | "isRoot": true,
43 | "items" : [
44 | "AntoPanel"
45 | ]
46 | }
47 | }
--------------------------------------------------------------------------------
/src/models/devColor.js:
--------------------------------------------------------------------------------
1 | import Sketch from '../sketch';
2 | import { join } from 'path';
3 | import fs from '@skpm/fs';
4 | import _ from 'lodash';
5 | import { hexToRgba } from 'hex-and-rgba';
6 |
7 | const home = require('os').homedir();
8 | const buildPath = join(home, '.anto');
9 |
10 | export default class devColor extends Sketch {
11 | constructor() {
12 | super();
13 | this.namespace = '颜色|devColor';
14 | }
15 |
16 | run() {
17 | const Tree = {};
18 | const sortedArtboards = _.sortBy(this.page.layers, ['frame.y', 'frame.x']);
19 | _.forEach(sortedArtboards, artboard => {
20 | if (!Tree[artboard.name]) Tree[artboard.name] = [];
21 | const sortedLayers = _.sortBy(artboard.layers, ['frame.y', 'frame.x']);
22 | _.forEach(sortedLayers, l => {
23 | const color = l.style.fills[0].color;
24 | const rgba = hexToRgba(color);
25 | console.log(rgba);
26 | Tree[artboard.name].push({
27 | name: l.name,
28 | desc:
29 | rgba[3] === 1
30 | ? color.slice(0, 7)
31 | : `rgba(${rgba[0]},${rgba[1]},${rgba[2]},${rgba[3].toFixed(2)})`,
32 | color: color,
33 | });
34 | });
35 | });
36 | console.log(Tree);
37 | fs.writeFileSync(join(buildPath, 'colors.json'), JSON.stringify(Tree));
38 | this.openPath(buildPath);
39 | this.ui.success('生产完成');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/models/devNumber.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class devNumber extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '序号|devNumber';
8 | }
9 | run() {
10 | if (this.selection.isEmpty) return this.ui.warn('请选择图层');
11 | const sortedLayers = _.sortBy(this.selection.layers, ['frame.y', 'frame.x']);
12 | _.forEach(sortedLayers, (layer, index) => {
13 | layer.moveToBack();
14 | let name = String(layer.name).split('|');
15 | name.length > 1 ? (name = name[1]) : (name = layer.name);
16 | layer.name = [index, name].join('|');
17 | });
18 | this.sortOrder();
19 | this.ui.success('序号标注成功');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/models/devSymbol.js:
--------------------------------------------------------------------------------
1 | import Sketch from '../sketch';
2 | import { join } from 'path';
3 | import fs from '@skpm/fs';
4 | import _ from 'lodash';
5 |
6 | const home = require('os').homedir();
7 | const buildPath = join(home, '.anto');
8 |
9 | export default class devSymbol extends Sketch {
10 | constructor() {
11 | super();
12 | this.namespace = '生产|handleBuild';
13 | }
14 |
15 | run() {
16 | const Tree = {};
17 | const libBuildPath = join(buildPath, this.fileName);
18 | try {
19 | fs.unlinkSync(libBuildPath);
20 | } catch (e) {}
21 | console.log(libBuildPath);
22 | const sortedArtboards = _.sortBy(this.page.layers, ['frame.y', 'frame.x']);
23 | _.forEach(sortedArtboards, artboard => {
24 | const imgTree = [];
25 | const sortedLayers = _.sortBy(artboard.layers, ['frame.y', 'frame.x']);
26 | _.forEach(sortedLayers, l => {
27 | const data = l.name.split(' / ');
28 | if (!data.length || data.length < 3) return;
29 | if (!Tree[data[0]]) Tree[data[0]] = {};
30 | // imgTree.push(data);
31 | imgTree.push({
32 | name: data,
33 | id: l.id,
34 | });
35 | Tree[data[0]][data[1]] = imgTree;
36 |
37 | this.export(l, {
38 | output: join(libBuildPath, 'img'),
39 | 'use-id-for-name': true,
40 | });
41 | });
42 | });
43 | console.log(Tree);
44 | fs.writeFileSync(join(libBuildPath, 'data.json'), JSON.stringify(Tree));
45 | this.openPath(libBuildPath);
46 | this.ui.success('生产完成');
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/models/devTest.js:
--------------------------------------------------------------------------------
1 | import Sketch from '../sketch';
2 | import { join } from 'path';
3 | import fs from '@skpm/fs';
4 |
5 | const home = require('os').homedir();
6 | const buildPath = join(home, '.anto');
7 |
8 | export default class devTest extends Sketch {
9 | constructor() {
10 | super();
11 | this.namespace = 'Test|devTest';
12 | }
13 |
14 | run() {
15 | const layer = this.selection.layers[0];
16 | fs.writeFileSync(join(buildPath, 'test.json'), JSON.stringify(layer));
17 | this.openPath(buildPath);
18 | this.ui.success('生产完成');
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/models/handleBlender.js:
--------------------------------------------------------------------------------
1 | import Sketch from '../sketch';
2 |
3 | export default class handleBlender extends Sketch {
4 | constructor() {
5 | super();
6 | this.namespace = '图层混合|handleBlender';
7 | }
8 |
9 | getColor(selectionType, layer) {
10 | let color;
11 |
12 | if (selectionType === 'fill') {
13 | if (layer instanceof MSTextLayer) {
14 | color = layer.textColor();
15 | } else {
16 | color = layer
17 | .style()
18 | .fills()
19 | .firstObject()
20 | .color();
21 | }
22 | } else {
23 | if (layer instanceof MSTextLayer) {
24 | color = layer
25 | .style()
26 | .borders()
27 | .firstObject()
28 | .color();
29 | } else {
30 | color = layer
31 | .style()
32 | .borders()
33 | .firstObject()
34 | .color();
35 | }
36 | }
37 | return color;
38 | }
39 |
40 | getNum(num1, num2, index, count) {
41 | return ((num2 - num1) / (count - 1)) * index + num1;
42 | }
43 |
44 | handleStep(steps) {
45 | const selectedLayers = this.native.selection.layers();
46 | const selectedCount = selectedLayers.length;
47 |
48 | if (selectedCount !== 0) {
49 | const layerColor = new Array(selectedCount);
50 | const layerBorderColor = new Array(selectedCount);
51 | const layerPosX = new Array(selectedCount);
52 | const layerPosY = new Array(selectedCount);
53 | const layerW = new Array(selectedCount);
54 | const layerH = new Array(selectedCount);
55 | const layerRadius = new Array(selectedCount);
56 | const layerOpacity = new Array(selectedCount);
57 | const layerRotation = new Array(selectedCount);
58 |
59 | // for font
60 | const layerFontSize = new Array(selectedCount);
61 | const layerCharacterSpacing = new Array(selectedCount);
62 | const layerLineheight = new Array(selectedCount);
63 |
64 | const copiedLayers = new Array(selectedCount - 1);
65 |
66 | for (let id = 0; id < selectedCount; id++) {
67 | // init array
68 | layerColor[id] = this.getColor('fill', selectedLayers[id]);
69 |
70 | layerPosX[id] = selectedLayers[id].rect().origin.x;
71 | layerPosY[id] = selectedLayers[id].rect().origin.y;
72 | layerW[id] = selectedLayers[id].rect().size.width;
73 | layerH[id] = selectedLayers[id].rect().size.height;
74 | layerOpacity[id] = selectedLayers[id]
75 | .style()
76 | .contextSettings()
77 | .opacity();
78 | layerRotation[id] = selectedLayers[id].rotation();
79 |
80 | // for font
81 | if (selectedLayers[id].className() === 'MSTextLayer') {
82 | layerFontSize[id] = selectedLayers[id].fontSize();
83 | layerCharacterSpacing[id] = selectedLayers[id].characterSpacing();
84 | layerLineheight[id] = selectedLayers[id].lineHeight();
85 | } else {
86 | // for rect radius
87 | // for borders
88 | layerBorderColor[id] = this.getColor('border', selectedLayers[id]);
89 | const shape = selectedLayers[id];
90 | if (shape && shape.isKindOfClass(MSRectangleShape)) {
91 | layerRadius[id] = selectedLayers[id].cornerRadiusFloat();
92 | }
93 | }
94 | }
95 |
96 | // duplicate layers
97 | for (let id = 0; id < selectedCount - 1; id++) {
98 | copiedLayers[id] = [];
99 | copiedLayers[id].push(selectedLayers[id]);
100 | copiedLayers[id].push(selectedLayers[id + 1]);
101 | let layerRec = selectedLayers[id];
102 |
103 | for (let k = 0; k < steps; k++) {
104 | const newLayer = layerRec.duplicate();
105 | newLayer.select_byExpandingSelection(true, true);
106 | newLayer.setName(k.toString());
107 | copiedLayers[id].splice(k + 1, 0, newLayer);
108 | layerRec = newLayer;
109 | }
110 | }
111 |
112 | for (let id = 0; id < selectedCount - 1; id++) {
113 | // change their styles
114 | for (let i = 0; i < steps + 2; i++) {
115 | const layer = copiedLayers[id][i];
116 |
117 | // color
118 | const r = Math.round(
119 | this.getNum(layerColor[id].red(), layerColor[id + 1].red(), i, steps + 2) * 255
120 | );
121 | const g = Math.round(
122 | this.getNum(layerColor[id].green(), layerColor[id + 1].green(), i, steps + 2) * 255
123 | );
124 | const b = Math.round(
125 | this.getNum(layerColor[id].blue(), layerColor[id + 1].blue(), i, steps + 2) * 255
126 | );
127 | const a = Math.round(
128 | this.getNum(layerColor[id].alpha(), layerColor[id + 1].alpha(), i, steps + 2) * 255
129 | );
130 |
131 | // position
132 | const x = Math.round(this.getNum(layerPosX[id], layerPosX[id + 1], i, steps + 2));
133 | const y = Math.round(this.getNum(layerPosY[id], layerPosY[id + 1], i, steps + 2));
134 |
135 | // width & height
136 | const w = Math.round(this.getNum(layerW[id], layerW[id + 1], i, steps + 2));
137 | const h = Math.round(this.getNum(layerH[id], layerH[id + 1], i, steps + 2));
138 |
139 | // opacity
140 | const op = this.getNum(layerOpacity[id], layerOpacity[id + 1], i, steps + 2);
141 |
142 | // rotation
143 | const rot = this.getNum(layerRotation[id], layerRotation[id + 1], i, steps + 2);
144 | layer.setRotation(rot);
145 |
146 | // set position and width height
147 | layer.rect = NSMakeRect(x, y, w, h);
148 | layer
149 | .style()
150 | .contextSettings()
151 | .setOpacity(op);
152 |
153 | if (selectedLayers[id].className() === 'MSTextLayer') {
154 | const fs = this.getNum(layerFontSize[id], layerFontSize[id + 1], i, steps + 2);
155 | const cs = this.getNum(
156 | layerCharacterSpacing[id],
157 | layerCharacterSpacing[id + 1],
158 | i,
159 | steps + 2
160 | );
161 | const lh = this.getNum(layerLineheight[id], layerLineheight[id + 1], i, steps + 2);
162 |
163 | layer.setFontSize(fs);
164 | layer.setCharacterSpacing(cs);
165 | layer.setLineHeight(lh);
166 | layer.textColor = MSColor.colorWithRed_green_blue_alpha(
167 | r / 255,
168 | g / 255,
169 | b / 255,
170 | a / 255
171 | );
172 | } else {
173 | const fill = layer
174 | .style()
175 | .fills()
176 | .firstObject();
177 | const br = Math.round(
178 | this.getNum(
179 | layerBorderColor[id].red(),
180 | layerBorderColor[id + 1].red(),
181 | i,
182 | steps + 2
183 | ) * 255
184 | );
185 | const bg = Math.round(
186 | this.getNum(
187 | layerBorderColor[id].green(),
188 | layerBorderColor[id + 1].green(),
189 | i,
190 | steps + 2
191 | ) * 255
192 | );
193 | const bb = Math.round(
194 | this.getNum(
195 | layerBorderColor[id].blue(),
196 | layerBorderColor[id + 1].blue(),
197 | i,
198 | steps + 2
199 | ) * 255
200 | );
201 | const ba = Math.round(
202 | this.getNum(
203 | layerBorderColor[id].alpha(),
204 | layerBorderColor[id + 1].alpha(),
205 | i,
206 | steps + 2
207 | ) * 255
208 | );
209 |
210 | const border = layer
211 | .style()
212 | .borders()
213 | .firstObject();
214 | border.color = MSColor.colorWithRed_green_blue_alpha(
215 | br / 255,
216 | bg / 255,
217 | bb / 255,
218 | ba / 255
219 | );
220 |
221 | const shape = selectedLayers[id];
222 | fill.color = MSColor.colorWithRed_green_blue_alpha(r / 255, g / 255, b / 255, a / 255);
223 | if (shape && shape.isKindOfClass(MSRectangleShape)) {
224 | layer.cornerRadiusFloat = Math.round(
225 | this.getNum(parseInt(layerRadius[id]), parseInt(layerRadius[id + 1]), i, steps + 2)
226 | );
227 | }
228 | }
229 | }
230 | }
231 | }
232 | }
233 |
234 | run() {
235 | if (this.selection.length < 2) return this.ui.warn('请选择两个以上图层');
236 | this.ui.inputPanel('请输入歩进数', { initialValue: 10 }, (err, value) => {
237 | if (err) return;
238 | if (parseInt(value) < 1) return this.ui.warn('请输入大于零的数字');
239 | this.handleStep(parseInt(value));
240 | this.ui.success(`图层混合完毕`);
241 | });
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/models/handleBuildLocalSymbol.js:
--------------------------------------------------------------------------------
1 | import fs from '@skpm/fs';
2 | import _ from 'lodash';
3 | import Sketch from '../sketch';
4 | import { join } from 'path';
5 |
6 | export default class handleBuildLocalSymbol extends Sketch {
7 | constructor() {
8 | super();
9 | this.namespace = '本地组件预览|handleBuildLocalSymbol';
10 | }
11 |
12 | build() {
13 | console.log('[start]', this.namespace);
14 | const data = [];
15 | const home = require('os').homedir();
16 | const path = join(home, 'localSymbols');
17 | try {
18 | fs.unlinkSync(path);
19 | } catch (e) {}
20 | _.forEach(this.symbols, symbol => {
21 | this.export(symbol, {
22 | overwriting: true,
23 | 'use-id-for-name': true,
24 | output: path,
25 | scales: '0.5',
26 | formats: 'jpg',
27 | compression: 0.5,
28 | });
29 | });
30 |
31 | _.forEach(this.symbols, symbol => {
32 | let img = fs.readFileSync(join(path, `${symbol.id}@0.5x.jpg`));
33 | img = new Buffer(img).toString('base64');
34 | const base64 = 'data:jpg;base64,' + img;
35 | data.push({
36 | name: symbol.name,
37 | id: symbol.id,
38 | path: base64,
39 | });
40 | });
41 | console.log('[end]', this.namespace);
42 | this.ui.success('本地组件刷新成功');
43 | return {
44 | time: String(new Date()),
45 | data: data,
46 | };
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/models/handleChange.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleChange extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '变向|handleChange';
8 | }
9 |
10 | run() {
11 | if (this.selection.isEmpty) return this.ui.warn('请选择线条');
12 | _.forEach(this.selection.layers, l => {
13 | if (l.type !== 'ShapePath') return;
14 | const Start = l.style.borderOptions.startArrowhead;
15 | const End = l.style.borderOptions.endArrowhead;
16 | l.style.borderOptions.startArrowhead = End;
17 | l.style.borderOptions.endArrowhead = Start;
18 | });
19 | this.ui.success('变向成功');
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/models/handleColor.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleColor extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '色板|handleColor';
8 | }
9 |
10 | shapeBorder(layer, fill) {
11 | const oldStyle = _.assign(layer.style.borders[0]);
12 | layer.style.borders = [_.assign(oldStyle, fill)];
13 | }
14 |
15 | shapeFill(layer, fill) {
16 | layer.style.fills = [fill];
17 | }
18 |
19 | textColor(layer, fill) {
20 | layer.style.textColor = fill.color;
21 | layer.style.fills = [];
22 | }
23 |
24 | run(e) {
25 | if (this.selection.isEmpty) return this.ui.warn('请选择图形');
26 | const { border, color } = JSON.parse(e);
27 | const fill = {
28 | fillType: 'Color',
29 | color: color,
30 | };
31 | _.forEach(this.selection.layers, layer => {
32 | if (!layer.type) return;
33 | if (layer.type === 'ShapePath') {
34 | border ? this.shapeBorder(layer, fill) : this.shapeFill(layer, fill);
35 | }
36 | if (layer.type === 'Text') {
37 | this.textColor(layer, fill);
38 | }
39 | });
40 |
41 | this.ui.success(`${border ? '描边' : '填充'}「${fill.color}」`);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/models/handleDash.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleDash extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '虚实|handleDash';
8 | }
9 |
10 | run() {
11 | if (this.selection.isEmpty) return this.ui.warn('请选择线条');
12 | _.forEach(this.selection.layers, l => {
13 | if (l.type !== 'ShapePath') return;
14 | const dash = l.style.borderOptions.dashPattern;
15 | if (!dash || dash.length === 0 || dash.toString() === [0, 0, 0, 0].toString()) {
16 | l.style.borderOptions.dashPattern = [4, 6, 4, 6];
17 | this.ui.success('变成虚线');
18 | } else {
19 | l.style.borderOptions.dashPattern = [0, 0, 0, 0];
20 | this.ui.success('变成实线');
21 | }
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/models/handleExport.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 | import moment from 'moment';
4 | import { join } from 'path';
5 | import preivew from '../preview.json';
6 | import fs from '@skpm/fs';
7 | import dialog from '@skpm/dialog';
8 | import jsZip from 'jszip';
9 |
10 | export default class handleExport extends Sketch {
11 | constructor() {
12 | super();
13 | this.namespace = '导出|handleExport';
14 | this.height = 400;
15 | }
16 |
17 | run() {
18 | const zip = new jsZip();
19 | const Data = {
20 | date: moment().format('YYYY-MM-DD'),
21 | author: this.setting.get('config-name'),
22 | pages: [],
23 | };
24 |
25 | // 获取路径
26 | const RootPath = dialog.showSaveDialog({
27 | title: 'Export Zip to',
28 | defaultPath: [this.fileName, moment().format('MMDD')].join(' '),
29 | });
30 |
31 | if (_.isUndefined(RootPath)) return;
32 |
33 | _.forEach(this.native.pages, page => {
34 | if (String(page.name())[0] === '@') return;
35 | _.forEach(page.layers(), layer => {
36 | if (String(layer.name()) === '@制版') {
37 | const sliceLayer = _.filter(layer.layers(), l => String(l.class()) === 'MSSliceLayer')[0];
38 | const name = String(sliceLayer.name());
39 | const info = name.split(' (')[1].split(') ');
40 | const path = join('preview', name + '.jpg');
41 | this.native.setY(sliceLayer, sliceLayer.frame().y() + this.height);
42 | this.native.setHeight(sliceLayer, sliceLayer.frame().height() - this.height);
43 |
44 | // 设置封面
45 | const symbol = _.filter(layer.layers(), l => String(l.class()) === 'MSSymbolInstance')[0];
46 | const symbolName = name + '-cover';
47 | const symbolSlice = this.native.setSlice(symbol, symbolName, '200', 'w');
48 | this.native.addLayers(layer, symbolSlice);
49 | this.native.setX(symbolSlice, sliceLayer.frame().x());
50 | this.native.setY(symbolSlice, sliceLayer.frame().y());
51 | this.native.setWidth(symbolSlice, sliceLayer.frame().width());
52 | this.native.setHeight(symbolSlice, sliceLayer.frame().height());
53 |
54 | // 导出切图
55 | this.native.exportSlice(sliceLayer, join(RootPath, 'preview'), name);
56 | this.native.setY(sliceLayer, sliceLayer.frame().y() - this.height);
57 | this.native.setHeight(sliceLayer, sliceLayer.frame().height() + this.height);
58 |
59 | // 导出封面
60 | this.native.exportSlice(symbolSlice, join(RootPath, 'preview'), symbolName);
61 | const coverPath = join('preview', symbolName + '.jpg');
62 | this.native.remove(symbolSlice);
63 |
64 | // 添加压缩包
65 | zip.file(path, fs.readFileSync(join(RootPath, path)));
66 | zip.file(coverPath, fs.readFileSync(join(RootPath, coverPath)));
67 |
68 | // 添加数据
69 | Data.pages.push({
70 | path: path,
71 | cover: coverPath,
72 | name: String(page.name()),
73 | mode: info[0],
74 | date: info[1],
75 | width: sliceLayer.frame().width(),
76 | height: sliceLayer.frame().height() - this.height,
77 | });
78 | }
79 | });
80 | });
81 |
82 | // 导出文件添加压缩包
83 | zip.file('data.js', ` localStorage.setItem('preview', '${JSON.stringify(Data)}');`);
84 | zip.file('index.css', preivew.css);
85 | zip.file('index.html', preivew.html);
86 | zip.file('index.js', preivew.js);
87 |
88 | // 删除缓存图片
89 | fs.unlinkSync(RootPath);
90 |
91 | // 创建压缩包
92 | this.ui.message(`⏲ 开始生产压缩文件,请稍后...`);
93 | zip.generateAsync({ type: 'base64' }).then(data => {
94 | const file = Buffer.from(data, 'base64');
95 | const filePath = RootPath + '.zip';
96 | fs.writeFileSync(filePath, file);
97 | this.ui.alert('✅ 导出成功', `导出至:${filePath}`);
98 | this.openPath(join(filePath, '..'));
99 | });
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/models/handleFrontBack.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleFrontBack extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '大置顶大置底|handleFrontBack';
8 | }
9 |
10 | run(name) {
11 | const GroupName = `@${name}`;
12 | const Groups = this.layer.get(this.page, GroupName);
13 | const Group =
14 | Groups[0] ||
15 | this.create.group({
16 | name: GroupName,
17 | parent: this.page,
18 | });
19 |
20 | const layers = _.filter(
21 | this.selection.layers,
22 | l => l.type && l.type !== 'Page' && l.type !== 'Artboard'
23 | );
24 | if (layers.length === 0) return this.ui.warn(`请选择需要${name}的图层`);
25 | this.setGroup(Group, layers);
26 | Group.locked = true;
27 | this.sortOrder();
28 | this.ui.success(`所选图层已${name}`);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/models/handleFrontBackLite.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleFrontBackLite extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '置顶置底|handleFrontBackLite';
8 | }
9 |
10 | run(name) {
11 | if (this.selection.isEmpty) return this.ui.warn('请选择图形');
12 |
13 | _.forEach(this.selection.layers, layer => {
14 | if (name === '置顶') {
15 | if (this.isTop(layer) && !this.needBreak(layer)) this.changeParent(layer);
16 | layer.moveToFront();
17 | } else {
18 | if (this.isBottom(layer) && !this.needBreak(layer)) this.changeParent(layer);
19 | layer.moveToBack();
20 | }
21 | });
22 |
23 | this.sortOrder();
24 | this.ui.success(`所选图层已${name}`);
25 | }
26 |
27 | needBreak(layer) {
28 | return layer.parent.type === 'Page' || layer.parent.type === 'Artboard';
29 | }
30 |
31 | changeParent(layer) {
32 | this.changeBasis(layer, { from: layer.parent, to: layer.parent.parent });
33 | layer.parent = layer.parent.parent;
34 | }
35 |
36 | isTop(layer) {
37 | const Parent = layer.parent;
38 | return layer.id === Parent.layers[Parent.layers.length - 1].id;
39 | }
40 |
41 | isBottom(layer) {
42 | const Parent = layer.parent;
43 | return layer.id === Parent.layers[0].id;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/models/handleHeight.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleHeight extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '调高|handleHeight';
8 | }
9 |
10 | run() {
11 | if (this.selection.isEmpty) return this.ui.warn('请选择画板或图层');
12 | if (this.selection.layers.length > 1) return this.ui.warn('请选择一个画板或图层');
13 | // 找画板
14 | let artboard;
15 | if (this.selection.layers[0].type !== 'Artboard') {
16 | artboard = this.layer.getArtboard(this.selection.layers[0]);
17 | if (!artboard) return this.ui.warn('请选择一个画板或图层');
18 | } else {
19 | artboard = this.selection.layers[0];
20 | }
21 | // 计算高度
22 | let maxHeight = 0;
23 | _.forEach(artboard.layers, l => {
24 | const height = l.frame.y + l.frame.height;
25 | if (height > maxHeight) maxHeight = height;
26 | });
27 | artboard.frame.height = maxHeight;
28 |
29 | this.ui.success('高度已适配');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/models/handleIgnore.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleIgnore extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '排除|handleIgnore';
8 | }
9 |
10 | run() {
11 | if (this.selection.isEmpty) return this.ui.warn('请选择图形');
12 |
13 | const GlobalStatus = this.isIgnore(this.selection.layers[0]);
14 |
15 | _.forEach(this.selection.layers, layer => {
16 | if (!layer.name) return;
17 | GlobalStatus ? this.setActive(layer) : this.setIgnore(layer);
18 | });
19 |
20 | this.ui.success(GlobalStatus ? '「激活」成功' : '「排除」成功');
21 | }
22 |
23 | isIgnore(layer) {
24 | return layer.name ? layer.name[0] === '@' : false;
25 | }
26 |
27 | setActive(layer) {
28 | layer.name = layer.name.replace(/^@/g, '');
29 | if (this.isIgnore(layer)) this.setActive(layer);
30 | }
31 |
32 | setIgnore(layer) {
33 | this.setActive(layer);
34 | layer.name = '@' + layer.name;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/models/handleLayout.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleLayout extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '对齐|handleLayout';
8 | this.option = {
9 | marginX: 100,
10 | marginY: 300,
11 | };
12 | }
13 |
14 | run() {
15 | const sortedArtboards = _.sortBy(this.artboards, ['frame.y', 'frame.x']);
16 |
17 | const snapDistance =
18 | sortedArtboards.reduce((initial, artboard) => {
19 | initial += artboard.frame.height;
20 | return initial;
21 | }, 0) / sortedArtboards.length;
22 |
23 | sortedArtboards.forEach(artboard => {
24 | artboard.frame.y = this.snapValueToGrid(artboard.frame.y, snapDistance);
25 | });
26 |
27 | const artboardRows = sortedArtboards.reduce((initial, artboard) => {
28 | initial.push(artboard.frame.y);
29 | return initial;
30 | }, []);
31 |
32 | const baseFrame = sortedArtboards[0].frame;
33 | let artboardY = baseFrame.y;
34 | let currentRow = 0;
35 |
36 | _.uniq(artboardRows).forEach(rowValue => {
37 | let tallestArtboard = 0;
38 | let artboardX = baseFrame.x;
39 |
40 | const artboardsInRow = sortedArtboards.filter(artboard => artboard.frame.y === rowValue);
41 | _.sortBy(artboardsInRow, 'frame.x').forEach(artboard => {
42 | artboard.frame.x = artboardX;
43 | artboard.frame.y = artboardY;
44 | artboardX += artboard.frame.width + this.option.marginX;
45 | if (artboard.frame.height > tallestArtboard) tallestArtboard = artboard.frame.height;
46 | });
47 |
48 | artboardY += tallestArtboard + this.option.marginY;
49 |
50 | currentRow++;
51 | });
52 |
53 | this.sortOrder();
54 | this.ui.success('对齐成功');
55 | }
56 |
57 | snapValueToGrid(value, grid) {
58 | let div = value / grid;
59 | const rest = div - Math.floor(div);
60 | if (rest > 0.8) {
61 | div += 1;
62 | }
63 | return Math.floor(Math.floor(div) * grid);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/models/handleLine.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | const GroupName = '@交互连线';
5 |
6 | export default class handleLine extends Sketch {
7 | constructor() {
8 | super();
9 | this.namespace = '变向|handleChange';
10 | this.lineStyle = {
11 | borders: [
12 | {
13 | color: '#2A72FF',
14 | thickness: 3,
15 | },
16 | ],
17 | borderOptions: {
18 | endArrowhead: 'FilledArrow',
19 | lineJoin: 'Round',
20 | },
21 | };
22 | }
23 |
24 | run() {
25 | if (this.selection.isEmpty) return this.ui.warn('请选择图层');
26 | if (this.selection.length > 2) return this.ui.warn('不能大于两个图层');
27 |
28 | // 上色
29 | if (this.selection.length === 1) {
30 | const layer = this.selection.layers[0];
31 | if (layer.type !== 'ShapePath') return this.ui.warn('单图层时只可选取连线');
32 | layer.style = this.lineStyle;
33 | return;
34 | }
35 |
36 | // 清除缓存线
37 | const aId = this.selection.layers[0].id;
38 | const bId = this.selection.layers[1].id;
39 | const newName = [this.selection.layers[0].name, this.selection.layers[1].name].join('-');
40 | const oldLine = this.getLine(aId, bId);
41 | if (oldLine) {
42 | this.lineStyle = oldLine.style;
43 | oldLine.remove();
44 | }
45 |
46 | // 连线
47 | const path = [];
48 | _.forEach(this.selection.layers, layer => {
49 | const rect = layer.frame;
50 | const newRect = rect.changeBasis({ from: layer.parent });
51 | const data = {
52 | x: newRect.x,
53 | y: newRect.y,
54 | width: rect.width,
55 | height: rect.height,
56 | };
57 | data.center = {
58 | x: data.x + data.width / 2,
59 | y: data.y + data.height / 2,
60 | };
61 | path.push(data);
62 | });
63 |
64 | // 画线
65 | const linePath = NSBezierPath.bezierPath();
66 | const newPath = this.handlePath(path);
67 | for (let i = 0; i < newPath.length; i++) {
68 | i === 0
69 | ? linePath.moveToPoint(NSMakePoint(newPath[i].x, newPath[i].y))
70 | : linePath.lineToPoint(NSMakePoint(newPath[i].x, newPath[i].y));
71 | }
72 |
73 | // 原生线条
74 | const lineSh = MSShapePathLayer.layerWithPath(MSPath.pathWithBezierPath(linePath));
75 | const tempName = '@tempLine' + Math.random();
76 | lineSh.setName(tempName);
77 | this.native.page.addLayers([lineSh]);
78 |
79 | this.selectionClear();
80 |
81 | const Groups = this.layer.get(this.page, GroupName);
82 | const Group = Groups[0] || this.create.group({ name: GroupName });
83 | Group.parent = this.page;
84 | Group.locked = true;
85 |
86 | const Line = this.layer.globalGet(this.document, tempName)[0];
87 | Line.selected = true;
88 | Line.name = newName;
89 | Line.style = this.lineStyle;
90 | this.changeBasis(Line, { from: this.page, to: Group });
91 | Line.parent = Group;
92 | this.setLine(aId, bId, Line.id);
93 |
94 | this.sortOrder();
95 |
96 | this.ui.success('连线成功');
97 | }
98 |
99 | handlePath(path) {
100 | const p1 = path[0];
101 | const p2 = path[1];
102 | const pointS = {};
103 | const pointE = {};
104 | let point2;
105 | let point3;
106 |
107 | if (Math.abs(p1.center.x - p2.center.x) > (p1.width + p2.width) / 2 && p1.x !== p2.x) {
108 | if (p1.x < p2.x) {
109 | pointS.x = p1.x + p1.width;
110 | pointE.x = p2.x;
111 | } else {
112 | pointS.x = p1.x;
113 | pointE.x = p2.x + p2.width;
114 | }
115 | pointS.y = p1.center.y;
116 | pointE.y = p2.center.y;
117 | point2 = {
118 | x: pointS.x - (pointS.x - pointE.x) / 2,
119 | y: pointS.y,
120 | };
121 | point3 = {
122 | x: pointS.x - (pointS.x - pointE.x) / 2,
123 | y: pointE.y,
124 | };
125 | } else {
126 | if (p1.y < p2.y) {
127 | pointS.y = p1.y + p1.height;
128 | pointE.y = p2.y;
129 | } else {
130 | pointS.y = p1.y;
131 | pointE.y = p2.y + p2.height;
132 | }
133 | pointS.x = p1.center.x;
134 | pointE.x = p2.center.x;
135 | point2 = {
136 | x: pointS.x,
137 | y: pointS.y - (pointS.y - pointE.y) / 2,
138 | };
139 | point3 = {
140 | x: pointE.x,
141 | y: pointS.y - (pointS.y - pointE.y) / 2,
142 | };
143 | }
144 |
145 | return [pointS, point2, point3, pointE];
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/models/handleLocalSymbol.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleLocalSymbol extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '本地组件|handleLocalSymbol';
8 | }
9 |
10 | run(id) {
11 | const master = this.layer.getById(this.document, id);
12 | const instance = master.createNewInstance();
13 |
14 | // 定位拖入位置
15 | let image = _.filter(this.layer.globalGet(this.document, id), l => l.type === 'Image')[0];
16 | if (!image) {
17 | image = _.filter(this.selection.layers, l => l.type === 'Image')[0];
18 | }
19 |
20 | instance.parent = image.parent;
21 | instance.frame.x = image.frame.x;
22 | instance.frame.y = image.frame.y;
23 | image.remove();
24 |
25 | this.selectionClear();
26 | this.selectionSet(instance);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/models/handleNote.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 | import moment from 'moment';
4 |
5 | export default class handleNote extends Sketch {
6 | constructor() {
7 | super();
8 | this.namespace = '标注|handleNote';
9 | this.option = {
10 | header: {
11 | name: `${this.mode} / 标题-链路`,
12 | },
13 | subheader: {
14 | name: `${this.mode} / 标题-小标题`,
15 | },
16 | block: {
17 | name: `${this.mode} / 注释-块`,
18 | },
19 | list: {
20 | name: `${this.mode} / 注释-列`,
21 | },
22 | ul: {
23 | name: `${this.mode} / 描述-列表`,
24 | },
25 | point: {
26 | name: `交互 / 节点-矩形`,
27 | },
28 | round: {
29 | name: `交互 / 节点-胶囊`,
30 | },
31 | if: {
32 | name: `交互 / 节点-判断`,
33 | },
34 | changelog: {
35 | name: `${this.mode} / 描述-变更记录`,
36 | replace: '日期',
37 | },
38 | };
39 | }
40 |
41 | run(type) {
42 | const isText = type === 'text';
43 | if (this.selection.isEmpty) return this.ui.warn('请选择文本');
44 | const texts = _.filter(
45 | this.selection.layers,
46 | l => l.type === 'Text' || l.type === 'SymbolInstance'
47 | );
48 | if (texts.length === 0) return this.ui.warn('请选择文本');
49 |
50 | // 找到Symbol
51 | let master;
52 | if (!isText) {
53 | master = this.library.getSymbol(this.library.antoExport, this.option[type].name);
54 | if (!master) return this.ui.warn('请检查Symbol是否存在');
55 | }
56 |
57 | this.selectionClear();
58 |
59 | _.forEach(texts, text => {
60 | if (!text.type) return this.ui.warn('请选择文本');
61 | if (isText) {
62 | if (text.type === 'Text') {
63 | this.setStyle(text);
64 | } else {
65 | const newText = this.create.text({
66 | name: text.name,
67 | frame: text.frame,
68 | parent: text.parent,
69 | });
70 | this.setStyle(newText);
71 | try {
72 | const value = _.filter(
73 | text.overrides,
74 | o => !o.isDefault && o.property === 'stringValue'
75 | )[0].value;
76 | newText.text = value;
77 | text.remove();
78 | } catch (e) {}
79 | }
80 | } else {
81 | const symbolMaster = master.import();
82 | const instance = symbolMaster.createNewInstance();
83 | instance.name = text.name;
84 | instance.parent = text.parent;
85 | instance.frame.x = text.frame.x;
86 | instance.frame.y = text.frame.y;
87 | instance.selected = true;
88 |
89 | // 设置override
90 | if (text.type === 'Text') {
91 | this.setByValue(instance, '文字', text.text);
92 | } else {
93 | _.forEach(text.overrides, o => {
94 | if (!o.isDefault && o.property === 'stringValue')
95 | this.setByValue(instance, '文字', o.value);
96 | });
97 | }
98 | if (type === 'changelog')
99 | this.setByValue(instance, this.option.changelog.replace, moment().format('MMDD'));
100 | text.remove();
101 | }
102 | });
103 |
104 | this.ui.success('标注成功');
105 | }
106 |
107 | setStyle(layer) {
108 | layer.systemFontSize = 32;
109 | layer.frame.width = 750;
110 | layer.alignment = 'justify';
111 | layer.selected = true;
112 | layer.style = {
113 | opacity: 1,
114 | borders: [],
115 | shadows: [],
116 | fills: [
117 | {
118 | color: this.mode === '交互' ? '#333' : '#ffffff',
119 | fill: 'Color',
120 | },
121 | ],
122 | };
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/models/handlePlate.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 | import moment from 'moment';
4 |
5 | const GroupName = '@制版';
6 | const SubGroupName = '@画板投影';
7 |
8 | export default class handlePlate extends Sketch {
9 | constructor() {
10 | super();
11 | this.namespace = '制版|handlePlate';
12 | this.padding = 400;
13 | }
14 |
15 | run() {
16 | const isInteractiveMode = this.mode === '交互';
17 | // 找到Symbol
18 | const master = this.library.getSymbol(this.library.antoExport, `${this.mode} / 画板`);
19 | if (!master) return this.ui.warn('请检查Symbol是否存在');
20 | const symbolMaster = master.import();
21 |
22 | // 清旧图层组
23 | this.layer.deepRemove(this.page, GroupName);
24 | this.layer.deepRemove(this.page, SubGroupName);
25 |
26 | // 框选画板>1个时只制版框选部分
27 | let Layers;
28 | if (_.filter(this.selection.layers, l => l.type && l.type === 'Artboard').length > 1) {
29 | Layers = this.selection;
30 | } else {
31 | Layers = _.filter(this.page.layers, layer => layer.name[0] !== '@');
32 | }
33 | if (Layers.length === 0) return this.ui.warn('找不到可用画板');
34 |
35 | // 遍历
36 | let x = Infinity;
37 | let y = Infinity;
38 | let x2 = -Infinity;
39 | let y2 = -Infinity;
40 |
41 | let ShadowGroup;
42 | if (isInteractiveMode)
43 | ShadowGroup = this.create.group({
44 | name: SubGroupName,
45 | parent: this.page,
46 | locked: true,
47 | });
48 |
49 | Layers.forEach(l => {
50 | const rect = l.frame;
51 | if (rect.x < x) x = rect.x;
52 | if (rect.y < y) y = rect.y;
53 | if (rect.x + rect.width > x2) x2 = rect.x + rect.width;
54 | if (rect.y + rect.height > y2) y2 = rect.y + rect.height;
55 | // 画投影
56 | if (isInteractiveMode && l.type === 'Artboard') {
57 | const Shadow = this.create.shape({
58 | name: l.name,
59 | frame: l.frame,
60 | style: {
61 | fills: [
62 | {
63 | color: '#ffffff',
64 | fill: 'Color',
65 | },
66 | ],
67 | borders: [],
68 | shadows: [
69 | {
70 | color: '#00000022',
71 | y: 40,
72 | blur: 100,
73 | spread: -20,
74 | },
75 | ],
76 | },
77 | });
78 | this.changeBasis(Shadow, { from: this.page, to: ShadowGroup });
79 | Shadow.parent = ShadowGroup;
80 | }
81 | });
82 |
83 | // 制版
84 | const instance = symbolMaster.createNewInstance();
85 | instance.frame.x = x - this.padding;
86 | instance.frame.y = y - this.padding * 2.1;
87 | instance.frame.width = x2 - x + 2 * this.padding;
88 | instance.frame.height = y2 - y + 3.7 * this.padding;
89 | instance.parent = this.page;
90 | instance.locked = true;
91 |
92 | // 设置override-title
93 | const author = this.setting.get('config-name');
94 | if (author && author.length > 0) this.setByValue(instance, '花名', author);
95 | const title = this.page.name + ` (${this.mode})`;
96 | this.setByValue(instance, '标题', title);
97 | this.setByValue(instance, '日期', moment().format('YYYY-MM-DD'));
98 | const Group = this.create.group({
99 | name: GroupName,
100 | parent: this.page,
101 | locked: true,
102 | });
103 |
104 | this.changeBasis(instance, { from: this.page, to: Group });
105 | instance.parent = Group;
106 |
107 | // 原生切片
108 | const sliceLayer = this.create.slice({
109 | name: [title, moment().format('MMDD')].join(' '),
110 | frame: instance.frame,
111 | parent: Group,
112 | exportFormats: [{ size: isInteractiveMode ? '0.5x' : '1x' }],
113 | });
114 | this.selectionClear();
115 | sliceLayer.selected = true;
116 | this.sortOrder();
117 | this.ui.success('制版成功');
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/models/handleSort.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handleSort extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '排序|handleSort';
8 | }
9 | run() {
10 | const sortedArtboards = _.sortBy(this.artboards, ['frame.y', 'frame.x']);
11 | _.forEach(sortedArtboards, layer => layer.moveToBack());
12 | this.sortOrder();
13 | this.ui.success('排序成功');
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/models/handleSymbol.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | export default class handlSymbol extends Sketch {
5 | constructor() {
6 | super();
7 | this.namespace = '组件|handlSymbol';
8 | }
9 |
10 | run(e) {
11 | const data = JSON.parse(e);
12 |
13 | // 找到Library
14 | const library = this.library.get(data.libname);
15 | const libDocument = library.getDocument();
16 | const symbol = libDocument.getLayerWithID(data.id);
17 |
18 | // 制作临时组件
19 | const copySymbol = symbol.duplicate();
20 | const name = ['✱', 'Temp', copySymbol.name].join(' / ');
21 | const masterArtboard = this.create.artboard({
22 | name: name,
23 | frame: copySymbol.frame,
24 | parent: libDocument.pages[1],
25 | layers: [],
26 | flowStartPoint: true,
27 | });
28 | copySymbol.frame.x = 0;
29 | copySymbol.frame.y = 0;
30 | copySymbol.parent = masterArtboard;
31 | const tempMaster = this.create.symbolMaster(masterArtboard);
32 |
33 | // 引入
34 | const master = this.library.getSymbol(library, name);
35 | if (!master) return this.ui.warn('请检查Symbol是否存在');
36 | const symbolMaster = master.import();
37 | const instance = symbolMaster.createNewInstance();
38 | instance.parent = this.page;
39 | const group = instance.detach();
40 | const inner = group.name[0] !== '✱' ? group.duplicate() : group.layers[0];
41 | group.remove();
42 | tempMaster.remove();
43 | masterArtboard.remove();
44 |
45 | // 定位拖入位置
46 | let image = _.filter(
47 | this.layer.globalGet(this.document, data.name),
48 | l => l.type === 'Image'
49 | )[0];
50 | if (!image) {
51 | image = _.filter(this.selection.layers, l => l.type === 'Image')[0];
52 | }
53 | if (image) {
54 | inner.parent = image.parent;
55 | inner.frame.x = image.frame.x;
56 | inner.frame.y = image.frame.y;
57 | image.remove();
58 | } else {
59 | inner.parent = group.parent;
60 | inner.frame.x = group.frame.x;
61 | inner.frame.y = group.frame.y;
62 | }
63 |
64 | this.selectionClear();
65 | this.selectionSet(inner);
66 |
67 | if (inner.layers) {
68 | _.forEach(inner.layers, l => {
69 | if (l.name === '@Bg') {
70 | l.remove();
71 | }
72 | });
73 | if (inner.layers.length === 1) {
74 | const innerChild = inner.layers[0];
75 | innerChild.parent = inner.parent;
76 | this.changeBasis(innerChild, { from: inner, to: inner.parent });
77 | innerChild.selected = true;
78 | inner.remove();
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/models/handleTitle.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Sketch from '../sketch';
3 |
4 | const GroupName = '@画板标题';
5 |
6 | export default class handleTitle extends Sketch {
7 | constructor() {
8 | super();
9 | this.namespace = '制标|handleTitle';
10 | }
11 |
12 | run() {
13 | const Artboards = _.filter(
14 | this.artboards,
15 | layer => layer.name[0] !== '@' && layer.frame.width >= 750 / 3 && layer.frame.width <= 750 * 3
16 | );
17 | if (Artboards.length === 0) return this.ui.warn('找不到可用画板');
18 |
19 | // 找到Symbol
20 | let symbolName;
21 | if (this.mode === '视觉') {
22 | symbolName = '视觉 / 标题';
23 | } else {
24 | const titleStyle = this.setting.get('config-title');
25 | symbolName = titleStyle === 'strong' ? '交互 / 标题-强' : '交互 / 标题';
26 | }
27 |
28 | const master = this.library.getSymbol(this.library.antoExport, symbolName);
29 | if (!master) return this.ui.warn('请检查Symbol是否存在');
30 |
31 | // 导入
32 | this.layer.deepRemove(this.page, GroupName);
33 | const Group = this.create.group({
34 | name: GroupName,
35 | parent: this.page,
36 | });
37 | const symbolMaster = master.import();
38 |
39 | // 生成
40 | _.forEach(Artboards, Artboard => {
41 | const instance = symbolMaster.createNewInstance();
42 | instance.parent = Group;
43 | instance.frame.x = Artboard.frame.x;
44 | instance.frame.y = Artboard.frame.y - 150;
45 | instance.frame.width = Artboard.frame.width;
46 | this.changeBasis(instance, { from: this.page, to: Group });
47 | // 设置override
48 | const titleId = '标题';
49 | this.setByValue(instance, titleId, Artboard.name);
50 | });
51 |
52 | // 锁定
53 | Group.locked = true;
54 | this.sortOrder();
55 | this.ui.success('制标成功');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/options.js:
--------------------------------------------------------------------------------
1 | export default {
2 | identifier: 'anto.tools',
3 | width: 48,
4 | height: 700,
5 | alwaysOnTop: true,
6 | minimizable: false,
7 | maximizable: false,
8 | fullscreenable: false,
9 | closable: false,
10 | titleBarStyle: 'hidden',
11 | vibrancy: 'sidebar',
12 | resizable: false,
13 | show: false,
14 | frame: false,
15 | transparent: true,
16 | };
17 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Options from './options';
3 | import Sketch from './sketch';
4 | // 组件
5 | import handleSymbol from './models/handleSymbol';
6 | import handleLocalSymbol from './models/handleLocalSymbol';
7 | import handleBuildLocalSymbol from './models/handleBuildLocalSymbol';
8 | // 色板
9 | import handleColor from './models/handleColor';
10 | // 注释
11 | import handleNote from './models/handleNote';
12 | // 连线
13 | import handleLine from './models/handleLine';
14 | import handleDash from './models/handleDash';
15 | import handleChange from './models/handleChange';
16 | // 图层
17 | import handleFrontBackLite from './models/handleFrontBackLite';
18 | import handleFrontBack from './models/handleFrontBack';
19 | import handleSort from './models/handleSort';
20 | import handleLayout from './models/handleLayout';
21 | import handleHeight from './models/handleHeight';
22 | import handleBlender from './models/handleBlender';
23 | // 制版
24 | import handleIgnore from './models/handleIgnore';
25 | import handleTitle from './models/handleTitle';
26 | import handlePlate from './models/handlePlate';
27 | import handleExport from './models/handleExport';
28 | // 开发
29 | import devSymbol from './models/devSymbol';
30 | import devColor from './models/devColor';
31 | import devNumber from './models/devNumber';
32 | import devTest from './models/devTest';
33 |
34 | export default class Router extends Sketch {
35 | constructor(browserWindow) {
36 | super();
37 | this.namespace = '路由|Router';
38 | this.browserWindow = browserWindow;
39 | this.webContents = browserWindow.webContents;
40 | this.width = Options.width;
41 | this.height = Options.height;
42 | }
43 |
44 | sendWebview(key, data) {
45 | this.browserWindow.webContents.executeJavaScript(
46 | `localStorage.setItem("${key}",'${JSON.stringify(data)}')`
47 | );
48 | }
49 |
50 | panel() {
51 | this.webContents.on('openPanel', e =>
52 | this.browserWindow.setSize(e ? this.width + e : this.width * 2, this.height)
53 | );
54 | this.webContents.on('closePanel', () =>
55 | this.browserWindow.setSize(this.width, this.height, true)
56 | );
57 | }
58 |
59 | symbol() {
60 | this.webContents.on('handleSymbol', e => new handleSymbol().start(e));
61 | this.webContents.on('handleLocalSymbol', e => new handleLocalSymbol().start(e));
62 | this.webContents.on('handleRule', e => {
63 | let url = 'https://yuque.antfin-inc.com/haitunmarket/docs';
64 | if (e === '交互')
65 | url = 'https://yuque.antfin-inc.com/books/share/411ef2eb-0b33-4e1c-a440-b7a2a68fecbb';
66 | this.openUrl(url);
67 | });
68 |
69 | this.webContents.on('handleBuildLocalSymbol', () => {
70 | const symbol = new handleBuildLocalSymbol().build();
71 | this.sendWebview('local-symbols-data', symbol.data);
72 | this.sendWebview('local-symbols-time', symbol.time);
73 | });
74 | }
75 |
76 | color() {
77 | this.webContents.on('handleColor', e => new handleColor().start(e));
78 | }
79 |
80 | note() {
81 | this.webContents.on('setHeader', () => new handleNote().start('header'));
82 | this.webContents.on('setSubHeader', () => new handleNote().start('subheader'));
83 | this.webContents.on('setText', () => new handleNote().start('text'));
84 | this.webContents.on('setBlock', () => new handleNote().start('block'));
85 | this.webContents.on('setList', () => new handleNote().start('list'));
86 | this.webContents.on('setUl', () => new handleNote().start('ul'));
87 | this.webContents.on('setPoint', () => new handleNote().start('point'));
88 | this.webContents.on('setRound', () => new handleNote().start('round'));
89 | this.webContents.on('setIf', () => new handleNote().start('if'));
90 | this.webContents.on('setChangelog', () => new handleNote().start('changelog'));
91 | }
92 |
93 | line() {
94 | this.webContents.on('handleLine', () => new handleLine().start());
95 | this.webContents.on('handleChange', () => new handleChange().start());
96 | this.webContents.on('handleDash', () => new handleDash().start());
97 | }
98 |
99 | layer() {
100 | this.webContents.on('handleTopLite', () => new handleFrontBackLite().start('置顶'));
101 | this.webContents.on('handleBottomLite', () => new handleFrontBackLite().start('置底'));
102 | this.webContents.on('handleTop', () => new handleFrontBack().start('置顶'));
103 | this.webContents.on('handleBottom', () => new handleFrontBack().start('置底'));
104 | this.webContents.on('handleSort', () => new handleSort().start());
105 | this.webContents.on('handleLayout', () => new handleLayout().start());
106 | this.webContents.on('handleHeight', () => new handleHeight().start());
107 | this.webContents.on('handleBlender', () => new handleBlender().start());
108 | }
109 |
110 | plate() {
111 | this.webContents.on('handleIgnore', () => new handleIgnore().start());
112 | this.webContents.on('handleTitle', () => new handleTitle().start());
113 | this.webContents.on('handlePlate', () => new handlePlate().start());
114 | this.webContents.on('handleExport', () => new handleExport().start());
115 | }
116 |
117 | word() {
118 | this.webContents.on('handleWord', e => {
119 | this.ui.success(`「${e}」已复制到剪切板`);
120 | });
121 | }
122 |
123 | yuque() {
124 | this.webContents.on('handleYuque', () => {
125 | const url = 'https://www.yuque.com/canisminor/anto/readme';
126 | this.openUrl(url);
127 | });
128 | }
129 |
130 | dev() {
131 | this.webContents.on('devNumber', () => new devNumber().start());
132 | this.webContents.on('devSymbol', () => new devSymbol().start());
133 | this.webContents.on('devColor', () => new devColor().start());
134 | this.webContents.on('devTest', () => new devTest().start());
135 | }
136 |
137 | config() {
138 | this.webContents.on('handleClose', () => {
139 | this.browserWindow.destroy();
140 | });
141 |
142 | this.webContents.on('changeMode', e => {
143 | this.setting.set('panel-mode', e);
144 | this.ui.success(`切换到「${e}模式」`);
145 | });
146 |
147 | this.webContents.on('closeSetting', e => {
148 | this.browserWindow.setSize(this.width, this.height, true);
149 | if (!e) return;
150 | console.log('[setting]', e);
151 | _.forEach(e, (value, key) => this.setting.set(`config-${key}`, value));
152 | this.ui.success(`保存设置成功`);
153 | });
154 | }
155 |
156 | run() {
157 | this.panel();
158 | this.symbol();
159 | this.color();
160 | this.note();
161 | this.line();
162 | this.layer();
163 | this.plate();
164 | this.yuque();
165 | this.word();
166 | this.dev();
167 | this.config();
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/sketch.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import { join } from 'path';
3 | import sketch from 'sketch/dom';
4 | import SketchLayer from './api/layer';
5 | import SketchCreate from './api/create';
6 | import sketchUI from './api/ui';
7 | import SketchSetting from './api/setting';
8 | import SketchLibrary from './api/library';
9 | import SketchNative from './api/native';
10 |
11 | export default class Sketch {
12 | constructor() {
13 | this.namespace = 'Anto';
14 | }
15 |
16 | // Export
17 | export(...props) {
18 | sketch.export(...props);
19 | }
20 |
21 | // UI
22 | get ui() {
23 | return new sketchUI();
24 | }
25 |
26 | get library() {
27 | return new SketchLibrary();
28 | }
29 |
30 | get create() {
31 | return new SketchCreate();
32 | }
33 |
34 | get layer() {
35 | return new SketchLayer();
36 | }
37 |
38 | get setting() {
39 | return new SketchSetting();
40 | }
41 |
42 | get fileName() {
43 | const fileName = this.native.document.displayName().stringByDeletingPathExtension();
44 | return String(fileName);
45 | }
46 |
47 | get filePath() {
48 | const filePath = this.native.document.fileURL()
49 | ? this.native.document
50 | .fileURL()
51 | .path()
52 | .stringByDeletingLastPathComponent()
53 | : '~';
54 | return String(filePath);
55 | }
56 |
57 | // Setting
58 | get line() {
59 | return this.setting.getDocument(this.document, 'anto-line');
60 | }
61 |
62 | set line(e) {
63 | return this.setting.setDocument(this.document, 'anto-line', e);
64 | }
65 |
66 | getLine(aId, bId) {
67 | const data = this.line;
68 | if (!data) this.line = {};
69 | if (!data || !data[aId] || !data[aId][bId]) return false;
70 | return this.layer.getById(this.document, data[aId][bId]);
71 | }
72 |
73 | setLine(aId, bId, lineId) {
74 | const data = this.line;
75 | if (!data) this.line = {};
76 | if (!data[aId]) data[aId] = {};
77 | if (!data[bId]) data[bId] = {};
78 | data[aId][bId] = lineId;
79 | data[bId][aId] = lineId;
80 | this.line = data;
81 | console.log(this.line);
82 | }
83 |
84 | get mode() {
85 | return this.setting.get('panel-mode');
86 | }
87 |
88 | set mode(e) {
89 | return this.setting.set('panel-mode', e);
90 | }
91 |
92 | // Document
93 | get document() {
94 | return sketch.getSelectedDocument();
95 | }
96 |
97 | get symbols() {
98 | const symbols = [];
99 | _.forEach(this.document.pages, page => {
100 | _.forEach(page.layers, l => {
101 | if (l.type && l.type === 'SymbolMaster') {
102 | symbols.push(l);
103 | }
104 | });
105 | });
106 | return symbols;
107 | }
108 |
109 | get allSymbols() {
110 | return this.document.getSymbols();
111 | }
112 |
113 | get page() {
114 | return this.document.selectedPage;
115 | }
116 |
117 | get artboards() {
118 | return _.filter(this.page.layers, l => l.type && l.type === 'Artboard');
119 | }
120 |
121 | // selection
122 |
123 | get selection() {
124 | return this.document.selectedLayers;
125 | }
126 |
127 | selectionClear() {
128 | this.selection.clear();
129 | }
130 |
131 | selectionSet(layers) {
132 | if (_.isArray(layers)) {
133 | _.forEach(layers, l => (l.selected = true));
134 | } else {
135 | layers.selected = true;
136 | }
137 | }
138 |
139 | // native
140 | get context() {
141 | return NSDocumentController.sharedDocumentController();
142 | }
143 |
144 | get native() {
145 | return new SketchNative();
146 | }
147 |
148 | // path
149 |
150 | get pluginPath() {
151 | return this.setting.get('url');
152 | }
153 |
154 | get pluginResourcesPath() {
155 | return join(this.pluginPath, 'Contents', 'Resources');
156 | }
157 |
158 | // open
159 |
160 | openUrl(url) {
161 | url = NSURL.URLWithString(url);
162 | NSWorkspace.sharedWorkspace().openURL(url);
163 | }
164 |
165 | openPath(path) {
166 | path = NSURL.fileURLWithPath(path);
167 | NSWorkspace.sharedWorkspace().openURL(path);
168 | }
169 |
170 | // utils
171 |
172 | changeBasis(layer, option = {}) {
173 | layer.frame = layer.frame.changeBasis(option);
174 | }
175 |
176 | setGroup(group, layers) {
177 | _.forEach(layers, l => {
178 | l.frame = l.frame.changeBasis({ from: l.parent, to: group });
179 | l.parent = group;
180 | });
181 | }
182 |
183 | resizeGroup(group) {
184 | let minX = 0;
185 | let minY = 0;
186 | let maxX = 0;
187 | let maxY = 0;
188 | const Layers = group.layers;
189 | _.forEach(Layers, l => {
190 | const size = l.frame.changeBasis({ from: group, to: group.parent });
191 | if (size.x < minX) minX = size.x;
192 | if (size.y < minY) minY = size.y;
193 | if (size.x + size.width > maxX) maxX = size.x + size.width;
194 | if (size.y + size.height > maxY) maxY = size.y + size.height;
195 | l.frame = l.frame.changeBasis({ from: group, to: this.page });
196 | l.parent = this.page;
197 | });
198 | group.frame.x = minX;
199 | group.frame.y = minY;
200 | group.frame.width = maxX - minX;
201 | group.frame.height = maxY - minY;
202 | _.forEach(Layers, l => {
203 | l.frame = l.frame.changeBasis({ from: this.page, to: group });
204 | l.parent = group;
205 | });
206 | }
207 |
208 | setById(instance, id, value) {
209 | const result = _.filter(instance.overrides, o => o.id === id);
210 | if (result.length === 0) return this.ui.message(`请检查Override-${instance.name}`);
211 | instance.setOverrideValue(result[0], value);
212 | }
213 |
214 | setByValue(instance, defaultValue, value) {
215 | const result = _.filter(instance.overrides, o => o.value === defaultValue);
216 | if (result.length === 0) return this.ui.message(`请检查Override-${instance.name}`);
217 | instance.setOverrideValue(result[0], value);
218 | }
219 |
220 | sortOrder(page = this.page) {
221 | const GroupTop = this.layer.deepGet(page, '@置顶');
222 | const GroupLine = this.layer.deepGet(page, '@交互连线');
223 | const GroupTitle = this.layer.deepGet(page, '@画板标题');
224 | const GroupShadow = this.layer.deepGet(page, '@画板投影');
225 | const GorupBottom = this.layer.deepGet(page, '@置底');
226 | const GroupBg = this.layer.deepGet(page, '@制版');
227 | // Front
228 | if (GroupTitle) _.forEach(GroupTitle, l => l.moveToFront());
229 | if (GroupLine) _.forEach(GroupLine, l => l.moveToFront());
230 | if (GroupTop) _.forEach(GroupTop, l => l.moveToFront());
231 | // Back
232 | if (GroupShadow) _.forEach(GroupShadow, l => l.moveToBack());
233 | if (GorupBottom) _.forEach(GorupBottom, l => l.moveToBack());
234 | if (GroupBg) _.forEach(GroupBg, l => l.moveToBack());
235 | }
236 |
237 | // run
238 |
239 | run() {}
240 |
241 | start(...props) {
242 | try {
243 | console.log('[Start]', this.namespace);
244 | this.run(...props);
245 | console.log('[End]', this.namespace);
246 | } catch (e) {
247 | console.log('[Error]', this.namespace, e);
248 | this.ui.alert(`🔵😥 ${this.namespace}`, String(e));
249 | }
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/update.js:
--------------------------------------------------------------------------------
1 | import compareVersions from 'compare-versions';
2 | import UI from 'sketch/ui';
3 |
4 | export const update = context => {
5 | fetch('https://api.github.com/repos/canisminor1990/anto/releases/latest')
6 | .then(res => res.json())
7 | .then(json => {
8 | const { name, assets } = json;
9 | const result = compareVersions(name, String(context.plugin.version()));
10 | if (result === 1) {
11 | const url = assets[0].browser_download_url;
12 | const ok = selectPanel(`发现最新版本 🔵 Anto,是否立即更新?`, [name]);
13 | if (ok) {
14 | NSWorkspace.sharedWorkspace().openURL(NSURL.URLWithString(url));
15 | }
16 | }
17 | });
18 | };
19 |
20 | function selectPanel(title, options = []) {
21 | const selection = UI.getSelectionFromUser(title, options);
22 | const ok = selection[2];
23 | const value = options[selection[1]];
24 | return ok ? value : false;
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import UI from 'sketch/ui';
2 | import _ from 'lodash';
3 |
4 | // 找到唯一图层
5 | export const find = (array, key, value) => {
6 | const result = _.filter(array, m => m[key] === value);
7 | return result.length > 0 ? result[0] : false;
8 | };
9 |
10 | // 选取指定图层
11 | export const getLayer = (page, name) => {
12 | let getLayer = false;
13 | page.layers.forEach(layer => {
14 | if (layer.name === name) getLayer = layer;
15 | });
16 | return getLayer;
17 | };
18 |
19 | // 删除指定图层
20 | export const removeLayer = (page, name) => {
21 | page.layers.forEach(layer => {
22 | if (layer.name === name) layer.remove();
23 | });
24 | };
25 |
26 | // 全局删除指定图层
27 | export const globalRemoveLayer = (document, name) => {
28 | try {
29 | const layers = document.getLayersNamed(name);
30 | if (layers.length > 0) _.forEach(layers, layer => layer.remove());
31 | } catch (e) {
32 | console.log('globalRemoveLayer', e);
33 | }
34 | };
35 |
36 | // 设置Symbol的Override
37 | export const setById = (instance, id, value) => {
38 | const result = _.filter(instance.overrides, o => o.id === id);
39 | if (result.length === 0) return UI.message(`请检查Override-${instance.name}`);
40 | instance.setOverrideValue(result[0], value);
41 | };
42 |
43 | export const setByValue = (instance, setByValue, value) => {
44 | const result = _.filter(instance.overrides, o => o.value === setByValue);
45 | if (result.length === 0) return UI.message(`请检查Override-${instance.name}`);
46 | instance.setOverrideValue(result[0], value);
47 | };
48 |
49 | // 排序
50 | export const GroupOrder = page => {
51 | const GorupTop = find(page.layers, 'name', '@置顶');
52 | const GroupLine = find(page.layers, 'name', '@交互连线');
53 | const GroupTitle = find(page.layers, 'name', '@画板标题');
54 | const GroupShadow = find(page.layers, 'name', '@画板投影');
55 | const GorupBottom = find(page.layers, 'name', '@置底');
56 | const GroupBg = find(page.layers, 'name', '@制版');
57 |
58 | if (GroupTitle) GroupTitle.moveToFront();
59 | if (GroupLine) GroupLine.moveToFront();
60 | if (GorupTop) GorupTop.moveToFront();
61 |
62 | if (GroupShadow) GroupShadow.moveToBack();
63 | if (GorupBottom) GorupBottom.moveToBack();
64 | if (GroupBg) GroupBg.moveToBack();
65 | };
66 |
--------------------------------------------------------------------------------
/test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type" : "SymbolInstance",
3 | "id" : "1908A112-B2E0-43ED-A733-4F67AC56DE5A",
4 | "frame" : {
5 | "x" : 92,
6 | "y" : 115.5,
7 | "width" : 326,
8 | "height": 237
9 | },
10 | "name" : "o",
11 | "selected" : true,
12 | "hidden" : false,
13 | "locked" : false,
14 | "exportFormats": [],
15 | "transform" : {
16 | "rotation" : 0,
17 | "flippedHorizontally": false,
18 | "flippedVertically" : false
19 | },
20 | "style" : {
21 | "type" : "Style",
22 | "id" : "A5C51B5D-0EC2-4459-9995-736638E9604B",
23 | "opacity" : 1,
24 | "blendingMode" : "Normal",
25 | "borderOptions": {
26 | "startArrowhead": "None",
27 | "endArrowhead" : "None",
28 | "dashPattern" : [],
29 | "lineEnd" : "Butt",
30 | "lineJoin" : "Miter"
31 | },
32 | "blur" : {
33 | "center" : {
34 | "x": 0.5,
35 | "y": 0.5
36 | },
37 | "motionAngle": 0,
38 | "radius" : 10,
39 | "enabled" : false,
40 | "blurType" : "Gaussian"
41 | },
42 | "fills" : [],
43 | "borders" : [],
44 | "shadows" : [],
45 | "innerShadows" : [],
46 | "styleType" : "Layer"
47 | },
48 | "sharedStyleId": null,
49 | "symbolId" : "F7BB0B0E-EB90-43D4-AA70-9292C475B2B3",
50 | "overrides" : [
51 | {
52 | "type" : "Override",
53 | "id" : "6F52AC17-1142-4BD9-A8D7-7B8BE37B86D4_stringValue",
54 | "path" : "6F52AC17-1142-4BD9-A8D7-7B8BE37B86D4",
55 | "property" : "stringValue",
56 | "affectedLayer" : {
57 | "type" : "Text",
58 | "id" : "6F52AC17-1142-4BD9-A8D7-7B8BE37B86D4",
59 | "frame" : {
60 | "x" : -22,
61 | "y" : 77,
62 | "width" : 371,
63 | "height": 70
64 | },
65 | "name" : "Type something",
66 | "hidden" : false,
67 | "locked" : false,
68 | "exportFormats": [],
69 | "transform" : {
70 | "rotation" : 0,
71 | "flippedHorizontally": false,
72 | "flippedVertically" : false
73 | },
74 | "style" : {
75 | "type" : "Style",
76 | "id" : "A4A6D38E-EB3C-4251-8216-35BEFB06611B",
77 | "opacity" : 1,
78 | "blendingMode" : "Normal",
79 | "borderOptions" : {
80 | "startArrowhead": "None",
81 | "endArrowhead" : "None",
82 | "dashPattern" : [],
83 | "lineEnd" : "Butt",
84 | "lineJoin" : "Miter"
85 | },
86 | "blur" : {
87 | "center" : {
88 | "x": 0.5,
89 | "y": 0.5
90 | },
91 | "motionAngle": 0,
92 | "radius" : 10,
93 | "enabled" : false,
94 | "blurType" : "Gaussian"
95 | },
96 | "fills" : [],
97 | "borders" : [],
98 | "shadows" : [],
99 | "innerShadows" : [],
100 | "styleType" : "Text",
101 | "alignment" : "justified",
102 | "verticalAlignment": "top",
103 | "kerning" : 0,
104 | "lineHeight" : null,
105 | "paragraphSpacing" : 0,
106 | "textColor" : "#999999ff",
107 | "fontSize" : 50,
108 | "textTransform" : "none",
109 | "fontFamily" : "PingFang SC",
110 | "fontWeight" : 5
111 | },
112 | "sharedStyleId": null,
113 | "text" : "Type something",
114 | "lineSpacing" : "constantBaseline",
115 | "fixedWidth" : false
116 | },
117 | "symbolOverride": false,
118 | "value" : "Type something",
119 | "isDefault" : true,
120 | "editable" : true,
121 | "selected" : false
122 | }
123 | ]
124 | }
--------------------------------------------------------------------------------