React Editor 應(yīng)用編輯器(2) - 編輯區(qū)基本設(shè)計

上一篇說了如何實(shí)現(xiàn)靈活的拖拽勇哗,那么加上編輯功能昼扛,拖拽編輯器的兩大核心功能就集齊了,剩下就是組件樹欲诺、版本管理、模板渺鹦、預(yù)覽扰法、快捷鍵、事件毅厚、動畫塞颁、在線編輯代碼以及部署方式這些邊角功能,當(dāng)然這些邊角功能都不影響大局吸耿,這次我們來談?wù)勅绾卧O(shè)計編輯區(qū)祠锣,類似下圖的結(jié)構(gòu):

editor.gif
  1. 從圖中可以看出,編輯區(qū)涉及很多數(shù)據(jù)同步操作咽安,我們使用了 mobx 很好的解決了這個問題伴网,本篇文章因?yàn)橹攸c(diǎn)描述編輯器設(shè)計,因此數(shù)據(jù)設(shè)計部分不會過多涉及妆棒。
  2. 除了基本屬性設(shè)置澡腾,還應(yīng)該有腳本設(shè)置沸伏、事件設(shè)置、動畫設(shè)置动分,這些后續(xù)文章再討論毅糟。

通用屬性編輯

我們發(fā)現(xiàn),樣式才是最通用的屬性澜公,無論何種組件都逃離不了樣式的設(shè)置姆另,除此以外的屬性都是自定義的,我們無法抽象出共性加以定制坟乾,但是樣式是固定的蜕青,所以編輯區(qū)先要支持通用樣式的編輯。

通用樣式:背景 邊框 字體 邊距 布局 溢出處理 寬高 透明度

我們提供了對應(yīng)的 13 余中定制編輯類型糊渊,比如像上圖的邊距調(diào)節(jié)器右核,專門針對邊距進(jìn)行修改,只要將編輯類型設(shè)置為 marginPadding 渺绒,編輯框中就會出現(xiàn)非常方便的邊距調(diào)節(jié)器贺喝。

還有一種通用屬性處理,比如有一個圖標(biāo)組件宗兼,實(shí)現(xiàn)以下效果:

icon.gif

如果單獨(dú)為圖標(biāo)類型設(shè)置一種編輯狀態(tài)很不劃算躏鱼,這種分類可以劃為 實(shí)例類型每一個圖標(biāo)其實(shí)是這個組件接收了某種參數(shù)后的狀態(tài)殷绍,我們預(yù)先提供這些狀態(tài)染苛,編輯器將這些狀態(tài)的組件分別實(shí)例化顯示出來,每當(dāng)鼠標(biāo)點(diǎn)擊時主到,就將當(dāng)前狀態(tài)覆蓋到頁面中茶行。編輯配置入下:

const instances = [{
    name: 'icnMineSettingB'
}, {
    name: 'iconFindSearch'
}, {
    name: 'minus'
}]

const editOption = {
    field: null as string,
    label: '',
    editor: 'instance',
    editable: true,
    instance: instances
}

每一種圖標(biāo)樣式其實(shí)就是 name 屬性的不同,將這些 name 分別填充給實(shí)例化出來的組件登钥,就能看到上圖的效果畔师,每次點(diǎn)擊都會將 instances 中當(dāng)前項(xiàng)作為 props 覆蓋到頁面組件中,便實(shí)現(xiàn)了預(yù)期效果牧牢,并且類似需求都具有很強(qiáng)的通用性看锉。

通用屬性如何設(shè)置在組件上

每個組件都是一個 React Class ,其 defaultProps 屬性只要包含了 gaeaName gaeaIcon gaeaUniqueKeygaeaEdit 屬性塔鳍,就擁有編輯功能伯铣。

gaeaNamegaeaIcon 分別是顯示在編輯器上的組件名和圖標(biāo)。

gaeaUniqueKey 是給每個組件起的唯一 key轮纫,所有類的尋找都以此為依據(jù)腔寡。

gaeaEdit 是數(shù)組,存放了編輯類型蜡感。

一個基本的 gaeaEdit 對象如下:

gaeaEdit = [{
    field: 'name',
    label: '名稱',
    editor: 'text',
    editable: true
}]

editor 表示了當(dāng)前屬性用什么類型編輯器編輯蹬蚁,通用編輯類型有文本框恃泪,選擇框,開關(guān)等等犀斋,除此之外還有定制編輯類型贝乎,比如 background

field 表示了編輯后對應(yīng)改變哪個字段的值叽粹。

label 表示在編輯器上顯示的提示文案览效。

editor 還有許多類型,比如 editor: number 類型的配置如下(透明度就是封裝了 number 的編輯類型):

export const opacityEditor = {
    field: 'style.opacity',
    label: '透明度',
    editor: 'number',
    number: {
        units: [{
            key: '',
            value: '%'
        }],
        currentUnit: '',
        max: 100,
        min: 0,
        step: 1,
        inputRange: [0, 100],
        outputRange: [0, 1],
        slider: true
    },
    editable: true
}

