• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jumpinjackie / mapguide-react-layout / 15160437878

21 May 2025 11:00AM UTC coverage: 21.631% (-42.6%) from 64.24%
15160437878

Pull #1552

github

web-flow
Merge 8b7153d9e into 236e2ea07
Pull Request #1552: Feature/package updates 2505

839 of 1165 branches covered (72.02%)

11 of 151 new or added lines in 25 files covered. (7.28%)

1332 existing lines in 50 files now uncovered.

4794 of 22163 relevant lines covered (21.63%)

6.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

32.77
/src/actions/init-command.ts
1
import { IInitAsyncOptions, normalizeInitPayload } from './init';
1✔
2
import { ReduxDispatch, Dictionary, ActiveMapTool } from '../api/common';
1✔
3
import { IGenericSubjectMapLayer, IInitAppActionPayload, isGenericSubjectMapLayer, MapInfo } from './defs';
4
import { ToolbarConf, convertFlexLayoutUIItems, parseWidgetsInAppDef, prepareSubMenus } from '../api/registry/command-spec';
1✔
5
import { makeUnique } from '../utils/array';
1✔
6
import { ApplicationDefinition, MapConfiguration } from '../api/contracts/fusion';
7
import { warn, info } from '../utils/logger';
1✔
8
import { registerCommand } from '../api/registry/command';
1✔
9
import { tr, registerStringBundle, DEFAULT_LOCALE } from '../api/i18n';
1✔
10
import { WEBLAYOUT_CONTEXTMENU } from "../constants";
1✔
11
import { Client } from '../api/client';
12
import { ActionType } from '../constants/actions';
1✔
13
import { ensureParameters } from '../utils/url';
1✔
14
import { MgError } from '../api/error';
1✔
15
import { strStartsWith } from '../utils/string';
1✔
16
import { IClusterSettings } from '../api/ol-style-contracts';
17

18
const TYPE_SUBJECT = "SubjectLayer";
1✔
19
const TYPE_EXTERNAL = "External";
1✔
20

21
export type SessionInit = {
22
    session: string;
23
    sessionWasReused: boolean;
24
}
25

26
function getMapGuideConfiguration(appDef: ApplicationDefinition): [string, MapConfiguration][] {
17✔
27
    const configs = [] as [string, MapConfiguration][];
17✔
28
    if (appDef.MapSet) {
17✔
29
        for (const mg of appDef.MapSet.MapGroup) {
17✔
30
            for (const map of mg.Map) {
75✔
31
                if (map.Type == "MapGuide") {
111✔
32
                    configs.push([mg["@id"], map]);
75✔
33
                }
75✔
34
            }
111✔
35
        }
75✔
36
    }
17✔
37
    return configs;
17✔
38
}
17✔
39

40
function tryExtractMapMetadata(extension: any) {
75✔
41
    const ext: any = {};
75✔
42
    for (const k in extension) {
75✔
43
        if (strStartsWith(k, "Meta_")) {
189!
44
            const sk = k.substring("Meta_".length);
×
45
            ext[sk] = extension[k];
×
UNCOV
46
        }
×
47
    }
189✔
48
    return ext;
75✔
49
}
75✔
50

