Commit 25dbd43c9d1f99bea1bfe5f00270864517b57ca4

Authored by ly525
1 parent 7b6a433f

refactor(core editor): render canvas、preview、props editor、plugin shortcuts in single file

front-end/h5/src/components/core/editor/canvas/edit.js 0 → 100644
  1 +export default {
  2 + props: ['elements', 'handleElementClick'],
  3 + methods: {
  4 + /**
  5 + * #!zh: renderCanvas 渲染中间画布
  6 + * elements
  7 + * @param {*} h
  8 + * @param {*} elements
  9 + * @returns
  10 + */
  11 + renderCanvas (h, elements) {
  12 + return (
  13 + <div style={{ height: '100%' }}>
  14 + {
  15 + elements.map((element, index) => {
  16 + const data = {
  17 + style: element.getStyle(),
  18 + props: element.pluginProps, // #6 #3
  19 + nativeOn: {
  20 + click: () => this.handleElementClick(element)
  21 + }
  22 + }
  23 + return h(element.name, data)
  24 + })
  25 + }
  26 + </div>
  27 + )
  28 + }
  29 + },
  30 + render (h) {
  31 + return this.renderCanvas(h, this.elements)
  32 + }
  33 +}
front-end/h5/src/components/core/editor/canvas/preview.js 0 → 100644
  1 +export default {
  2 + props: ['elements'],
  3 + methods: {
  4 + renderPreview (h, elements) {
  5 + return (
  6 + <div style={{ height: '100%' }}>
  7 + {elements.map((element, index) => {
  8 + return (() => {
  9 + const data = {
  10 + style: element.getStyle(),
  11 + props: element.pluginProps, // #6 #3
  12 + nativeOn: {}
  13 + }
  14 + return h(element.name, data)
  15 + })()
  16 + })}
  17 + </div>
  18 + )
  19 + }
  20 + },
  21 + render (h) {
  22 + return this.renderPreview(h, this.elements)
  23 + }
  24 +}
front-end/h5/src/components/core/editor/edit-panel/props.js 0 → 100644
  1 +export default {
  2 + props: ['editingElement'],
  3 + methods: {
  4 + /**
  5 + * 将插件属性的 自定义增强编辑器注入 属性编辑面板中
  6 + */
  7 + mixinEnhancedPropsEditor (editingElement) {
  8 + const { components } = editingElement.editorConfig
  9 + for (const key in components) {
  10 + if (this.$options.components[key]) return
  11 + this.$options.components[key] = components[key]
  12 + }
  13 + },
  14 + renderPropsEditorPanel (h, editingElement) {
  15 + const propsConfig = editingElement.editorConfig.propsConfig
  16 + return (
  17 + <a-form ref="form" label-width="100px" size="mini" label-position="left">
  18 + {
  19 + Object.keys(propsConfig).map(propKey => {
  20 + const item = propsConfig[propKey]
  21 + // https://vuejs.org/v2/guide/render-function.html
  22 + const data = {
  23 + props: {
  24 + ...item.prop,
  25 + // https://vuejs.org/v2/guide/render-function.html#v-model
  26 + value: editingElement.pluginProps[propKey] || item.defaultPropValue
  27 + },
  28 + on: {
  29 + // https://vuejs.org/v2/guide/render-function.html#v-model
  30 + // input (e) {
  31 + // editingElement.pluginProps[propKey] = e.target ? e.target.value : e
  32 + // }
  33 + change (e) {
  34 + // TODO fixme: update plugin props in vuex with dispatch
  35 + editingElement.pluginProps[propKey] = e.target ? e.target.value : e
  36 + }
  37 + }
  38 + }
  39 + return (
  40 + <a-form-item label={item.label}>
  41 + { h(item.type, data) }
  42 + </a-form-item>
  43 + )
  44 + })
  45 + }
  46 + </a-form>
  47 + )
  48 + }
  49 + },
  50 + render (h) {
  51 + const ele = this.editingElement
  52 + if (!ele) return (<span>请先选择一个元素</span>)
  53 + this.mixinEnhancedPropsEditor(ele)
  54 + return this.renderPropsEditorPanel(h, ele)
  55 + }
  56 +}
