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

jumpinjackie / mapguide-react-layout / 15327964582

29 May 2025 03:55PM UTC coverage: 34.795% (+9.5%) from 25.285%
15327964582

push

github

web-flow
Merge pull request #1560 from jumpinjackie/feature/bp-replace

Abstract away Blueprint.js usage

1323 of 1792 branches covered (73.83%)

365 of 842 new or added lines in 61 files covered. (43.35%)

2 existing lines in 2 files now uncovered.

7870 of 22618 relevant lines covered (34.8%)

8.54 hits per line

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

0.0
/src/containers/quick-plot.tsx
1
import * as React from "react";
×
2
import { getViewer, getFusionRoot } from "../api/runtime";
×
3
import { tr as xlate, tr } from "../api/i18n";
×
4
import { RuntimeMap } from "../api/contracts/runtime-map";
5
import {
×
6
    GenericEvent,
7
    IMapView,
8
    IConfigurationReducerState,
9
    IExternalBaseLayer,
10
    IMapViewer,
11
    Size,
12
    isVisualBaseLayer
13
} from "../api/common";
14
import { MapCapturerContext, IMapCapturerContextCallback } from "./map-capturer-context";
×
15
import { useActiveMapName, useActiveMapView, useActiveMapExternalBaseLayers, useViewerLocale, useAvailableMaps, usePrevious } from './hooks';
×
16
import { setViewRotation, setViewRotationEnabled } from '../actions/map';
×
17
import { debug } from '../utils/logger';
×
18
import { useActiveMapState } from './hooks-mapguide';
×
19
import { useReduxDispatch } from "../components/map-providers/context";
×
NEW
20
import { TypedSelect, useElementContext } from "../components/elements/element-context";
×
21

NEW
22
const PAPER_SIZES = [
×
NEW
23
    { value: "210.0,297.0,A4", label: "A4 (210x297 mm; 8.27x11.69 In) " },
×
NEW
24
    { value: "297.0,420.0,A3", label: "A3 (297x420 mm; 11.69x16.54 In) " },
×
NEW
25
    { value: "148.0,210.0,A5", label: "A5 (148x210 mm; 5.83x8.27 in) " },
×
NEW
26
    { value: "216.0,279.0,Letter", label: "Letter (216x279 mm; 8.50x11.00 In) " },
×
NEW
27
    { value: "216.0,356.0,Legal", label: "Legal (216x356 mm; 8.50x14.00 In) " }
×
NEW
28
];
×
29

NEW
30
const DPIS = [
×
NEW
31
    { value: "96", label: "96" },
×
NEW
32
    { value: "150", label: "150" },
×
NEW
33
    { value: "300", label: "300" },
×
NEW
34
    { value: "600", label: "600" }
×
NEW
35
];
×
36

NEW
37
const SCALES = [
×
NEW
38
    { value: "500", label: "1: 500" },
×
NEW
39
    { value: "1000", label: "1: 1000" },
×
NEW
40
    { value: "2500", label: "1: 2500" },
×
NEW
41
    { value: "5000", label: "1: 5000" }
×
NEW
42
]
×
43

44
function getMargin() {
×
45
    /*
46
    var widget = getParent().Fusion.getWidgetsByType("QuickPlot")[0];
47
    var margin;
48
    
49
    if(!!widget.margin){
50
         margin = widget.margin;
51
    }else{
52
        //the default margin
53
        margin = {top: 25.4, buttom: 12.7, left: 12.7, right: 12.7};
54
    }
55
    return margin;
56
    */
57
    return { top: 25.4, buttom: 12.7, left: 12.7, right: 12.7 };
×
58
}
×
59