使用時我們直接放入 gaeaEdit 數(shù)組中:

gaeaEdit = [
    opacityEditor
]

其中 utils 表示數(shù)字類型框可選的單位虫几,inputRange outputRange 如上設(shè)置锤灿,那么編輯器中輸入框填入80,實(shí)際會轉(zhuǎn)換成 0.8 賦值到 opacity 屬性上辆脸。

因?yàn)橥ㄓ脤傩允枪潭ǖ牡#晕覀兲峁┝?gaeaHelper ,提供許多常用編輯類型:

import gaeaHelper from 'gaea-helper'

export class PropsGaea {
    gaeaName = '圖標(biāo)'
    gaeaIcon = 'square-o'
    gaeaUniqueKey = 'wefan-icon'
    gaeaEdit = [
        '圖標(biāo)',
        {
            field: null as string,
            label: '',
            editor: 'instance',
            editable: true,
            instance: instances
        },
        '布局',
        gaeaHelper.marginPaddingEditor,
        gaeaHelper.widthHeightEditor,
        '特效',
        gaeaHelper.opacityEditor
    ]
}

最后我們寫自定義的 props 類集成描述編輯狀態(tài)的 PropsGaea

export class Props extends PropsGaea {
    name = '名稱'
}

將其實(shí)例化后賦值在 defaultProps 即可:

static defaultProps = new Props()

自定義屬性編輯

值得尋味的是啡氢,通用屬性看起來其實(shí)更像定制屬性状囱,而自定義屬性其實(shí)更需要通用設(shè)計。

許多時候編輯器需要修改的屬性都是某些字段倘是,而這些字段都其對應(yīng)的類型和通用編輯規(guī)則亭枷,所以我們提供了基礎(chǔ)的 text number selector switch array object 等通用編輯類型,并且通過額外配置來適配簡單需求搀崭。

比如 number 類型的編輯配置:

{
    field: 'style.opacity',
    label: '透明度',
    editor: 'number',
    number: {
        units: [{
            key: '',
            value: '%'
        }],
        currentUnit: '',
        max: 100,
        min: 0,
        step: 1,
        inputRange: [0, 100],
        outputRange: [0, 1],
        slider: true
    }
}

field 屬性支持 . 的方式訪問深層對象叨粘,比如 style 屬性的 opacity 字段就是這次要修改的字段。number 類型的編輯類型瘤睹,通過 number 字段描述其詳細(xì)設(shè)置升敲。比如最大最小值、單位默蚌、輸出轉(zhuǎn)換冻晤、按鈕調(diào)解速度、步長绸吸、是否擁有 Slider 做滑動調(diào)節(jié)。

自定義與通用屬性混合編輯

編輯器混合了通用屬性與自定義屬性设江,完全通過 gaeaEditor 這個字段來描述:

gaeaEdit = [
    '圖標(biāo)',
    {
        field: null as string,
        label: '',
        editor: 'instance',
        editable: true,
        instance: instances
    },
    '布局',
    gaeaHelper.marginPaddingEditor,
    gaeaHelper.widthHeightEditor,
    '特效',
    gaeaHelper.opacityEditor
]

只要將兩者混合寫入數(shù)組即可锦茁,同時如果傳入的是字符串,會作為標(biāo)題分割叉存,方便區(qū)分功能區(qū)域码俩。

記錄編輯歷史

本來支持 undo redo 快捷鍵是個邊角功能,但是由于需要編輯區(qū)的支持歼捏,所以也放在這一節(jié)說稿存。

Undo Redo

就像編輯 word 一樣笨篷,我們需要記錄每一次用戶操作,以便回退或者重做瓣履,記錄歷史有以下三種方案:

每次操作記錄全量編輯 json率翅,撤銷的時候刷新整體視圖區(qū)域

這種方式太原始了,雖然操作方便不容易出錯袖迎,但弊端也非常明顯冕臭,就是占用內(nèi)存過大,每次記錄了全量數(shù)據(jù)肯定不是一件好事燕锥。

每次操作記錄增量編輯 json, 撤銷的時候根據(jù)每一步驟做 merge 辜贵,再刷新整體視圖區(qū)域

這種方式改進(jìn)了一下內(nèi)存占用,但缺點(diǎn)是刷新整體視圖區(qū)域的操作太笨重归形,如果視圖區(qū)域有 1000 個組件實(shí)例托慨,全量刷新就是一件很痛苦的事,我們操作時明明是局部刷新暇榴,為什么回退歷史要全量呢厚棵?

記錄每一步的操作類型、操作數(shù)據(jù)跺撼,回退時根據(jù)操作類型模擬人工操作

一個好的系統(tǒng)架構(gòu)窟感,是會將 action store 分離出來的,我們手動拖拽歉井、編輯組件的時候柿祈,都會觸發(fā)對應(yīng) action,進(jìn)而修改 store哩至,自動觸發(fā)視圖區(qū)域刷新(利用了mobx)躏嚎,在回退歷史記錄的時候,我們只需要逆向調(diào)用對應(yīng)的 action 就能夠模擬出高性能人工操作菩貌,付出的代價是需要記錄不同操作類型卢佣,并記錄不同的數(shù)據(jù)格式。

