@things-factory/kpi 9.0.29 → 9.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/google-map/common-google-map.ts +332 -0
- package/client/google-map/google-map-loader.ts +29 -0
- package/client/google-map/script-loader.ts +173 -0
- package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +248 -0
- package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +369 -0
- package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +443 -0
- package/client/pages/kpi-dashboard/components/kpi-chart-toggle.ts +73 -0
- package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +222 -0
- package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +786 -0
- package/client/pages/kpi-dashboard/kpi-dashboard.ts +416 -0
- package/client/route.ts +4 -0
- package/dist-client/google-map/common-google-map.d.ts +34 -0
- package/dist-client/google-map/common-google-map.js +300 -0
- package/dist-client/google-map/common-google-map.js.map +1 -0
- package/dist-client/google-map/google-map-loader.d.ts +6 -0
- package/dist-client/google-map/google-map-loader.js +22 -0
- package/dist-client/google-map/google-map-loader.js.map +1 -0
- package/dist-client/google-map/script-loader.d.ts +3 -0
- package/dist-client/google-map/script-loader.js +144 -0
- package/dist-client/google-map/script-loader.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +17 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +279 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +19 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +385 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +23 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +465 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.d.ts +8 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js +79 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-chart-toggle.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +23 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +223 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +38 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +813 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +21 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +398 -0
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/index.d.ts +1 -0
- package/dist-server/migrations/index.js +12 -0
- package/dist-server/migrations/index.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/index.ts +1 -0
- package/server/migrations/index.ts +9 -0
- package/things-factory.config.js +2 -1
- package/translations/en.json +1 -0
- package/translations/ja.json +1 -0
- package/translations/ko.json +1 -0
- package/translations/ms.json +1 -0
- package/translations/zh.json +1 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit'
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js'
|
|
3
|
+
import { ScrollbarStyles } from '@operato/styles'
|
|
4
|
+
|
|
5
|
+
import GoogleMapLoader from './google-map-loader.js'
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
interface Window {
|
|
9
|
+
google: any
|
|
10
|
+
markerClusterer?: any
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare const google: any
|
|
15
|
+
|
|
16
|
+
declare namespace google.maps {
|
|
17
|
+
interface MapsLibrary {
|
|
18
|
+
Map: any
|
|
19
|
+
InfoWindow: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface MarkerLibrary {
|
|
23
|
+
AdvancedMarkerElement: any
|
|
24
|
+
PinElement: any
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@customElement('common-google-map')
|
|
29
|
+
export class CommonGoogleMap extends LitElement {
|
|
30
|
+
static styles = [
|
|
31
|
+
ScrollbarStyles,
|
|
32
|
+
css`
|
|
33
|
+
:host {
|
|
34
|
+
display: flex;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
[map] {
|
|
38
|
+
flex: 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.gm-style .gm-style-iw-c {
|
|
42
|
+
padding: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.gm-style .gm-style-iw-d {
|
|
46
|
+
overflow: auto !important;
|
|
47
|
+
}
|
|
48
|
+
.gm-style .gm-style-iw-d + button {
|
|
49
|
+
top: 0 !important;
|
|
50
|
+
right: 0 !important;
|
|
51
|
+
}
|
|
52
|
+
`
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
@property({ type: Object }) center
|
|
56
|
+
@property({ type: Number }) zoom
|
|
57
|
+
@property({ type: Array }) locations: any[] = []
|
|
58
|
+
@property({ type: Object }) focused
|
|
59
|
+
@property({ type: Array }) polygons
|
|
60
|
+
@property({ type: Array }) polylines
|
|
61
|
+
@property({ type: Array }) markers
|
|
62
|
+
@property({ type: Array }) boundCoords
|
|
63
|
+
@property({ type: Object }) controls
|
|
64
|
+
@property({ type: Number }) clusterZoom = 10
|
|
65
|
+
|
|
66
|
+
@state() map: any = null
|
|
67
|
+
@state() defaultCenter: any = { lat: 36.5, lng: 127.5 }
|
|
68
|
+
private _infoWindow: any = null
|
|
69
|
+
private _markerClusterer: any = null
|
|
70
|
+
|
|
71
|
+
get anchor() {
|
|
72
|
+
return this.renderRoot.querySelector('[map]')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async readyMap() {
|
|
76
|
+
await GoogleMapLoader.load()
|
|
77
|
+
|
|
78
|
+
// MarkerClusterer 라이브러리 로드
|
|
79
|
+
if (!window.markerClusterer) {
|
|
80
|
+
await GoogleMapLoader.loadMarkerClusterer()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this.map) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// DOM이 준비될 때까지 기다림
|
|
88
|
+
await this.updateComplete
|
|
89
|
+
|
|
90
|
+
// anchor가 준비될 때까지 기다림
|
|
91
|
+
let attempts = 0
|
|
92
|
+
const maxAttempts = 20
|
|
93
|
+
while (attempts < maxAttempts) {
|
|
94
|
+
const anchor = this.anchor as HTMLElement
|
|
95
|
+
if (anchor && anchor.offsetWidth > 0) {
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, 50))
|
|
99
|
+
attempts++
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!this.anchor) {
|
|
103
|
+
console.error('Map anchor element not found')
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var show = async (center, zoom) => {
|
|
108
|
+
try {
|
|
109
|
+
// Google Maps 최신 API 사용
|
|
110
|
+
const { Map } = (await google.maps.importLibrary('maps')) as google.maps.MapsLibrary
|
|
111
|
+
|
|
112
|
+
const map = new Map(this.anchor, {
|
|
113
|
+
zoom,
|
|
114
|
+
center,
|
|
115
|
+
mapId: 'DEMO_MAP_ID'
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
this.markers && this.markers.forEach(marker => marker.setMap(map))
|
|
119
|
+
|
|
120
|
+
this.map = map
|
|
121
|
+
|
|
122
|
+
this.dispatchEvent(
|
|
123
|
+
new CustomEvent('map-change', {
|
|
124
|
+
detail: this.map
|
|
125
|
+
})
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
this.resetBounds()
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error(e)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
var { center, zoom = 10 } = this
|
|
135
|
+
|
|
136
|
+
/* center 속성이 설정되어있지 않으면, 현재 위치를 구해서 지도의 center로 설정한다. */
|
|
137
|
+
if (!center && 'geolocation' in navigator && !this.boundCoords?.length) {
|
|
138
|
+
navigator.geolocation.getCurrentPosition(
|
|
139
|
+
({ coords: { latitude: lat, longitude: lng } }) => show({ lat, lng }, zoom),
|
|
140
|
+
err => {
|
|
141
|
+
console.warn(`navigator.geolocation.getCurrentPosition failed. (${err.code}): ${err.message}`)
|
|
142
|
+
show(this.defaultCenter, zoom)
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
/* https://stackoverflow.com/questions/3397585/navigator-geolocation-getcurrentposition-sometimes-works-sometimes-doesnt */
|
|
146
|
+
timeout: 500
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
} else {
|
|
150
|
+
show(center, zoom)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async buildMarkers(locations: any[] = []) {
|
|
155
|
+
if (!this.map) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (this.markers) {
|
|
160
|
+
this.markers.forEach(marker => marker.setMap(null))
|
|
161
|
+
this.markers = []
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 기존 클러스터 제거
|
|
165
|
+
if (this._markerClusterer) {
|
|
166
|
+
this._markerClusterer.clearMarkers()
|
|
167
|
+
this._markerClusterer = null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Google Maps 최신 API 사용
|
|
171
|
+
const { AdvancedMarkerElement, PinElement } = (await google.maps.importLibrary(
|
|
172
|
+
'marker'
|
|
173
|
+
)) as google.maps.MarkerLibrary
|
|
174
|
+
|
|
175
|
+
this.markers = locations
|
|
176
|
+
.map(location => {
|
|
177
|
+
// location 객체가 유효한지 확인
|
|
178
|
+
if (!location || typeof location !== 'object') {
|
|
179
|
+
console.warn('Invalid location object:', location)
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// lat, lng 값이 유효한지 확인
|
|
184
|
+
const lat = parseFloat(location.lat)
|
|
185
|
+
const lng = parseFloat(location.lng)
|
|
186
|
+
|
|
187
|
+
if (isNaN(lat) || isNaN(lng)) {
|
|
188
|
+
console.warn('Invalid lat/lng values:', location)
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// LatLng 객체 생성
|
|
193
|
+
const position = new google.maps.LatLng(lat, lng)
|
|
194
|
+
|
|
195
|
+
// AdvancedMarkerElement 사용
|
|
196
|
+
const marker = new AdvancedMarkerElement({
|
|
197
|
+
position: position,
|
|
198
|
+
map: null // 클러스터에서 관리하므로 지도에 직접 추가하지 않음
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
marker.addListener('click', () => {
|
|
202
|
+
if (location?.content) {
|
|
203
|
+
var infowindow = this.infoWindow
|
|
204
|
+
infowindow.open(this.map, marker)
|
|
205
|
+
infowindow.setContent(location.content)
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
return marker
|
|
210
|
+
})
|
|
211
|
+
.filter(marker => marker !== null) // null 마커 제거
|
|
212
|
+
|
|
213
|
+
// Google Maps 공식 MarkerClusterer 사용 (예시와 동일한 방식)
|
|
214
|
+
if (this.markers.length > 0 && window.markerClusterer) {
|
|
215
|
+
this._markerClusterer = new window.markerClusterer.MarkerClusterer({
|
|
216
|
+
markers: this.markers,
|
|
217
|
+
map: this.map
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
get infoWindow() {
|
|
223
|
+
if (!this._infoWindow && this.map) {
|
|
224
|
+
this._infoWindow = new google.maps.InfoWindow({
|
|
225
|
+
content: 'loading...'
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return this._infoWindow
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setFocus(focus, icon) {
|
|
233
|
+
focus.setZIndex(1)
|
|
234
|
+
focus.setIcon(icon)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
resetFocus(focus, icon) {
|
|
238
|
+
focus.setZIndex(0)
|
|
239
|
+
focus.setIcon(icon)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async changeFocus(after, before) {
|
|
243
|
+
await this.readyMap()
|
|
244
|
+
|
|
245
|
+
// map이 준비되지 않았으면 포커스 변경하지 않음
|
|
246
|
+
if (!this.map) {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
var locations = this.locations || []
|
|
251
|
+
|
|
252
|
+
if (before) {
|
|
253
|
+
var idx = locations.findIndex(location => {
|
|
254
|
+
// location 객체의 구조를 안전하게 확인
|
|
255
|
+
const beforePos = before?.position
|
|
256
|
+
const locationPos = location?.position
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
location?.name == before?.name && locationPos?.lat == beforePos?.lat && locationPos?.lng == beforePos?.lng
|
|
260
|
+
)
|
|
261
|
+
})
|
|
262
|
+
idx !== -1 && this.markers && this.resetFocus(this.markers[idx], locations[idx]?.icon)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (after) {
|
|
266
|
+
var idx = locations.findIndex(location => {
|
|
267
|
+
// location 객체의 구조를 안전하게 확인
|
|
268
|
+
const afterPos = after?.position
|
|
269
|
+
const locationPos = location?.position
|
|
270
|
+
|
|
271
|
+
return location?.name == after?.name && locationPos?.lat == afterPos?.lat && locationPos?.lng == afterPos?.lng
|
|
272
|
+
})
|
|
273
|
+
idx !== -1 && this.markers && this.setFocus(this.markers[idx], after?.icon)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async updated(changes) {
|
|
278
|
+
if (!this.map) {
|
|
279
|
+
await this.readyMap()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (changes.has('locations')) {
|
|
283
|
+
this.buildMarkers(this.locations)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (changes.has('focused')) {
|
|
287
|
+
this.changeFocus(this.focused, changes.get('focused'))
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (changes.has('center')) {
|
|
291
|
+
this.map.setCenter(this.center)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (changes.has('polygons')) {
|
|
295
|
+
;(changes.get('polygons') || []).forEach(geofence => geofence.setMap(null))
|
|
296
|
+
;(this.polygons || []).forEach(geofence => geofence.setMap(this.map))
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (changes.has('polylines')) {
|
|
300
|
+
;(changes.get('polylines') || []).forEach(polyline => polyline.setMap(null))
|
|
301
|
+
;(this.polylines || []).forEach(polyline => polyline.setMap(this.map))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (changes.has('markers')) {
|
|
305
|
+
;(changes.get('markers') || []).forEach(marker => marker.setMap(null))
|
|
306
|
+
;(this.markers || []).forEach(marker => marker.setMap(this.map))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (changes.has('boundCoords')) {
|
|
310
|
+
this.resetBounds()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 클러스터링 설정 변경 시 마커 재구성
|
|
314
|
+
if (changes.has('clusterZoom')) {
|
|
315
|
+
this.buildMarkers(this.locations)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
render() {
|
|
320
|
+
return html` <div map></div> `
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
resetBounds() {
|
|
324
|
+
if (!this.boundCoords || this.boundCoords.length < 1 || !this.map) {
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
var bounds = new google.maps.LatLngBounds()
|
|
329
|
+
this.boundCoords.forEach(coord => bounds.extend(coord))
|
|
330
|
+
this.map.fitBounds(bounds)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ScriptLoader from './script-loader.js'
|
|
2
|
+
|
|
3
|
+
export default class GoogleMapLoader {
|
|
4
|
+
static loaded = false
|
|
5
|
+
static markerClustererLoaded = false
|
|
6
|
+
|
|
7
|
+
static async load() {
|
|
8
|
+
if (GoogleMapLoader.loaded) {
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
var key = 'AIzaSyBgQZb-SFqjQBC_XTxNiz0XapejNwV9PgA'
|
|
12
|
+
|
|
13
|
+
await ScriptLoader.load(
|
|
14
|
+
'https://maps.googleapis.com/maps/api/js' + (key ? '?key=' + key : '') + '&libraries=places'
|
|
15
|
+
)
|
|
16
|
+
GoogleMapLoader.loaded = true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static async loadMarkerClusterer() {
|
|
20
|
+
if (GoogleMapLoader.markerClustererLoaded) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Google Maps 공식 MarkerClusterer 라이브러리 로드
|
|
25
|
+
await ScriptLoader.load('https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js')
|
|
26
|
+
|
|
27
|
+
GoogleMapLoader.markerClustererLoaded = true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 1. SCRIPTS, STYLES 에는 각 소스별 상태를 관리한다.
|
|
3
|
+
* SCRIPTS, STYLES : {
|
|
4
|
+
* 'http://echarts.baidu.com/dist/echarts.min.js': true | false (true means loaded, false means not loaded yet)
|
|
5
|
+
* }
|
|
6
|
+
*
|
|
7
|
+
* 2. PROMISES : 각 소스군별 pending resolver, reject 을 관리한다.
|
|
8
|
+
* PROMISES: [{
|
|
9
|
+
* resolve,
|
|
10
|
+
* reject,
|
|
11
|
+
* srcs: ['src1', 'src2']
|
|
12
|
+
* }]
|
|
13
|
+
*
|
|
14
|
+
* [특징]
|
|
15
|
+
* 1. CORS 제약에서 자유롭다.
|
|
16
|
+
* 2. 같은 스크립트를 반복해서 로드하지 않는다.
|
|
17
|
+
* 3. 로드에 실패한 스크립트도 다시 반복해서 로드를 시도할 수 있다.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// 타입 정의 추가
|
|
21
|
+
interface LoadPromise {
|
|
22
|
+
resolve: (value?: any) => void
|
|
23
|
+
reject: (error: any) => void
|
|
24
|
+
scripts: string[]
|
|
25
|
+
styles?: string[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// SystemJS를 활용하는 경우에는 다음과 같이 import 할 수 있다.
|
|
29
|
+
//
|
|
30
|
+
// import System from 'systemjs/dist/system-production'
|
|
31
|
+
|
|
32
|
+
var SCRIPTS: Record<string, boolean> = {}
|
|
33
|
+
var STYLES: Record<string, boolean> = {}
|
|
34
|
+
|
|
35
|
+
var PROMISES: (LoadPromise | null)[] = []
|
|
36
|
+
|
|
37
|
+
function onload(e) {
|
|
38
|
+
var types = e.target.tagName == 'SCRIPT' ? SCRIPTS : STYLES
|
|
39
|
+
var src = e.target.src || e.target.href
|
|
40
|
+
|
|
41
|
+
types[src] = true
|
|
42
|
+
|
|
43
|
+
PROMISES.forEach((ready, index) => {
|
|
44
|
+
if (!ready) return
|
|
45
|
+
|
|
46
|
+
let { resolve, scripts, styles } = ready
|
|
47
|
+
|
|
48
|
+
if (types == SCRIPTS) {
|
|
49
|
+
let idx = scripts.indexOf(src)
|
|
50
|
+
if (idx > -1 && idx < scripts.length - 1) request_script(scripts[idx + 1])
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < scripts.length; i++) if (!SCRIPTS[scripts[i]]) return
|
|
54
|
+
|
|
55
|
+
if (styles) {
|
|
56
|
+
for (let i = 0; i < styles.length; i++) if (!STYLES[styles[i]]) return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
resolve()
|
|
60
|
+
|
|
61
|
+
PROMISES[index] = null
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
PROMISES = PROMISES.filter(x => x)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function onerror(e) {
|
|
68
|
+
var src = e.target.src
|
|
69
|
+
var types = e.target.tagName == 'SCRIPT' ? SCRIPTS : STYLES
|
|
70
|
+
|
|
71
|
+
PROMISES.forEach((ready, index) => {
|
|
72
|
+
if (!ready) return
|
|
73
|
+
|
|
74
|
+
let { reject, scripts, styles } = ready
|
|
75
|
+
|
|
76
|
+
let done = false
|
|
77
|
+
|
|
78
|
+
if (types === SCRIPTS) {
|
|
79
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
80
|
+
if (scripts[i] == src) {
|
|
81
|
+
done = true
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} else if (styles) {
|
|
86
|
+
for (let i = 0; i < styles.length; i++) {
|
|
87
|
+
if (styles[i] == src) {
|
|
88
|
+
done = true
|
|
89
|
+
break
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (done) {
|
|
95
|
+
reject(e)
|
|
96
|
+
|
|
97
|
+
PROMISES[index] = null
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
PROMISES = PROMISES.filter(x => x)
|
|
102
|
+
|
|
103
|
+
delete types[src]
|
|
104
|
+
document.head.removeChild(e.target)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function request_script(src) {
|
|
108
|
+
SCRIPTS[src] = false
|
|
109
|
+
|
|
110
|
+
var script = document.createElement('script')
|
|
111
|
+
script.onload = onload
|
|
112
|
+
script.onerror = onerror
|
|
113
|
+
|
|
114
|
+
script.type = 'text/javascript'
|
|
115
|
+
script.src = src
|
|
116
|
+
document.head.appendChild(script)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function request_style(src) {
|
|
120
|
+
STYLES[src] = false
|
|
121
|
+
|
|
122
|
+
var link = document.createElement('link')
|
|
123
|
+
link.onload = onload
|
|
124
|
+
link.onerror = onerror
|
|
125
|
+
|
|
126
|
+
link.type = 'text/css'
|
|
127
|
+
link.rel = 'stylesheet'
|
|
128
|
+
link.media = 'screen,print'
|
|
129
|
+
link.href = src
|
|
130
|
+
|
|
131
|
+
document.head.appendChild(link)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default class ScriptLoader {
|
|
135
|
+
static load(scripts: string | string[], styles?: string | string[]) {
|
|
136
|
+
if (typeof scripts == 'string') scripts = [scripts]
|
|
137
|
+
if (typeof styles == 'string') styles = [styles]
|
|
138
|
+
|
|
139
|
+
return new Promise<void>(function (resolve, reject) {
|
|
140
|
+
if ((scripts && !(scripts instanceof Array)) || (styles && !(styles instanceof Array))) {
|
|
141
|
+
reject('invalid sources for load')
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* check state of each src */
|
|
146
|
+
var done = true
|
|
147
|
+
|
|
148
|
+
// style first
|
|
149
|
+
styles &&
|
|
150
|
+
styles.forEach(src => {
|
|
151
|
+
if (!STYLES.hasOwnProperty(src)) request_style(src)
|
|
152
|
+
|
|
153
|
+
if (!STYLES[src]) done = false
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
var first
|
|
157
|
+
if (scripts && scripts.length > 0) {
|
|
158
|
+
scripts.forEach(src => {
|
|
159
|
+
if (!SCRIPTS.hasOwnProperty(src)) first = first || src
|
|
160
|
+
else if (!SCRIPTS[src]) done = false
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (first) request_script(first)
|
|
165
|
+
else if (done) {
|
|
166
|
+
resolve()
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
PROMISES.push({ resolve, reject, scripts, styles })
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
}
|