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

jumpinjackie / mapguide-react-layout / 15165370206

21 May 2025 02:48PM UTC coverage: 22.447%. Remained the same
15165370206

push

github

jumpinjackie
#1555: More internalization of things that shouldn't be in the API documentation.

Also dd merge-modules plugin for typedoc which flattens our public symbol list, which makes better sense as our node module usage story is to import from a flat barrel "mapguide-react-layout" module.

878 of 1206 branches covered (72.8%)

4975 of 22163 relevant lines covered (22.45%)

6.95 hits per line

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

42.01
/src/api/registry/command.ts
1
import {
1✔
2
    IMapViewer,
3
    ICommand,
4
    Dictionary,
5
    IInvokeUrlCommand,
6
    ISearchCommand,
7
    ITargetedCommand,
8
    IApplicationState,
9
    ReduxDispatch,
10
    getSelectionSet,
11
    getRuntimeMap,
12
    NOOP,
13
    ALWAYS_FALSE,
14
    IInvokeUrlCommandParameter,
15
    ActiveMapTool} from "../../api/common";
16
import { getFusionRoot } from "../../api/runtime";
1✔
17
import { IItem, IInlineMenu, IFlyoutMenu, IComponentFlyoutItem } from "../../components/toolbar";
18
import { tr } from "../i18n";
1✔
19
import { getAssetRoot } from "../../utils/asset";
1✔
20
import {
1✔
21
    SPRITE_ICON_ERROR
22
} from "../../constants/assets";
23
import { assertNever } from "../../utils/never";
1✔
24
import { ensureParameters } from "../../utils/url";
1✔
25
import { ActionType } from '../../constants/actions';
1✔
26
import { showModalUrl } from '../../actions/modal';
1✔
27
import { error } from '../../utils/logger';
1✔
28

29
const FUSION_ICON_REGEX = /images\/icons\/[a-zA-Z\-]*.png/
1✔
30