51
export function buildSubjectLayerDefn(name: string, map: MapConfiguration): IGenericSubjectMapLayer {
1✔
52
    const st = map.Extension.source_type;
×
53
    const initiallyVisible = map.Extension.initially_visible ?? true;
×
54
    const sp: any = {};
×
55
    const lo: any = {};
×
56
    const meta: any = {};
×
57
    const keys = Object.keys(map.Extension);
×
58
    let popupTemplate = map.Extension.popup_template;
×
59
    let selectable: boolean | undefined = map.Extension.is_selectable ?? true;
×
60
    let disableHover: boolean | undefined = map.Extension.disable_hover ?? false;
×
61
    for (const k of keys) {
×
62
        const spidx = k.indexOf("source_param_");
×
63
        const loidx = k.indexOf("layer_opt_");
×
64
        const midx = k.indexOf("meta_");
×
65
        if (spidx == 0) {
×
66
            const kn = k.substring("source_param_".length);
×
67
            sp[kn] = map.Extension[k];
×
68
        } else if (loidx == 0) {
×
69
            const kn = k.substring("layer_opt_".length);
×
70
            lo[kn] = map.Extension[k];
×
71
        } else if (midx == 0) {
×
72
            const kn = k.substring("meta_".length);
×
73
            meta[kn] = map.Extension[k];
×
UNCOV
74
        }
×
UNCOV
75
    }
×
76
    const sl = {
×
UNCOV
77
        name: name,
×
UNCOV
78
        description: map.Extension.layer_description,
×
UNCOV
79
        displayName: map.Extension.display_name,
×
UNCOV
80
        driverName: map.Extension.driver_name,
×
UNCOV
81
        type: st,
×
UNCOV
82
        layerOptions: lo,
×
UNCOV
83
        sourceParams: sp,
×
UNCOV
84
        meta: (Object.keys(meta).length > 0 ? meta : undefined),
×
UNCOV
85
        initiallyVisible,
×
UNCOV
86
        selectable,
×
UNCOV
87
        disableHover,
×
UNCOV
88
        popupTemplate,
×
UNCOV
89
        vectorStyle: map.Extension.vector_layer_style
×
UNCOV
90
    } as IGenericSubjectMapLayer;
×
91

92
    if (map.Extension.cluster) {
×
93
        sl.cluster = {
×
UNCOV
94
            ...map.Extension.cluster
×
UNCOV
95
        } as IClusterSettings;
×
UNCOV
96
    }
×
97
    return sl;
×
UNCOV
98
}
×
99

100
export function getMapDefinitionsFromFlexLayout(appDef: ApplicationDefinition): (MapToLoad | IGenericSubjectMapLayer)[] {
1✔
101
    const maps = [] as (MapToLoad | IGenericSubjectMapLayer)[];
17✔
102
    const configs = getMapGuideConfiguration(appDef);
17✔
103
    if (configs.length > 0) {
17✔
104
        for (const c of configs) {
17✔
105
            maps.push({ 
75✔
106
                name: c[0],
75✔
107
                mapDef: c[1].Extension.ResourceId,
75✔
108
                metadata: tryExtractMapMetadata(c[1].Extension)
75✔
109
            });
75✔
110
        }
75✔
111
    }
17✔
112
    if (appDef.MapSet?.MapGroup) {
17✔
113
        for (const mGroup of appDef.MapSet.MapGroup) {
17✔
114
            for (const map of mGroup.Map) {
75✔
115
                if (map.Type == TYPE_SUBJECT) {
111!
116
                    const name = mGroup["@id"];
×
117
                    maps.push(buildSubjectLayerDefn(name, map));
×
UNCOV
118
                }
×
119
            }
111✔
120
        }
75✔
121
    }
17✔
122
    if (maps.length == 0)
17✔
123
        throw new MgError("No Map Definition or subject layer found in Application Definition");
17!
124

125
    return maps;
17✔
126
}
17✔
127

128
export type MapToLoad = { name: string, mapDef: string, metadata: any };
129

130
export function isMapDefinition(arg: MapToLoad | IGenericSubjectMapLayer): arg is MapToLoad {
1✔
131
    return (arg as any).mapDef != null;
17✔
132
}
17✔
133

134
export function isStateless(appDef: ApplicationDefinition) {
1✔
135
    // This appdef is stateless if:
136
    //
137
    //  1. It has a Stateless extension property set to "true" (ie. The author has opted-in to this feature)
138
    //  2. No MapGuide Map Definitions were found in the appdef
139
    if (appDef.Extension?.Stateless == "true")
26✔
140
        return true;
26✔
141

142
    try {
17✔
143
        const maps = getMapDefinitionsFromFlexLayout(appDef);
17✔
144
        for (const m of maps) {
17✔
145
            if (isMapDefinition(m)) {
17✔
146
                return false;
17✔
147
            }
17✔
148
        }
17!
149
        return true;
×
UNCOV
150
    } catch (e) {
×
151
        return true;
×
UNCOV
152
    }
×
153
}
26✔
154

