forked from owid/owid-grapher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAbstractChartEditor.ts
148 lines (127 loc) · 4.78 KB
/
AbstractChartEditor.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
import {
isEqual,
omit,
GrapherInterface,
diffGrapherConfigs,
mergeGrapherConfigs,
} from "@ourworldindata/utils"
import { action, computed, observable, when } from "mobx"
import { EditorFeatures } from "./EditorFeatures.js"
import { Admin } from "./Admin.js"
import { defaultGrapherConfig, Grapher } from "@ourworldindata/grapher"
export type EditorTab =
| "basic"
| "data"
| "text"
| "customize"
| "map"
| "scatter"
| "marimekko"
| "revisions"
| "refs"
| "export"
| "inheritance"
| "debug"
export interface AbstractChartEditorManager {
admin: Admin
patchConfig: GrapherInterface
parentConfig?: GrapherInterface
isInheritanceEnabled?: boolean
}
export abstract class AbstractChartEditor<
Manager extends AbstractChartEditorManager = AbstractChartEditorManager,
> {
manager: Manager
@observable.ref grapher = new Grapher()
@observable.ref currentRequest: Promise<any> | undefined // Whether the current chart state is saved or not
@observable.ref tab: EditorTab = "basic"
@observable.ref errorMessage?: { title: string; content: string }
@observable.ref previewMode: "mobile" | "desktop"
@observable.ref showStaticPreview = false
@observable.ref savedPatchConfig: GrapherInterface = {}
// parent config derived from the current chart config
@observable.ref parentConfig: GrapherInterface | undefined = undefined
// if inheritance is enabled, the parent config is applied to grapher
@observable.ref isInheritanceEnabled: boolean | undefined = undefined
constructor(props: { manager: Manager }) {
this.manager = props.manager
this.previewMode =
localStorage.getItem("editorPreviewMode") === "mobile"
? "mobile"
: "desktop"
when(
() => this.manager.parentConfig !== undefined,
() => (this.parentConfig = this.manager.parentConfig)
)
when(
() => this.manager.isInheritanceEnabled !== undefined,
() =>
(this.isInheritanceEnabled = this.manager.isInheritanceEnabled)
)
when(
() => this.grapher.hasData && this.grapher.isReady,
() => (this.savedPatchConfig = this.patchConfig)
)
}
/** original grapher config used to init the grapher instance */
@computed get originalGrapherConfig(): GrapherInterface {
const { patchConfig, parentConfig, isInheritanceEnabled } = this.manager
if (!isInheritanceEnabled) return patchConfig
return mergeGrapherConfigs(parentConfig ?? {}, patchConfig)
}
/** live-updating full config */
@computed get fullConfig(): GrapherInterface {
return mergeGrapherConfigs(defaultGrapherConfig, this.grapher.object)
}
/** parent config currently applied to grapher */
@computed get activeParentConfig(): GrapherInterface | undefined {
return this.isInheritanceEnabled ? this.parentConfig : undefined
}
@computed get activeParentConfigWithDefaults():
| GrapherInterface
| undefined {
if (!this.activeParentConfig) return undefined
return mergeGrapherConfigs(
defaultGrapherConfig,
this.activeParentConfig
)
}
/** patch config of the chart that is written to the db on save */
@computed get patchConfig(): GrapherInterface {
return diffGrapherConfigs(
this.fullConfig,
this.activeParentConfigWithDefaults ?? defaultGrapherConfig
)
}
@computed get isModified(): boolean {
return !isEqual(
omit(this.patchConfig, "version"),
omit(this.savedPatchConfig, "version")
)
}
@computed get features(): EditorFeatures {
return new EditorFeatures(this)
}
@action.bound updateLiveGrapher(config: GrapherInterface): void {
this.grapher.reset()
this.grapher.updateFromObject(config)
this.grapher.updateAuthoredVersion(config)
}
// only works for top-level properties
isPropertyInherited(property: keyof GrapherInterface): boolean {
if (!this.isInheritanceEnabled || !this.activeParentConfigWithDefaults)
return false
return (
!Object.hasOwn(this.patchConfig, property) &&
Object.hasOwn(this.activeParentConfigWithDefaults, property)
)
}
// only works for top-level properties
couldPropertyBeInherited(property: keyof GrapherInterface): boolean {
if (!this.isInheritanceEnabled || !this.activeParentConfig) return false
return Object.hasOwn(this.activeParentConfig, property)
}
abstract get isNewGrapher(): boolean
abstract get availableTabs(): EditorTab[]
abstract saveGrapher(props?: { onError?: () => void }): Promise<void>
}