31
function fixIconPath(path: string): string {
×
32
    if (FUSION_ICON_REGEX.test(path)) {
×
33
        return `${getAssetRoot()}/${path}`.replace(/\/\//g, "/");
×
34
    }
×
35
    return path;
×
36
}
×
37

38
function fusionFixSpriteClass(tb: any, cmd?: ICommand): string | undefined {
×
39
    if (tb.spriteClass) {
×
40
        return tb.spriteClass;
×
41
    }
×
42
    if (cmd && cmd.iconClass) {
×
43
        return cmd.iconClass;
×
44
    }
×
45
    return undefined;
×
46
}
×
47

48
/**
49
 * @hidden
50
 */
51
export function mergeInvokeUrlParameters(currentParameters: IInvokeUrlCommandParameter[], extraParameters?: any) {
1✔
52
    const currentP = currentParameters.reduce<any>((prev, current, i, arr) => {
3✔
53
        prev[current.name] = current.value;
3✔
54
        return prev;
3✔
55
    }, {});
3✔
56
    if (extraParameters) {
3✔
57
        const keys = Object.keys(extraParameters);
2✔
58
        for (const k of keys) {
2✔
59
            currentP[k] = extraParameters[k];
4✔
60
        }
4✔
61
    }
2✔
62
    const merged: IInvokeUrlCommandParameter[] = [];
3✔
63
    const mkeys = Object.keys(currentP);
3✔
64
    for (const k of mkeys) {
3✔
65
        merged.push({ name: k, value: currentP[k] });
6✔
66
    }
6✔
67
    return merged;
3✔
68
}
3✔
69

70
function fixChildItems(childItems: any[], state: IToolbarAppState, commandInvoker: (cmd: ICommand, parameters?: any) => void): IItem[] {
×
71
    return childItems
×
72
        .map(tb => mapToolbarReference(tb, state, commandInvoker))
×
73
        .filter(tb => tb != null) as IItem[];
×
74
}
×
75

76
//TODO: This function should be its own react hook that layers on top of the
77
//useDispatch() and useSelector() hooks provided by react-redux
78
/**
79
 * @hidden
80
 */
81
export function mapToolbarReference(tb: any, state: IToolbarAppState, commandInvoker: (cmd: ICommand, parameters?: any) => void): IItem | IInlineMenu | IFlyoutMenu | IComponentFlyoutItem | null {
1✔
82
    if (tb.error) {
×
83
        const cmdItem: IItem = {
×
84
            iconClass: SPRITE_ICON_ERROR,
×
85
            tooltip: tb.error,
×
86
            label: tr("ERROR"),
×
87
            selected: ALWAYS_FALSE,
×
88
            enabled: ALWAYS_FALSE,
×
89
            invoke: NOOP
×
90
        };
×
91
        return cmdItem;
×
92
    } else if (tb.componentName) {
×
93
        return {
×
94
            icon: tb.icon,
×
95
            iconClass: fusionFixSpriteClass(tb),
×
96
            flyoutId: tb.flyoutId,
×
97
            tooltip: tb.tooltip,
×
98
            label: tb.label,
×
99
            componentName: tb.componentName,
×
100
            componentProps: tb.componentProps
×
101
        };
×
102
    } else if (tb.isSeparator === true) {
×
103
        return { isSeparator: true };
×
104
    } else if (tb.command) {
×
105
        const cmd = getCommand(tb.command);
×
106
        if (cmd != null) {
×
107
            const cmdItem: IItem = {
×
108
                icon: fixIconPath(tb.icon || cmd.icon),
×
109
                iconClass: fusionFixSpriteClass(tb, cmd),
×
110
                tooltip: tb.tooltip,
×
111
                label: tb.label,
×
112
                selected: () => cmd.selected(state),
×
113
                enabled: () => cmd.enabled(state, tb.parameters),
×
114
                invoke: () => commandInvoker(cmd, tb.parameters)
×
115
            };
×
116
            return cmdItem;
×
117
        }
×
118
    } else if (tb.children) {
×
119
        const childItems: any[] = tb.children;
×
120
        return {
×
121
            icon: fixIconPath(tb.icon),
×
122
            iconClass: fusionFixSpriteClass(tb),
×
123
            label: tb.label,
×
124
            tooltip: tb.tooltip,
×
125
            childItems: fixChildItems(childItems, state, commandInvoker)
×
126
        };
×
127
    } else if (tb.label && tb.flyoutId) {
×
128
        return {
×
129
            icon: fixIconPath(tb.icon),
×
130
            iconClass: fusionFixSpriteClass(tb),
×
131
            label: tb.label,
×
132
            tooltip: tb.tooltip,
×
133
            flyoutId: tb.flyoutId
×
134
        }
×
135
    }
×
136
    return null;
×
137
}
×
138

139
/**
140
 * A subset of IApplicationState that's only relevant for toolbar items
141
 * @since 0.13
142
 */
143
export interface IToolbarAppState {
144
    /**
145
     * @since 0.14
146
     */
147
    stateless: boolean;
148
    visibleAndSelectableWmsLayerCount: number;
149
    busyWorkerCount: number;
150
    hasSelection: boolean;
151
    /**
152
     * @since 0.14
153
     */
154
    hasClientSelection: boolean;
155
    hasPreviousView: boolean;
156
    hasNextView: boolean;
157
    featureTooltipsEnabled: boolean;
158
    activeTool: ActiveMapTool;
159
}
160

161
/**
162
 * Helper function to reduce full application state to state relevant for toolbar items
163
 * 
164
 * @param state The full application state
165
 * @since 0.13
166
 */
167
export function reduceAppToToolbarState(state: Readonly<IApplicationState>): Readonly<IToolbarAppState> {
1✔
168
    let hasSelection = false;
23✔
169
    let hasClientSelection = false;
23✔
170
    let hasPreviousView = false;
23✔
171
    let hasNextView = false;
23✔
172
    let visibleWmsLayerCount = 0;
23✔
173
    const selection = getSelectionSet(state);
23✔
174
    hasSelection = (selection != null && selection.SelectedFeatures != null);
23✔
175
    if (state.config.activeMapName) {
23✔
176
        hasClientSelection = state.mapState[state.config.activeMapName].clientSelection != null;
10✔
177
        hasPreviousView = state.mapState[state.config.activeMapName].historyIndex > 0;
10✔
178
        hasNextView = state.mapState[state.config.activeMapName].historyIndex < state.mapState[state.config.activeMapName].history.length - 1;
10✔
179
        visibleWmsLayerCount = (state.mapState[state.config.activeMapName].layers ?? []).filter(l => l.visible && l.selectable && l.type == "WMS").length;
10!
180
    }
10✔
181
    return {
23✔
182
        stateless: state.config.viewer.isStateless,
23✔
183
        visibleAndSelectableWmsLayerCount: visibleWmsLayerCount,
23✔
184
        busyWorkerCount: state.viewer.busyCount,
23✔
185
        hasSelection,
23✔
186
        hasClientSelection,
23✔
187
        hasPreviousView,
23✔
188
        hasNextView,
23✔
189
        activeTool: state.viewer.tool,
23✔
190
        featureTooltipsEnabled: state.viewer.featureTooltipsEnabled
23✔
191
    }
23✔
192
}
23✔
193

194
/**
195
 * Common command condition evaluators
196
 *
197
 *
198
 * @class CommandConditions
199
 */
200
export class CommandConditions {
1✔
201
    /**
202
     * The viewer is not busy
203
     *
204
     * @param {Readonly<IToolbarAppState>} state
205
     * @returns {boolean}
206
     */
207
    public static isNotBusy(state: Readonly<IToolbarAppState>): boolean {
1✔
208
        return state.busyWorkerCount == 0;
2✔
209
    }
2✔
210
    /**
211
     * The viewer has a MapGuide selection set
212
     *
213
     * @param {Readonly<IToolbarAppState>} state
214
     * @returns {boolean}
215
     */
216
    public static hasSelection(state: Readonly<IToolbarAppState>): boolean {
1✔
217
        return state.hasSelection;
5✔
218
    }
5✔
219
    /**
220
     * The viewer has a client-side selection set
221
     *
222
     * @param state 
223
     * @returns  
224
     * @since 0.14
225
     */
226
    public static hasClientSelection(state: Readonly<IToolbarAppState>): boolean {
1✔
227
        return state.hasClientSelection;
×
228
    }
×
229
    /**
230
     * The command is set to be disabled if selection is empty
231
     *
232
     * @param {*} [parameters]
233
     * @returns {boolean}
234
     */
235
    public static disabledIfEmptySelection(state: Readonly<IToolbarAppState>, parameters?: any): boolean {
1✔
236
        if (!state.hasSelection) {
10✔
237
            return (parameters != null && (parameters.DisableIfSelectionEmpty == "true" || parameters.DisableIfSelectionEmpty == true));
6✔
238
        } else
6✔
239
            return false;
4✔
240
    }
10✔
241
    /**
242
     * The viewer has a previous view in the view navigation stack
243
     *
244
     * @param {Readonly<IToolbarAppState>} state
245
     * @returns {boolean}
246
     */
247
    public static hasPreviousView(state: Readonly<IToolbarAppState>): boolean {
1✔
248
        return state.hasPreviousView;
3✔
249
    }
3✔
250
    /**
251
     * The viewer has a next view in the view navigation stack
252
     *
253
     * @param {Readonly<IToolbarAppState>} state
254
     * @returns {boolean}
255
     */
256
    public static hasNextView(state: Readonly<IToolbarAppState>): boolean {
1✔
257
        return state.hasNextView;
3✔
258
    }
3✔
259
}
1✔
260

261
/**
262
 * The set of default command names
263
 *
264
 *
265
 * @class DefaultCommands
266
 */
267
export enum DefaultCommands {
1✔
268
    Select = "Select",
1✔
269
    Pan = "Pan",
1✔
270
    Zoom = "Zoom",
1✔
271
    MapTip = "MapTip",
1✔
272
    ZoomIn = "ZoomIn",
1✔
273
    ZoomOut = "ZoomOut",
1✔
274
    RestoreView = "RestoreView",
1✔
275
    ZoomExtents = "ZoomExtents",
1✔
276
    SelectRadius = "SelectRadius",
1✔
277
    SelectPolygon = "SelectPolygon",
1✔
278
    ClearSelection = "ClearSelection",
1✔
279
    ZoomToSelection = "ZoomToSelection",
1✔
280
    PanLeft = "PanLeft",
1✔
281
    PanRight = "PanRight",
1✔
282
    PanUp = "PanUp",
1✔
283
    PanDown = "PanDown",
1✔
284
    RefreshMap = "RefreshMap",
1✔
285
    PreviousView = "PreviousView",
1✔
286
    NextView = "NextView",
1✔
287
    About = "About",
1✔
288
    Help = "Help",
1✔
289
    Measure = "Measure",
1✔
290
    ViewerOptions = "ViewerOptions",
1✔
291
    Buffer = "Buffer",
1✔
292
    SelectWithin = "SelectWithin",
1✔
293
    QuickPlot = "QuickPlot",
1✔
294
    Redline = "Redline",
1✔
295
    FeatureInfo = "FeatureInfo",
1✔
296
    Theme = "Theme",
1✔
297
    Query = "Query",
1✔
298
    Geolocation = "Geolocation",
1✔
299
    CoordinateTracker = "CoordinateTracker",
1✔
300
    /**
301
     * @since 0.11
302
     */
303
    AddManageLayers = "AddManageLayers",
1✔
304
    /**
305
     * @since 0.11
306
     */
307
    CenterSelection = "CenterSelection",
1✔
308
    /**
309
     * @since 0.14
310
     */
311
    Print = "Print"
1✔
312
}
313

314
const commands: Dictionary<ICommand> = {};
1✔
315

316
function isInvokeUrlCommand(cmdDef: any): cmdDef is IInvokeUrlCommand {
×
317
    return typeof cmdDef.url !== 'undefined';
×
318
}
×
319

320
function isSearchCommand(cmdDef: any): cmdDef is ISearchCommand {
×
321
    return typeof cmdDef.layer !== 'undefined';
×
322
}
×
323

324
function openModalUrl(name: string, dispatch: ReduxDispatch, url: string, modalTitle?: string) {
×
325
    dispatch(showModalUrl({
×
326
        modal: {
×
327
            title: modalTitle || tr(name as any),
×
328
            backdrop: false,
×
329
            overflowYScroll: true
×
330
        },
×
331
        name: name,
×
332
        url: url
×
333
    }));
×
334
}
×
335

336
export function isSupportedCommandInStatelessMode(name: string | undefined) {
1✔
337
    switch (name) {
70✔
338
        case DefaultCommands.MapTip:
70✔
339
        case DefaultCommands.QuickPlot:
70✔
340
        case DefaultCommands.SelectRadius:
70✔
341
        case DefaultCommands.SelectPolygon:
70✔
342
        case DefaultCommands.Buffer:
70✔
343
        case DefaultCommands.SelectWithin:
70✔
344
        case DefaultCommands.Redline:
70✔
345
        case DefaultCommands.FeatureInfo:
70✔
346
        case DefaultCommands.Query:
70✔
347
        case DefaultCommands.Theme:
70✔
348
        case DefaultCommands.CenterSelection:
70✔
349
            return false;
19✔
350
    }
70✔
351
    return true;
51✔
352
}
51✔
353

354
/**
355
 * Opens the given URL in the specified target
356
 *
357
 * @hidden
358
 * @param name
359
 * @param cmdDef
360
 * @param dispatch
361
 * @param url
362
 */
363
export function openUrlInTarget(name: string, cmdDef: ITargetedCommand, hasTaskPane: boolean, dispatch: ReduxDispatch, url: string, modalTitle?: string): void {
1✔
364
    const target = cmdDef.target;
×
365
    if (target == "TaskPane") {
×
366
        //If there's no actual task pane, fallback to modal dialog
367
        if (!hasTaskPane) {
×
368
            openModalUrl(name, dispatch, url, modalTitle);
×
369
        } else {
×
370
            dispatch({
×
371
                type: ActionType.TASK_INVOKE_URL,
×
372
                payload: {
×
373
                    url: url
×
374
                }
×
375
            });
×
376
        }
×
377
    } else if (target == "NewWindow") {
×
378
        openModalUrl(name, dispatch, url, modalTitle);
×
379
    } else if (target == "SpecifiedFrame") {
×
380
        if (cmdDef.targetFrame) {
×
381
            const frames = (window as any).frames as any[];
×
382
            let bInvoked = false;
×
383
            for (let i = 0; i < frames.length; i++) {
×
384
                if (frames[i].name == cmdDef.targetFrame) {
×
385
                    frames[i].location = url;
×
386
                    bInvoked = true;
×
387
                    break;
×
388
                }
×
389
            }
×
390
            if (!bInvoked) {
×
391
                error(`Frame not found: ${cmdDef.targetFrame}`);
×
392
            }
×
393
        } else {
×
394
            error(`Command ${name} has a target of "SpecifiedFrame", but does not specify a target frame`);
×
395
        }
×
396
    } else {
×
397
        assertNever(target);
×
398
    }
×
399
}
×
400

401
/**
402
 * Registers a viewer command
403
 *
404
 *
405
 * @param {string} name
406
 * @param {(ICommand | IInvokeUrlCommand | ISearchCommand)} cmdDef
407
 */
408
export function registerCommand(name: string, cmdDef: ICommand | IInvokeUrlCommand | ISearchCommand) {
1✔
409
    let cmd: ICommand;
×
410
    if (isInvokeUrlCommand(cmdDef)) {
×
411
        cmd = {
×
412
            icon: cmdDef.icon,
×
413
            iconClass: cmdDef.iconClass,
×
414
            title: cmdDef.title,
×
415
            enabled: (state) => {
×
416
                if (cmdDef.disableIfSelectionEmpty === true) {
×
417
                    return CommandConditions.hasSelection(state);
×
418
                }
×
419
                return true;
×
420
            },
×
421
            selected: () => false,
×
422
            invoke: (dispatch: ReduxDispatch, getState: () => IApplicationState, viewer: IMapViewer, parameters?: any) => {
×
423
                const state = getState();
×
424
                const config = state.config;
×
425
                const map = getRuntimeMap(state);
×
426
                const params = mergeInvokeUrlParameters(cmdDef.parameters, parameters);
×
427
                const url = ensureParameters(cmdDef.url, map?.Name, map?.SessionId, config.locale, true, params);
×
428
                openUrlInTarget(name, cmdDef, config.capabilities.hasTaskPane, dispatch, url, cmd.title);
×
429
            }
×
430
        };
×
431
    } else if (isSearchCommand(cmdDef)) {
×
432
        cmd = {
×
433
            icon: cmdDef.icon,
×
434
            iconClass: cmdDef.iconClass,
×
435
            title: cmdDef.title,
×
436
            enabled: state => !state.stateless,
×
437
            selected: () => false,
×
438
            invoke: (dispatch: ReduxDispatch, getState: () => IApplicationState, viewer: IMapViewer, parameters?: any) => {
×
439
                const state = getState();
×
440
                const config = state.config;
×
441
                const map = getRuntimeMap(state);
×
442
                if (map) {
×
443
                    const url = ensureParameters(`${getFusionRoot()}/widgets/Search/SearchPrompt.php`, map.Name, map.SessionId, config.locale, false)
×
444
                        + `&popup=0`
×
445
                        + `&target=TaskPane`
446
                        + `&title=${encodeURIComponent(cmdDef.title)}`
×
447
                        + `&prompt=${encodeURIComponent(cmdDef.prompt)}`
×
448
                        + `&layer=${encodeURIComponent(cmdDef.layer)}`
×
449
                        + `&pointZoomLevel=${parameters.PointZoomLevel}`
×
450
                        + (cmdDef.filter ? `&filter=${encodeURIComponent(cmdDef.filter)}` : '')
×
451
                        + `&limit=${cmdDef.matchLimit}`
×
452
                        + `&properties=${(cmdDef.resultColumns.Column || []).map(col => col.Property).join(",")}`
×
453
                        + `&propNames=${(cmdDef.resultColumns.Column || []).map(col => col.Name).join(",")}`;
×
454
                    openUrlInTarget(name, cmdDef, config.capabilities.hasTaskPane, dispatch, url, cmd.title);
×
455
                }
×
456
            }
×
457
        };
×
458
    } else {
×
459
        cmd = cmdDef;
×
460
    }
×
461
    commands[name] = cmd;
×
462
}
×
463

464
/**
465
 * Gets a registered viewer command by its name
466
 *
467
 *
468
 * @param {string} name
469
 * @returns {(ICommand | undefined)}
470
 */
471
export function getCommand(name: string): ICommand | undefined {
1✔
472
    return commands[name];
×
473
}
×
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