NEW
60
function getPrintSize(viewer: IMapViewer, showAdvanced: boolean, paperSize: string, orientation: Orientation): Size {
×
61
    const value = paperSize.split(",");
×
62
    let size: Size;
×
63
    if (orientation === "P") {
×
64
        size = { w: parseFloat(value[0]), h: parseFloat(value[1]) };
×
65
    } else {
×
66
        size = { w: parseFloat(value[1]), h: parseFloat(value[0]) };
×
67
    }
×
68

69
    if (!showAdvanced) {
×
70
        // Calculate the paper size to make sure it has a same ratio with the viweport
71
        const paperRatio = size.w / size.h;
×
72
        var viewSize = viewer.getSize();
×
73
        let vs: Size | undefined;
×
74
        if (orientation === "P") {
×
75
            vs = {
×
76
                w: viewSize[1],
×
77
                h: viewSize[0]
×
78
            };
×
79
        } else {
×
80
            vs = {
×
81
                w: viewSize[0],
×
82
                h: viewSize[1]
×
83
            };
×
84
        }
×
85
        if (vs) {
×
86
            const viewRatio = vs.w / vs.h;
×
87
            if (paperRatio > viewRatio) {
×
88
                size.w = size.h * viewRatio;
×
89
            } else {
×
90
                size.h = size.w / viewRatio;
×
91
            }
×
92
        }
×
93
    }
×
94

95
    const margins = getMargin();
×
96
    size.h = size.h - margins.top - margins.buttom;
×
97
    size.w = size.w - margins.left - margins.right;
×
98

99
    return size;
×
100
}
×
101

102
const _mapCapturers: MapCapturerContext[] = [];
×
103

104
function getActiveCapturer(viewer: IMapViewer, mapNames: string[], activeMapName: string): MapCapturerContext | undefined {
×
105
    let activeCapturer: MapCapturerContext | undefined;
×
106
    if (_mapCapturers.length == 0) {
×
107
        if (mapNames.length) {
×
108
            for (const mapName of mapNames) {
×
109
                const context = new MapCapturerContext(viewer, mapName);
×
110
                _mapCapturers.push(context);
×
111
                if (activeMapName == mapName) {
×
112
                    activeCapturer = context;
×
113
                }
×
114
            }
×
115
        }
×
116
    } else {
×
117
        activeCapturer = _mapCapturers.filter(m => m.getMapName() === activeMapName)[0];
×
118
    }
×
119
    return activeCapturer;
×
120
}
×
121

122
function toggleMapCapturerLayer(locale: string,
×
123
    mapNames: string[] | undefined,
×
124
    activeMapName: string,
×
125
    showAdvanced: boolean,
×
126
    paperSize: string,
×
NEW
127
    orientation: Orientation,
×
128
    scale: string,
×
129
    rotation: number,
×
130
    updateBoxCoords: (box: string, normalizedBox: string) => void,
×
131
    setViewRotationEnabled: (flag: boolean) => void,
×
132
    setViewRotation: (rot: number) => void) {
×
133
    const bVisible: boolean = showAdvanced;
×
134
    const viewer = getViewer();
×
135
    if (viewer && mapNames) {
×
136
        const activeCapturer = getActiveCapturer(viewer, mapNames, activeMapName);
×
137
        if (activeCapturer) {
×
138
            if (bVisible) {
×
139
                const ppSize = getPrintSize(viewer, showAdvanced, paperSize, orientation);
×
140
                const cb: IMapCapturerContextCallback = {
×
141
                    updateBoxCoords
×
142
                }
×
143
                activeCapturer.activate(cb, ppSize, parseFloat(scale), rotation);
×
144
                //For simplicity, reset rotation to 0 and prevent the ability to rotate while the map capture box
145
                //is active
146
                setViewRotationEnabled(false);
×
147
                setViewRotation(0);
×
148
                viewer.toastPrimary("info-sign", tr("QUICKPLOT_BOX_INFO", locale));
×
149
            } else {
×
150
                activeCapturer.deactivate();
×
151
                setViewRotationEnabled(true);
×
152
            }
×
153
        }
×
154
    }
×
155
}
×
156

157
export interface IQuickPlotContainerOwnProps {
158

159
}
160

161
export interface IQuickPlotContainerConnectedState {
162
    config: IConfigurationReducerState;
163
    map: RuntimeMap;
164
    view: IMapView;
165
    externalBaseLayers: IExternalBaseLayer[];
166
    mapNames: string[];
167
}
168

169
export interface IQuickPlotContainerDispatch {
170
    setViewRotation: (rotation: number) => void;
171
    setViewRotationEnabled: (enabled: boolean) => void;
172
}
173