155
export interface IViewerInitCommand {
156
    attachClient(client: Client): void;
157
    runAsync(options: IInitAsyncOptions): Promise<IInitAppActionPayload>;
158
}
159

160
export abstract class ViewerInitCommand<TSubject> implements IViewerInitCommand {
1✔
161
    constructor(protected readonly dispatch: ReduxDispatch) { }
1✔
162
    public abstract attachClient(client: Client): void;
163
    public abstract runAsync(options: IInitAsyncOptions): Promise<IInitAppActionPayload>;
164
    protected abstract isArbitraryCoordSys(map: TSubject): boolean;
165
    protected abstract establishInitialMapNameAndSession(mapsByName: Dictionary<TSubject>): [string, string];
166
    protected abstract setupMaps(appDef: ApplicationDefinition, mapsByName: Dictionary<TSubject>, config: any, warnings: string[], locale: string): Dictionary<MapInfo>;
167
    protected async initLocaleAsync(options: IInitAsyncOptions): Promise<void> {
1✔
168
        //English strings are baked into this bundle. For non-en locales, we assume a strings/{locale}.json
169
        //exists for us to fetch
170
        const { locale } = options;
×
171
        if (locale != DEFAULT_LOCALE) {
×
172
            const r = await fetch(`strings/${locale}.json`);
×
173
            if (r.ok) {
×
174
                const res = await r.json();
×
175
                registerStringBundle(locale, res);
×
176
                // Dispatch the SET_LOCALE as it is safe to change UI strings at this point
177
                this.dispatch({
×
UNCOV
178
                    type: ActionType.SET_LOCALE,
×
UNCOV
179
                    payload: locale
×
UNCOV
180
                });
×
181
                info(`Registered string bundle for locale: ${locale}`);
×
UNCOV
182
            } else {
×
183
                //TODO: Push warning to init error/warning reducer when we implement it
184
                warn(`Failed to register string bundle for locale: ${locale}`);
×
UNCOV
185
            }
×
UNCOV
186
        }
×
UNCOV
187
    }
×
188
    protected getExtraProjectionsFromFlexLayout(appDef: ApplicationDefinition): string[] {
1✔
189
        //The only widget we care about is the coordinate tracker
190
        const epsgs: string[] = [];
×
191
        for (const ws of appDef.WidgetSet) {
×
192
            for (const w of ws.Widget) {
×
193
                if (w.Type == "CoordinateTracker") {
×
194
                    const ps = w.Extension.Projection || [];
×
195
                    for (const p of ps) {
×
196
                        epsgs.push(p.split(':')[1]);
×
UNCOV
197
                    }
×
198
                } else if (w.Type == "CursorPosition") {
×
199
                    const dp = w.Extension.DisplayProjection;
×
200
                    if (dp) {
×
201
                        epsgs.push(dp.split(':')[1]);
×
UNCOV
202
                    }
×
UNCOV
203
                }
×
UNCOV
204
            }
×
UNCOV
205
        }
×
206
        return makeUnique(epsgs);
×
UNCOV
207
    }
×
208
    
209
    protected async initFromAppDefCoreAsync(appDef: ApplicationDefinition, options: IInitAsyncOptions, mapsByName: Dictionary<TSubject | IGenericSubjectMapLayer>, warnings: string[]): Promise<IInitAppActionPayload> {
1✔
210
        const {
×
UNCOV
211
            taskPane,
×
UNCOV
212
            hasTaskBar,
×
UNCOV
213
            hasStatus,
×
UNCOV
214
            hasNavigator,
×
UNCOV
215
            hasSelectionPanel,
×
UNCOV
216
            hasLegend,
×
UNCOV
217
            viewSize,
×
UNCOV
218
            widgetsByKey,
×
UNCOV
219
            isStateless,
×
UNCOV
220
            initialTask
×
UNCOV
221
        } = parseWidgetsInAppDef(appDef, registerCommand);
×
222
        const { locale, featureTooltipsEnabled } = options;
×
223
        const config: any = {};
×
224
        config.isStateless = isStateless;
×
225
        const tbConf: Dictionary<ToolbarConf> = {};
×
226
        
227
        //Now build toolbar layouts
228
        for (const widgetSet of appDef.WidgetSet) {
×
229
            for (const cont of widgetSet.Container) {
×
230
                let tbName = cont.Name;
×
231
                tbConf[tbName] = { items: convertFlexLayoutUIItems(isStateless, cont.Item, widgetsByKey, locale) };
×
UNCOV
232
            }
×
233
            for (const w of widgetSet.Widget) {
×
234
                if (w.Type == "CursorPosition") {
×
235
                    config.coordinateProjection = w.Extension.DisplayProjection;
×
236
                    config.coordinateDecimals = w.Extension.Precision;
×
237
                    config.coordinateDisplayFormat = w.Extension.Template;
×
UNCOV
238
                }
×
UNCOV
239
            }
×
UNCOV
240
        }
×
241

242
        const mapsDict: any  = mapsByName; //HACK: TS generics doesn't want to play nice with us
×
243
        const maps = this.setupMaps(appDef, mapsDict, config, warnings, locale);
×
244
        if (appDef.Title) {
×
245
            document.title = appDef.Title || document.title;
×
UNCOV
246
        }
×
247
        const [firstMapName, firstSessionId] = this.establishInitialMapNameAndSession(mapsDict);
×
248
        const [tb, bFoundContextMenu] = prepareSubMenus(tbConf);
×
249
        if (!bFoundContextMenu) {
×
250
            warnings.push(tr("INIT_WARNING_NO_CONTEXT_MENU", locale, { containerName: WEBLAYOUT_CONTEXTMENU }));
×
UNCOV
251
        }
×
252
        const settings: Record<string, string> = {};
×
253
        if (Array.isArray(appDef.Extension?.ViewerSettings?.Setting)) {
×
254
            for (const s of appDef.Extension.ViewerSettings.Setting) {
×
255
                const [sn] = s["@name"];
×
256
                const [sv] = s["@value"];
×
257
                settings[sn] = sv;
×
UNCOV
258
            }
×
UNCOV
259
        }
×
260
        return normalizeInitPayload({
×
UNCOV
261
            appSettings: settings,
×
UNCOV
262
            activeMapName: firstMapName,
×
UNCOV
263
            initialUrl: ensureParameters(initialTask, firstMapName, firstSessionId, locale),
×
UNCOV
264
            featureTooltipsEnabled: featureTooltipsEnabled,
×
UNCOV
265
            locale: locale,
×
UNCOV
266
            maps: maps,
×
UNCOV
267
            config: config,
×
UNCOV
268
            capabilities: {
×
UNCOV
269
                hasTaskPane: (taskPane != null),
×
UNCOV
270
                hasTaskBar: hasTaskBar,
×
UNCOV
271
                hasStatusBar: hasStatus,
×
UNCOV
272
                hasNavigator: hasNavigator,
×
UNCOV
273
                hasSelectionPanel: hasSelectionPanel,
×
UNCOV
274
                hasLegend: hasLegend,
×
UNCOV
275
                hasToolbar: (Object.keys(tbConf).length > 0),
×
UNCOV
276
                hasViewSize: (viewSize != null)
×
UNCOV
277
            },
×
UNCOV
278
            toolbars: tb,
×
UNCOV
279
            warnings: warnings,
×
UNCOV
280
            initialActiveTool: ActiveMapTool.Pan
×
UNCOV
281
        }, options.layout);
×
UNCOV
282
    }
×
283
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc