-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathview.ts
205 lines (180 loc) · 6.33 KB
/
view.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import Viewport from '../viewports/viewport';
import {parsePosition, getPosition, Position} from '../utils/positions';
import {deepEqual} from '../utils/deep-equal';
import type Controller from '../controllers/controller';
import type {ControllerOptions} from '../controllers/controller';
import type {TransitionProps} from '../controllers/transition-manager';
import type {Padding} from '../viewports/viewport';
import type {ConstructorOf} from '../types/types';
export type CommonViewState = TransitionProps;
export type CommonViewProps<ViewState> = {
/** A unique id of the view. In a multi-view use case, this is important for matching view states and place contents into this view. */
id?: string;
/** A relative (e.g. `'50%'`) or absolute position. Default `0`. */
x?: number | string;
/** A relative (e.g. `'50%'`) or absolute position. Default `0`. */
y?: number | string;
/** A relative (e.g. `'50%'`) or absolute extent. Default `'100%'`. */
width?: number | string;
/** A relative (e.g. `'50%'`) or absolute extent. Default `'100%'`. */
height?: number | string;
/** Padding around the view, expressed in either relative (e.g. `'50%'`) or absolute pixels. Default `null`. */
padding?: {
left?: number | string;
right?: number | string;
top?: number | string;
bottom?: number | string;
} | null;
/** When using multiple views, set this flag to wipe the pixels drawn by other overlaping views */
clear?: boolean;
/** State of the view */
viewState?:
| string
| ({
id?: string;
} & Partial<ViewState>);
/** Options for viewport interactivity. */
controller?:
| null
| boolean
| ConstructorOf<Controller<any>>
| (ControllerOptions & {
type?: ConstructorOf<Controller<any>>;
});
};
export default abstract class View<
ViewState extends CommonViewState = CommonViewState,
ViewProps extends CommonViewProps<ViewState> = CommonViewProps<ViewState>
> {
id: string;
abstract getViewportType(viewState: ViewState): ConstructorOf<Viewport>;
protected abstract get ControllerType(): ConstructorOf<Controller<any>>;
private _x: Position;
private _y: Position;
private _width: Position;
private _height: Position;
private _padding: {
left: Position;
right: Position;
top: Position;
bottom: Position;
} | null;
readonly props: ViewProps;
constructor(props: ViewProps) {
const {id, x = 0, y = 0, width = '100%', height = '100%', padding = null} = props;
// @ts-ignore
this.id = id || this.constructor.displayName || 'view';
this.props = {...props, id: this.id};
// Extents
this._x = parsePosition(x);
this._y = parsePosition(y);
this._width = parsePosition(width);
this._height = parsePosition(height);
this._padding = padding && {
left: parsePosition(padding.left || 0),
right: parsePosition(padding.right || 0),
top: parsePosition(padding.top || 0),
bottom: parsePosition(padding.bottom || 0)
};
// Bind methods for easy access
this.equals = this.equals.bind(this);
Object.seal(this);
}
equals(view: this): boolean {
if (this === view) {
return true;
}
// To correctly compare padding use depth=2
return this.constructor === view.constructor && deepEqual(this.props, view.props, 2);
}
/** Clone this view with modified props */
clone(newProps: Partial<ViewProps>): this {
const ViewConstructor = this.constructor as new (props: ViewProps) => this;
return new ViewConstructor({...this.props, ...newProps});
}
/** Make viewport from canvas dimensions and view state */
makeViewport({width, height, viewState}: {width: number; height: number; viewState: ViewState}) {
viewState = this.filterViewState(viewState);
// Resolve relative viewport dimensions
const viewportDimensions = this.getDimensions({width, height});
if (!viewportDimensions.height || !viewportDimensions.width) {
return null;
}
const ViewportType = this.getViewportType(viewState);
return new ViewportType({...viewState, ...this.props, ...viewportDimensions});
}
getViewStateId(): string {
const {viewState} = this.props;
if (typeof viewState === 'string') {
// if View.viewState is a string, return it
return viewState;
}
return viewState?.id || this.id;
}
// Allows view to override (or completely define) viewState
filterViewState(viewState: ViewState): ViewState {
if (this.props.viewState && typeof this.props.viewState === 'object') {
// If we have specified an id, then intent is to override,
// If not, completely specify the view state
if (!this.props.viewState.id) {
return this.props.viewState as ViewState;
}
// Merge in all props from View's viewState, except id
const newViewState = {...viewState};
for (const key in this.props.viewState) {
if (key !== 'id') {
newViewState[key] = this.props.viewState[key];
}
}
return newViewState;
}
return viewState;
}
/** Resolve the dimensions of the view from overall canvas dimensions */
getDimensions({width, height}: {width: number; height: number}): {
x: number;
y: number;
width: number;
height: number;
padding?: Padding;
} {
const dimensions: {
x: number;
y: number;
width: number;
height: number;
padding?: Padding;
} = {
x: getPosition(this._x, width),
y: getPosition(this._y, height),
width: getPosition(this._width, width),
height: getPosition(this._height, height)
};
if (this._padding) {
dimensions.padding = {
left: getPosition(this._padding.left, width),
top: getPosition(this._padding.top, height),
right: getPosition(this._padding.right, width),
bottom: getPosition(this._padding.bottom, height)
};
}
return dimensions;
}
// Used by sub classes to resolve controller props
get controller(): (ControllerOptions & {type: ConstructorOf<Controller<any>>}) | null {
const opts = this.props.controller;
if (!opts) {
return null;
}
if (opts === true) {
return {type: this.ControllerType};
}
if (typeof opts === 'function') {
return {type: opts};
}
return {type: this.ControllerType, ...opts};
}
}