/
editor.ts
154 lines (139 loc) · 4.22 KB
/
editor.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import {DataStorage, LocalDataStorage} from '../utility/dataStorage';
import {EditorConfig, GlobalConfig} from '@blinkk/selective-edit';
import {EditorState, StatePromiseKeys} from './state';
import {LiveEditorApiComponent, ProjectTypes} from './api';
import {AmagakiProjectType} from '../projectType/amagaki/amagakiProjectType';
import {AppUi} from './ui/app';
import {EVENT_SAVE} from './events';
import {GrowProjectType} from '../projectType/grow/growProjectType';
import {LiveEditorLabels} from './template';
import {PreviewCommunicator} from './preview';
import {ProjectTypeComponent} from '../projectType/projectType';
import cloneDeep from 'lodash.clonedeep';
/**
* Global configuration used by the selective editor fields.
*
* Allows the fields to access the api.
*/
export interface LiveEditorGlobalConfig extends GlobalConfig {
api: LiveEditorApiComponent;
editor?: LiveEditor;
labels: LiveEditorLabels;
state: EditorState;
}
/**
* Custom selective editor config.
*
* Customized to use the live editor global config interface.
*/
export interface LiveEditorSelectiveEditorConfig extends EditorConfig {
global?: LiveEditorGlobalConfig;
}
/**
* Configuration for the live editor.
*/
export interface LiveEditorConfig {
/**
* Api for working with the live editor project.
*/
api: LiveEditorApiComponent;
/**
* Custom UI labels for the editor UI.
*/
labels?: LiveEditorLabels;
/**
* Base configuration for the selective editor.
*/
selectiveConfig: LiveEditorSelectiveEditorConfig;
/**
* Editor state.
*/
state: EditorState;
/**
* Is the editor being used in a testing environment?
*
* For example: selenium or webdriver.
*/
isTest?: boolean;
}
export class LiveEditor {
ui: AppUi;
config: LiveEditorConfig;
isPendingRender?: boolean;
isRendering?: boolean;
/**
* Used to handle communiction between the editor and the preview iframe.
*/
preview: PreviewCommunicator;
state: EditorState;
storage: DataStorage;
constructor(config: LiveEditorConfig, container: HTMLElement) {
this.config = config;
this.storage = new LocalDataStorage();
this.state = this.config.state;
this.preview = new PreviewCommunicator();
// Bind the editor to the global selective config.
if (this.config.selectiveConfig.global) {
this.config.selectiveConfig.global.editor = this;
}
this.ui = new AppUi(
{
editor: this,
state: this.state,
storage: this.storage,
selectiveConfig: this.config.selectiveConfig,
},
container
);
// Update the project type when the project changes.
this.state.addListener(StatePromiseKeys.GetProject, () => {
if (this.state.project?.type === ProjectTypes.Grow) {
this.updateProjectType(new GrowProjectType());
} else if (this.state.project?.type === ProjectTypes.Amagaki) {
this.updateProjectType(new AmagakiProjectType());
}
// Pull in the UI labels.
if (this.state.project?.ui?.labels) {
this.config.labels = Object.assign(
{},
this.config.labels || {},
this.state.project?.ui?.labels
);
}
});
// Handle the global key bindings.
container.addEventListener('keydown', (evt: KeyboardEvent) => {
if (evt.ctrlKey || evt.metaKey) {
switch (evt.key) {
case 's':
evt.preventDefault();
document.dispatchEvent(new CustomEvent(EVENT_SAVE));
break;
}
}
});
}
get api(): LiveEditorApiComponent {
return this.config.api;
}
render() {
this.ui.render();
}
updateProjectType(projectType: ProjectTypeComponent) {
this.state.setProjectType(projectType);
}
}
/**
* When using the selective configuration for different forms is uses a global
* configuration, but the individual selective editors may try to change the
* config since it is not immutable, so we need to deep copy the configuration
* while preserving the references to objects.
*/
export function cloneSelectiveConfig(
config: LiveEditorSelectiveEditorConfig
): LiveEditorSelectiveEditorConfig {
// TODO: Clone without cloning global key?
const newConfig = cloneDeep(config);
newConfig.global = config.global;
return newConfig;
}