174
/**
175
 * @since 0.15
176
 */
177
export type Orientation = "L" | "P";
178

179
export interface IQuickPlotContainerState {
180
    title: string;
181
    subTitle: string;
182
    showLegend: boolean;
183
    showNorthBar: boolean;
184
    showCoordinates: boolean;
185
    showScaleBar: boolean;
186
    showDisclaimer: boolean;
187
    showAdvanced: boolean;
188
    orientation: Orientation;
189
    paperSize: string;
190
    scale: string;
191
    dpi: string;
192
    rotation: number;
193
    box: string;
194
    normalizedBox: string;
195
}
196

197
export const QuickPlotContainer = () => {
×
NEW
198
    const { Slider, Callout, Button, Select } = useElementContext();
×
199
    const [title, setTitle] = React.useState(""); ``
×
200
    const [subTitle, setSubTitle] = React.useState("");
×
201
    const [showLegend, setShowLegend] = React.useState(false);
×
202
    const [showNorthBar, setShowNorthBar] = React.useState(false);
×
203
    const [showCoordinates, setShowCoordinates] = React.useState(false);
×
204
    const [showScaleBar, setShowScaleBar] = React.useState(false);
×
205
    const [showDisclaimer, setShowDisclaimer] = React.useState(false);
×
206
    const [showAdvanced, setShowAdvanced] = React.useState(false);
×
NEW
207
    const [orientation, setOrientation] = React.useState<Orientation>("P");
×
208
    const [paperSize, setPaperSize] = React.useState("210.0,297.0,A4");
×
209
    const [scale, setScale] = React.useState("5000");
×
210
    const [dpi, setDpi] = React.useState("96");
×
211
    const [rotation, setRotation] = React.useState(0);
×
212
    const [box, setBox] = React.useState("");
×
213
    const [normalizedBox, setNormalizedBox] = React.useState("");
×
214

215
    const locale = useViewerLocale();
×
216
    const activeMapName = useActiveMapName();
×
217
    const mapNames = useAvailableMaps()?.map(m => m.value);
×
218
    const map = useActiveMapState();
×
219
    const view = useActiveMapView();
×
220
    const externalBaseLayers = useActiveMapExternalBaseLayers()?.filter(ebl => isVisualBaseLayer(ebl));
×
221
    const dispatch = useReduxDispatch();
×
222
    const setViewRotationAction = (rotation: number) => dispatch(setViewRotation(rotation));
×
223
    const setViewRotationEnabledAction = (enabled: boolean) => dispatch(setViewRotationEnabled(enabled));
×
224

225
    const onTitleChanged = (e: GenericEvent) => {
×
226
        setTitle(e.target.value);
×
227
    };
×
228
    const onSubTitleChanged = (e: GenericEvent) => {
×
229
        setSubTitle(e.target.value);
×
230
    };
×
231
    const onShowLegendChanged = () => {
×
232
        setShowLegend(!showLegend);
×
233
    };
×
234
    const onShowNorthArrowChanged = () => {
×
235
        setShowNorthBar(!showNorthBar);
×
236
    };
×
237
    const onShowCoordinatesChanged = () => {
×
238
        setShowCoordinates(!showCoordinates);
×
239
    };
×
240
    const onShowScaleBarChanged = () => {
×
241
        setShowScaleBar(!showScaleBar);
×
242
    };
×
243
    const onShowDisclaimerChanged = () => {
×
244
        setShowDisclaimer(!showDisclaimer);
×
245
    };
×
246
    const onAdvancedOptionsChanged = () => {
×
247
        setShowAdvanced(!showAdvanced);
×
248
    };
×
249
    const onRotationChanged = (value: number) => {
×
250
        setRotation(value);
×
251
    };
×
252
    const onGeneratePlot = () => { };
×
253
    const updateBoxCoords = (box: string, normalizedBox: string): void => {
×
254
        setBox(box);
×
255
        setNormalizedBox(normalizedBox);
×
256
    };
×
257
    //Side-effect that emulates the old componentWillUnmount lifecyle method to tear down
258
    //the active map capturer
259
    React.useEffect(() => {
×
260
        return () => {
×
261
            //Tear down all active capture box layers
262
            const viewer = getViewer();
×
263
            if (viewer && mapNames) {
×
264
                for (const activeMapName of mapNames) {
×
265
                    const activeCapturer = getActiveCapturer(viewer, mapNames, activeMapName);
×
266
                    if (activeCapturer) {
×
267
                        debug(`De-activating map capturer for: ${activeMapName}`);
×
268
                        activeCapturer.deactivate();
×
269
                    }
×
270
                }
×
271
            }
×
272
        };
×
273
    }, []);
×
274
    //Although the dep array arg of React.useEffect() has effectively rendered the need to
275
    //track previous values obsolete, we still need to track the previous advanced flag to
276
    //verify that the flag is amongst the actual values in the dep array that has changed
277
    const prevShowAdvanced = usePrevious(showAdvanced);
×
278
    //Side-effect that toggles/updates associated map capturers
279
    React.useEffect(() => {
×
280
        if (activeMapName && mapNames) {
×
281
            if (showAdvanced != prevShowAdvanced) {
×
282
                toggleMapCapturerLayer(locale,
×
283
                    mapNames,
×
284
                    activeMapName,
×
285
                    showAdvanced,
×
286
                    paperSize,
×
287
                    orientation,
×
288
                    scale,
×
289
                    rotation,
×
290
                    updateBoxCoords,
×
291
                    setViewRotationEnabledAction,
×
292
                    setViewRotationAction);
×
293
            }
×
294
            if (showAdvanced) {
×
295
                const v = getViewer();
×
296
                if (v) {
×
297
                    const capturer = getActiveCapturer(v, mapNames, activeMapName);
×
298
                    if (capturer) {
×
299
                        const ppSize = getPrintSize(v, showAdvanced, paperSize, orientation);
×
300
                        debug(`Updating map capturer for: ${activeMapName}`);
×
301
                        capturer.updateBox(ppSize, parseFloat(scale), rotation);
×
302
                    }
×
303
                }
×
304
            }
×
305
        }
×
306
    }, [mapNames, activeMapName, showAdvanced, scale, paperSize, orientation, rotation, locale]);
×
307
    const viewer = getViewer();
×
308
    if (!viewer || !map || !view) {
×
309
        return <noscript />;
×
310
    }
×
311
    let hasExternalBaseLayers = false;
×
312
    if (externalBaseLayers) {
×
313
        hasExternalBaseLayers = externalBaseLayers.length > 0;
×
314
    }
×
315
    let normBox = normalizedBox;
×
316
    let theBox = box;
×
317
    if (!showAdvanced) {
×
318
        const extent = viewer.getCurrentExtent();
×
319
        theBox = `${extent[0]}, ${extent[1]}, ${extent[2]}, ${extent[1]}, ${extent[2]}, ${extent[3]}, ${extent[0]}, ${extent[3]}, ${extent[0]}, ${extent[1]}`;
×
320
        normBox = theBox;
×
321
    }
×
322
    let ppSize: string;
×
323
    let prSize: string;
×
324
    const tokens = paperSize.split(",");
×
325
    if (orientation === "L") {
×
326
        prSize = `${tokens[1]},${tokens[0]}`;
×
327
        ppSize = `${prSize},${tokens[2]}`;
×
328
    } else { // P
×
329
        prSize = `${tokens[0]},${tokens[1]}`;
×
330
        ppSize = `${prSize},${tokens[2]}`;
×
331
    }
×
NEW
332
    const url = `${getFusionRoot()}/widgets/QuickPlot/PlotAsPDF.php`;
×
NEW
333
    const ORIENTATIONS = [
×
NEW
334
        { value: "P" as Orientation, label: xlate("QUICKPLOT_ORIENTATION_P", locale) },
×
NEW
335
        { value: "L" as Orientation, label: xlate("QUICKPLOT_ORIENTATION_L", locale) }
×
NEW
336
    ];
×
337
    return <div className="component-quick-plot">
×
338
        <form id="Form1" name="Form1" target="_blank" method="post" action={url}>
×
339
            <input type="hidden" id="printId" name="printId" value={`${Math.random() * 1000}`} />
×
340
            <div className="Title FixWidth">{xlate("QUICKPLOT_HEADER", locale)}</div>
×
341
            <label className="bp3-label">
×
342
                {xlate("QUICKPLOT_TITLE", locale)}
×
343
                <input type="text" className="bp3-input bp3-fill" dir="auto" name="{field:title}" id="title" maxLength={100} value={title} onChange={onTitleChanged} />
×
344
            </label>
×
345
            <label className="bp3-label">
×
346
                {xlate("QUICKPLOT_SUBTITLE", locale)}
×
347
                <input type="text" className="bp3-input bp3-fill" dir="auto" name="{field:sub_title}" id="subtitle" maxLength={100} value={subTitle} onChange={onSubTitleChanged} />
×
348
            </label>
×
349
            <label className="bp3-label">
×
350
                {xlate("QUICKPLOT_PAPER_SIZE", locale)}
×
351
                {/*
352
                    The pre-defined paper size list. The value for each "option" item is in this format: [width,height]. The unit is in millimeter.
353
                    We can change the html code to add more paper size or remove some ones.
354
                */}
NEW
355
                <TypedSelect<string, false> fill
×
NEW
356
                    id="paperSizeSelect"
×
NEW
357
                    name="paperSizeSelect"
×
NEW
358
                    value={paperSize}
×
NEW
359
                    onChange={e => setPaperSize(e)}
×
NEW
360
                    items={PAPER_SIZES} />
×
361
            </label>
×
362
            <label className="bp3-label">
×
363
                {xlate("QUICKPLOT_ORIENTATION", locale)}
×
364
                {/*
365
                    The pre-defined paper orientations
366
                */}
NEW
367
                <TypedSelect<Orientation, false>
×
NEW
368
                    fill
×
NEW
369
                    id="orientation"
×
NEW
370
                    name="orientation"
×
NEW
371
                    value={orientation}
×
NEW
372
                    onChange={e => setOrientation(e)}
×
NEW
373
                    items={ORIENTATIONS} />
×
374
            </label>
×
375
            <input type="hidden" id="paperSize" name="paperSize" value={ppSize} />
×
376
            <input type="hidden" id="printSize" name="printSize" value={prSize} />
×
377
            <fieldset>
×
378
                <legend>{xlate("QUICKPLOT_SHOWELEMENTS", locale)}</legend>
×
379
                <label className="bp3-control bp3-checkbox">
×
380
                    <input type="checkbox" id="ShowLegendCheckBox" name="ShowLegend" checked={showLegend} onChange={onShowLegendChanged} />
×
381
                    <span className="bp3-control-indicator" />
×
382
                    {xlate("QUICKPLOT_SHOWLEGEND", locale)}
×
383
                </label>
×
384
                <label className="bp3-control bp3-checkbox">
×
385
                    <input type="checkbox" id="ShowNorthArrowCheckBox" name="ShowNorthArrow" checked={showNorthBar} onChange={onShowNorthArrowChanged} />
×
386
                    <span className="bp3-control-indicator" />
×
387
                    {xlate("QUICKPLOT_SHOWNORTHARROW", locale)}
×
388
                </label>
×
389
                <label className="bp3-control bp3-checkbox">
×
390
                    <input type="checkbox" id="ShowCoordinatesCheckBox" name="ShowCoordinates" checked={showCoordinates} onChange={onShowCoordinatesChanged} />
×
391
                    <span className="bp3-control-indicator" />
×
392
                    {xlate("QUICKPLOT_SHOWCOORDINTES", locale)}
×
393
                </label>
×
394
                <label className="bp3-control bp3-checkbox">
×
395
                    <input type="checkbox" id="ShowScaleBarCheckBox" name="ShowScaleBar" checked={showScaleBar} onChange={onShowScaleBarChanged} />
×
396
                    <span className="bp3-control-indicator" />
×
397
                    {xlate("QUICKPLOT_SHOWSCALEBAR", locale)}
×
398
                </label>
×
399
                <label className="bp3-control bp3-checkbox">
×
400
                    <input type="checkbox" id="ShowDisclaimerCheckBox" name="ShowDisclaimer" checked={showDisclaimer} onChange={onShowDisclaimerChanged} />
×
401
                    <span className="bp3-control-indicator" />
×
402
                    {xlate("QUICKPLOT_SHOWDISCLAIMER", locale)}
×
403
                </label>
×
404
            </fieldset>
×
405
            <div className="HPlaceholder5px"></div>
×
406
            <div>
×
407
                <label className="bp3-control bp3-checkbox">
×
408
                    <input type="checkbox" id="AdvancedOptionsCheckBox" onChange={onAdvancedOptionsChanged} />
×
409
                    <span className="bp3-control-indicator" />
×
410
                    {xlate("QUICKPLOT_ADVANCED_OPTIONS", locale)}
×
411
                </label>
×
412
            </div>
×
413
            {(() => {
×
414
                if (showAdvanced) {
×
415
                    return <div>
×
416
                        <label className="bp3-label">
×
417
                            {xlate("QUICKPLOT_SCALING", locale)}
×
418
                            {/*
419
                                The pre-defined scales. The value for each "option" item is the scale denominator.
420
                                We can change the html code to extend the pre-defined scales
421
                            */}
NEW
422
                            <TypedSelect<string, false> fill
×
NEW
423
                                id="scaleDenominator"
×
NEW
424
                                name="scaleDenominator"
×
NEW
425
                                value={scale}
×
NEW
426
                                onChange={e => setScale(e)}
×
NEW
427
                                items={SCALES} />
×
428
                        </label>
×
429
                        <label className="bp3-label">
×
430
                            {xlate("QUICKPLOT_DPI", locale)}
×
431
                            {/*
432
                                The pre-defined print DPI.
433
                                We can change the html code to extend the pre-defined values
434
                            */}
NEW
435
                            <TypedSelect<string, false> fill
×
NEW
436
                                id="dpi"
×
NEW
437
                                name="dpi"
×
NEW
438
                                onChange={e => setDpi(e)}
×
NEW
439
                                items={DPIS} />
×
440
                        </label>
×
441
                        <label className="bp3-label noselect">
×
442
                            {xlate("QUICKPLOT_BOX_ROTATION", locale)}
×
443
                            <div style={{ paddingLeft: 16, paddingRight: 16 }}>
×
444
                                <Slider min={0} max={360} labelStepSize={90} stepSize={1} value={rotation} onChange={onRotationChanged} />
×
445
                            </div>
×
446
                        </label>
×
447
                    </div>;
×
448
                } else {
×
449
                    return <div>
×
450
                        <input type="hidden" id="scaleDenominator" name="scaleDenominator" value={`${view.scale}`} />
×
451
                        <input type="hidden" id="dpi" name="dpi" value={dpi} />
×
452
                    </div>;
×
453
                }
×
454
            })()}
×
455
            <div className="HPlaceholder5px"></div>
×
456
            {(() => {
×
457
                if (hasExternalBaseLayers) {
×
NEW
458
                    return <Callout variant="primary" icon="info-sign">
×
459
                        {xlate("QUICKPLOT_COMMERCIAL_LAYER_WARNING", locale)}
×
460
                    </Callout>;
×
461
                }
×
462
            })()}
×
463
            <div className="ButtonContainer FixWidth">
×
NEW
464
                <Button type="submit" variant="primary" icon="print" onClick={onGeneratePlot}>{xlate("QUICKPLOT_GENERATE", locale)}</Button>
×
465
            </div>
×
466
            <input type="hidden" id="margin" name="margin" />
×
467
            <input type="hidden" id="normalizedBox" name="normalizedBox" value={normBox} />
×
468
            <input type="hidden" id="rotation" name="rotation" value={-(rotation || 0)} />
×
469
            <input type="hidden" id="sessionId" name="sessionId" value={map.SessionId} />
×
470
            <input type="hidden" id="mapName" name="mapName" value={map.Name} />
×
471
            <input type="hidden" id="box" name="box" value={theBox} />
×
472
            <input type="hidden" id="legalNotice" name="legalNotice" />
×
473
        </form>
×
474
    </div>;
×
475
}
×
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