front-end/h5/src/components/core/editor/index.js
@@ -2,42 +2,31 @@ import Vue from &#39;vue&#39; @@ -2,42 +2,31 @@ import Vue from &#39;vue&#39;
2 import Element from '../models/element' 2 import Element from '../models/element'
3 import '../styles/index.scss' 3 import '../styles/index.scss'
4 4
  5 +import RenderEditCanvas from './canvas/edit'
  6 +import RenderPreviewCanvas from './canvas/preview'
  7 +import RenderPropsEditor from './edit-panel/props'
  8 +import RenderShortcutsPanel from './shortcuts-panel/index'
  9 +
  10 +const sidebarMenus = [
  11 + {
  12 + label: '组件列表',
  13 + value: 'pluginList',
  14 + antIcon: 'user'
  15 + },
  16 + {
  17 + label: '页面管理',
  18 + value: 'pageManagement',
  19 + antIcon: 'copy'
  20 + },
  21 + {
  22 + label: '免费模板',
  23 + value: 'freeTemplate',
  24 + antIcon: 'appstore'
  25 + }
  26 +]
5 export default { 27 export default {
6 name: 'Editor', 28 name: 'Editor',
7 - components: {  
8 - ShortcutButton: {  
9 - functional: true,  
10 - props: {  
11 - faIcon: {  
12 - required: true,  
13 - type: String  
14 - },  
15 - title: {  
16 - required: true,  
17 - type: String  
18 - },  
19 - clickFn: {  
20 - required: false,  
21 - type: Function  
22 - }  
23 - },  
24 - render: (h, { props, listeners, slots }) => {  
25 - const onClick = props.clickFn || function () {}  
26 - return (  
27 - <a-button  
28 - class="shortcut-button"  
29 - onClick={onClick}  
30 - >  
31 - <i  
32 - class={['shortcut-icon', 'fa', `fa-${props.faIcon}`]}  
33 - aria-hidden='true'  
34 - />  
35 - <span>{ props.title }</span>  
36 - </a-button>  
37 - )  
38 - }  
39 - }  
40 - }, 29 + components: {},
41 data: () => ({ 30 data: () => ({
42 activeMenuKey: 'pluginList', 31 activeMenuKey: 'pluginList',
43 pages: [], 32 pages: [],
@@ -63,159 +52,8 @@ export default { @@ -63,159 +52,8 @@ export default {
63 const editorConfig = this.getEditorConfig(name) 52 const editorConfig = this.getEditorConfig(name)
64 this.elements.push(new Element({ name, zindex, editorConfig })) 53 this.elements.push(new Element({ name, zindex, editorConfig }))
65 }, 54 },
66 - mixinPluginCustomComponents2Editor () {  
67 - const { components } = this.editingElement.editorConfig  
68 - for (const key in components) {  
69 - if (this.$options.components[key]) return  
70 - this.$options.components[key] = components[key]  
71 - }  
72 - },  
73 setCurrentEditingElement (element) { 55 setCurrentEditingElement (element) {
74 this.editingElement = element 56 this.editingElement = element
75 - this.mixinPluginCustomComponents2Editor()  
76 - },  
77 - /**  
78 - * #!zh: 在左侧或顶部导航上显示可用的组件快捷方式,用户点击之后,即可将其添加到中间画布上  
79 - * #!en: render shortcust at the sidebar or the header. if user click the shortcut, the related plugin will be added to the canvas  
80 - * @param {Object} group: {children, title, icon}  
81 - */  
82 - renderPluginShortcut (group) {  
83 - return group.children.length === 1  
84 - ? this.renderSinglePluginShortcut(group)  
85 - : this.renderMultiPluginShortcuts(group)  
86 - },  
87 - /**  
88 - * #!zh 渲染多个插件的快捷方式  
89 - * #!en render shortcuts for multi plugins  
90 - * @param {Object} group: {children, title, icon}  
91 - */  
92 - renderMultiPluginShortcuts (group) {  
93 - const plugins = group.children  
94 - return <a-popover  
95 - placement="bottom"  
96 - class="shortcust-button"  
97 - trigger="hover">  
98 - <a-row slot="content" gutter={20} style={{ width: '400px' }}>  
99 - {  
100 - plugins.sort().map(item => (  
101 - <a-col span={6}>  
102 - <ShortcutButton  
103 - clickFn={this.clone.bind(this, item)}  
104 - title={item.title}  
105 - faIcon={item.icon}  
106 - />  
107 - </a-col>  
108 - ))  
109 - }  
110 - </a-row>  
111 - <ShortcutButton  
112 - title={group.title}  
113 - faIcon={group.icon}  
114 - />  
115 - </a-popover>  
116 - },  
117 - /**  
118 - * #!zh: 渲染单个插件的快捷方式  
119 - * #!en: render shortcut for single plugin  
120 - * @param {Object} group: {children, title, icon}  
121 - */  
122 - renderSinglePluginShortcut ({ children }) {  
123 - const [plugin] = children  
124 - return <ShortcutButton  
125 - clickFn={this.clone.bind(this, plugin)}  
126 - title={plugin.title}  
127 - faIcon={plugin.icon}  
128 - />  
129 - },  
130 - /**  
131 - * #!zh: renderCanvas 渲染中间画布  
132 - * elements  
133 - * @param {*} h  
134 - * @param {*} elements  
135 - * @returns  
136 - */  
137 - renderCanvas (h, elements) {  
138 - return (  
139 - <div style={{ height: '100%' }}>  
140 - {elements.map((element, index) => {  
141 - return (() => {  
142 - const data = {  
143 - style: element.getStyle(),  
144 - props: element.pluginProps, // #6 #3  
145 - nativeOn: {  
146 - click: this.setCurrentEditingElement.bind(this, element)  
147 - }  
148 - }  
149 - return h(element.name, data)  
150 - })()  
151 - })}  
152 - </div>  
153 - )  
154 - },  
155 - renderPreview (h, elements) {  
156 - return (  
157 - <div style={{ height: '100%' }}>  
158 - {elements.map((element, index) => {  
159 - return (() => {  
160 - const data = {  
161 - style: element.getStyle(),  
162 - props: element.pluginProps, // #6 #3  
163 - nativeOn: {}  
164 - }  
165 - return h(element.name, data)  
166 - })()  
167 - })}  
168 - </div>  
169 - )  
170 - },  
171 - renderPluginListPanel () {  
172 - return (  
173 - <a-row gutter={20}>  
174 - {  
175 - this.groups.sort().map(group => (  
176 - <a-col span={12} style={{ marginTop: '10px' }}>  
177 - {this.renderPluginShortcut(group)}  
178 - </a-col>  
179 - ))  
180 - }  
181 - </a-row>  
182 - )  
183 - },  
184 - renderPropsEditorPanel (h) {  
185 - if (!this.editingElement) return (<span>请先选择一个元素</span>)  
186 - const editingElement = this.editingElement  
187 - const propsConfig = editingElement.editorConfig.propsConfig  
188 - return (  
189 - <a-form ref="form" label-width="100px" size="mini" label-position="left">  
190 - {  
191 - Object.keys(propsConfig).map(propKey => {  
192 - const item = propsConfig[propKey]  
193 - // https://vuejs.org/v2/guide/render-function.html  
194 - const data = {  
195 - props: {  
196 - ...item.prop,  
197 - // https://vuejs.org/v2/guide/render-function.html#v-model  
198 - value: editingElement.pluginProps[propKey] || item.defaultPropValue  
199 - },  
200 - on: {  
201 - // https://vuejs.org/v2/guide/render-function.html#v-model  
202 - // input (e) {  
203 - // editingElement.pluginProps[propKey] = e.target ? e.target.value : e  
204 - // }  
205 - change (e) {  
206 - editingElement.pluginProps[propKey] = e.target ? e.target.value : e  
207 - }  
208 - }  
209 - }  
210 - return (  
211 - <a-form-item label={item.label}>  
212 - { h(item.type, data) }  
213 - </a-form-item>  
214 - )  
215 - })  
216 - }  
217 - </a-form>  
218 - )  
219 } 57 }
220 }, 58 },
221 render (h) { 59 render (h) {
@@ -223,21 +61,7 @@ export default { @@ -223,21 +61,7 @@ export default {
223 <a-layout id="luban-layout" style={{ height: '100vh' }}> 61 <a-layout id="luban-layout" style={{ height: '100vh' }}>
224 <a-layout-header class="header"> 62 <a-layout-header class="header">
225 <div class="logo">鲁班 H5</div> 63 <div class="logo">鲁班 H5</div>
226 - {/* TODO we can show the plugins shortcuts here  
227 - <a-menu  
228 - theme="dark"  
229 - mode="horizontal"  
230 - defaultSelectedKeys={['2']}  
231 - style={{ lineHeight: '64px', float: 'left', marginLeft: '30%', background: 'transparent' }}  
232 - >  
233 - {  
234 - this.groups.sort().map((group, id) => (  
235 - <a-menu-item key={id} class="transparent-bg">  
236 - {this.renderPluginShortcut(group)}  
237 - </a-menu-item>  
238 - ))  
239 - }  
240 - </a-menu> */} 64 + {/* TODO we can show the plugins shortcuts here */}
241 <a-menu 65 <a-menu
242 theme="dark" 66 theme="dark"
243 mode="horizontal" 67 mode="horizontal"
@@ -252,22 +76,18 @@ export default { @@ -252,22 +76,18 @@ export default {
252 <a-layout> 76 <a-layout>
253 <a-layout-sider width="160" style="background: #fff"> 77 <a-layout-sider width="160" style="background: #fff">
254 <a-menu onSelect={val => { this.activeMenuKey = val }} mode="inline" defaultSelectedKeys={['pluginList']} style={{ height: '100%', borderRight: 1 }}> 78 <a-menu onSelect={val => { this.activeMenuKey = val }} mode="inline" defaultSelectedKeys={['pluginList']} style={{ height: '100%', borderRight: 1 }}>
255 - <a-menu-item key="pluginList">  
256 - <a-icon type="user" />  
257 - <span>组件列表</span>  
258 - </a-menu-item>  
259 - <a-menu-item key="2">  
260 - <a-icon type="video-camera" />  
261 - <span>页面管理</span>  
262 - </a-menu-item>  
263 - <a-menu-item key="3">  
264 - <a-icon type="upload" />  
265 - <span>更多模板</span>  
266 - </a-menu-item> 79 + {
  80 + sidebarMenus.map(menu => (
  81 + <a-menu-item key={menu.value}>
  82 + <a-icon type={menu.antIcon} />
  83 + <span>{menu.label}</span>
  84 + </a-menu-item>
  85 + ))
  86 + }
267 </a-menu> 87 </a-menu>
268 </a-layout-sider> 88 </a-layout-sider>
269 <a-layout-sider width="240" theme='light' style={{ background: '#fff', padding: '0 12px' }}> 89 <a-layout-sider width="240" theme='light' style={{ background: '#fff', padding: '0 12px' }}>
270 - { this.renderPluginListPanel() } 90 + <RenderShortcutsPanel groups={this.groups} handleClickShortcut={this.clone} />
271 </a-layout-sider> 91 </a-layout-sider>
272 <a-layout style="padding: 0 24px 24px"> 92 <a-layout style="padding: 0 24px 24px">
273 <a-layout-content style={{ padding: '24px', margin: 0, minHeight: '280px' }}> 93 <a-layout-content style={{ padding: '24px', margin: 0, minHeight: '280px' }}>
@@ -283,7 +103,8 @@ export default { @@ -283,7 +103,8 @@ export default {
283 </a-radio-group> 103 </a-radio-group>
284 </div> 104 </div>
285 <div class='canvas-wrapper'> 105 <div class='canvas-wrapper'>
286 - { this.isPreviewMode ? this.renderPreview(h, this.elements) : this.renderCanvas(h, this.elements) } 106 + {/* { this.isPreviewMode ? this.renderPreview(h, this.elements) : this.renderCanvas(h, this.elements) } */}
  107 + { this.isPreviewMode ? <RenderPreviewCanvas elements={this.elements}/> : <RenderEditCanvas elements={this.elements} handleElementClick={this.setCurrentEditingElement} /> }
287 </div> 108 </div>
288 </a-layout-content> 109 </a-layout-content>
289 </a-layout> 110 </a-layout>
@@ -300,7 +121,8 @@ export default { @@ -300,7 +121,8 @@ export default {
300 <a-icon type="apple" /> 121 <a-icon type="apple" />
301 属性 122 属性
302 </span> 123 </span>
303 - { this.renderPropsEditorPanel(h) } 124 + {/* { this.renderPropsEditorPanel(h) } */}
  125 + <RenderPropsEditor editingElement={this.editingElement} />
304 </a-tab-pane> 126 </a-tab-pane>
305 <a-tab-pane label="动画" key='动画' tab='动画'>动画</a-tab-pane> 127 <a-tab-pane label="动画" key='动画' tab='动画'>动画</a-tab-pane>
306 <a-tab-pane label="动作" key='动作' tab='动作'>动作</a-tab-pane> 128 <a-tab-pane label="动作" key='动作' tab='动作'>动作</a-tab-pane>
front-end/h5/src/components/core/editor/shortcuts-panel/index.js 0 → 100644
  1 +import ShortcutButton from './shortcut-button'
  2 +export default {
  3 + props: {
  4 + groups: {
  5 + required: false,
  6 + type: Array,
  7 + default: () => []
  8 + },
  9 + handleClickShortcut: {
  10 + type: Function
  11 + }
  12 + },
  13 + methods: {
  14 + onClickShortcut (item) {
  15 + if (this.handleClickShortcut) {
  16 + this.handleClickShortcut(item)
  17 + }
  18 + },
  19 + /**
  20 + * #!zh 渲染多个插件的快捷方式
  21 + * #!en render shortcuts for multi plugins
  22 + * @param {Object} group: {children, title, icon}
  23 + */
  24 + renderMultiShortcuts (group) {
  25 + const plugins = group.children
  26 + return <a-popover
  27 + placement="bottom"
  28 + class="shortcust-button"
  29 + trigger="hover">
  30 + <a-row slot="content" gutter={20} style={{ width: '400px' }}>
  31 + {
  32 + plugins.sort().map(item => (
  33 + <a-col span={6}>
  34 + <ShortcutButton
  35 + clickFn={this.onClickShortcut.bind(this, item)}
  36 + title={item.title}
  37 + faIcon={item.icon}
  38 + />
  39 + </a-col>
  40 + ))
  41 + }
  42 + </a-row>
  43 + <ShortcutButton
  44 + title={group.title}
  45 + faIcon={group.icon}
  46 + />
  47 + </a-popover>
  48 + },
  49 + /**
  50 + * #!zh: 渲染单个插件的快捷方式
  51 + * #!en: render shortcut for single plugin
  52 + * @param {Object} group: {children, title, icon}
  53 + */
  54 + renderSingleShortcut ({ children }) {
  55 + const [plugin] = children
  56 + return <ShortcutButton
  57 + clickFn={this.onClickShortcut.bind(this, plugin)}
  58 + title={plugin.title}
  59 + faIcon={plugin.icon}
  60 + />
  61 + },
  62 + /**
  63 + * #!zh: 在左侧或顶部导航上显示可用的组件快捷方式,用户点击之后,即可将其添加到中间画布上
  64 + * #!en: render shortcust at the sidebar or the header. if user click the shortcut, the related plugin will be added to the canvas
  65 + * @param {Object} group: {children, title, icon}
  66 + */
  67 + renderShortCutsPanel (groups) {
  68 + return (
  69 + <a-row gutter={20}>
  70 + {
  71 + groups.sort().map(group => (
  72 + <a-col span={12} style={{ marginTop: '10px' }}>
  73 + {
  74 + group.children.length === 1
  75 + ? this.renderSingleShortcut(group)
  76 + : this.renderMultiShortcuts(group)
  77 + }
  78 + </a-col>
  79 + ))
  80 + }
  81 + </a-row>
  82 + )
  83 + }
  84 + },
  85 + render (h) {
  86 + return this.renderShortCutsPanel(this.groups)
  87 + }
  88 +}
front-end/h5/src/components/core/editor/shortcuts-panel/shortcut-button.js 0 → 100644
  1 +export default {
  2 + functional: true,
  3 + props: {
  4 + faIcon: {
  5 + required: true,
  6 + type: String
  7 + },
  8 + title: {
  9 + required: true,
  10 + type: String
  11 + },
  12 + clickFn: {
  13 + required: false,
  14 + type: Function
  15 + }
  16 + },
  17 + render: (h, { props, listeners, slots }) => {
  18 + const onClick = props.clickFn || function () {}
  19 + return (
  20 + <a-button
  21 + class="shortcut-button"
  22 + onClick={onClick}
  23 + >
  24 + <i
  25 + class={['shortcut-icon', 'fa', `fa-${props.faIcon}`]}
  26 + aria-hidden='true'
  27 + />
  28 + <span>{ props.title }</span>
  29 + </a-button>
  30 + )
  31 + }
  32 +}