分類記錄操作歷史

值得記錄的操作種類有 添加 移動 刪除 排序 更新組件屬性 粘貼 等箭阶,我們的 editor 還有 屬性重置 新增模板 這兩種操作屬性虚茶,下面是對這幾種操作類型的描述:

export interface Diff {
    // 操作類型
    type: 'add' | 'move' | 'remove' | 'exchange' | 'update' | 'paste' | 'reset' | 'addCombo' | 'addSource'
    // 操作組件的 mapUniqueKey
    mapUniqueKey: string
    // 新增操作
    add?: {
        // 新增組件的唯一標(biāo)識 id
        uniqueId: string
        // 父級 mapKey
        parentMapUniqueKey: string
        // 插入的位置
        index: number
    }
    // 移動到另一個父元素
    move?: {
        // 移動到的父級 mapKey
        targetParentMapUniqueKey: string
        // 移動前父級 mapKey
        sourceParentMapUniqueKey: string
        // 插入的位置
        targetIndex: number
        // 移除的位置
        sourceIndex: number
    }
    // 刪除組件
    remove?: DiffRemove
    // 內(nèi)部交換順序
    exchange?: {
        oldIndex: number
        newIndex: number
    }
    // 更新操作
    update?: {
        oldValue: ComponentProps
        newValue: ComponentProps
    }
    // 粘貼操作
    paste?: DiffRemove
    // 重置組件
    reset?: {
        // 重置前的信息
        beforeProps: ComponentProps
        beforeName: string
    }
    // 新增組合
    addCombo?: {
        // 父級 mapKey
        parentMapUniqueKey: string
        // 父級的 index
        index: number
        // 組合的完整信息(不是 copy 的, 是真正對應(yīng)的 mapUniqueKey)
        componentInfo: ViewportComponentFullInfo
    }
    // 新增模板
    addSource?: {
        // 父級 mapKey
        parentMapUniqueKey: string
        // 父級的 index
        index: number
        // 組合的完整信息(不是 copy 的, 是真正對應(yīng)的 mapUniqueKey)
        componentInfo: ViewportComponentFullInfo
    }
}

在 undo,redo時仇参,根據(jù)不同編輯類型還原操作嘹叫,就可以高效模擬操作了。

https://github.com/ascoders/gaea-editor/blob/master/gaea-editor/store/viewport.tsx#L768

上述倉庫地址中可以看到每一步歷史只存了還原它需要的最小字段诈乒,因此大大降低了內(nèi)存占用罩扇。順帶一提,因?yàn)槭褂昧?Mobx 打平 map 存儲視圖中的所有組件怕磨,因此每個組件都會保存對應(yīng) mapUniqueKey 來找到對應(yīng)實(shí)例喂饥。

undo redo 操作效果如圖所示:

undo-redo.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末消约,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子员帮,更是在濱河造成了極大的恐慌或粮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件集侯,死亡現(xiàn)場離奇詭異被啼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棠枉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門浓体,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辈讶,你說我怎么就攤上這事命浴。” “怎么了贱除?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵生闲,是天一觀的道長。 經(jīng)常有香客問我月幌,道長碍讯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任扯躺,我火速辦了婚禮捉兴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘录语。我一直安慰自己倍啥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布澎埠。 她就那樣靜靜地躺著虽缕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒲稳。 梳的紋絲不亂的頭發(fā)上氮趋,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音江耀,去河邊找鬼凭峡。 笑死,一個胖子當(dāng)著我的面吹牛决记,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倍踪,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼系宫,長吁一口氣:“原來是場噩夢啊……” “哼索昂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扩借,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤椒惨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后潮罪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體康谆,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年嫉到,在試婚紗的時候發(fā)現(xiàn)自己被綠了沃暗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡何恶,死狀恐怖孽锥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情细层,我是刑警寧澤惜辑,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站疫赎,受9級特大地震影響盛撑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捧搞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一抵卫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧实牡,春花似錦陌僵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至题涨,卻和暖如春偎谁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纲堵。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工巡雨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人席函。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓铐望,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子正蛙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理督弓,服務(wù)發(fā)現(xiàn),斷路器乒验,智...
    卡卡羅2017閱讀 134,661評論 18 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,986評論 6 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法愚隧,類相關(guān)的語法,內(nèi)部類的語法锻全,繼承相關(guān)的語法狂塘,異常的語法,線程的語...
    子非魚_t_閱讀 31,639評論 18 399
  • 今天和老妹一起出去逛街硝训,她說大洋百貨做活動充1000有2300,特別劃算新思。 于是我們背上大包窖梁,抱上一一,逛街去了夹囚!...
    葉聽雨閱讀 2,016評論 0 1
  • 文/老葫蘆 (2017年3月25日) 何時對你如此依賴 記不清那個流年 絲絲一吸的醉 放飛在每日的夜晚 風(fēng)吹得不是...
    老葫蘆閱讀 536評論 0 1