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

jumpinjackie / mapguide-react-layout / 25447908521

06 May 2026 04:29PM UTC coverage: 59.713% (+0.04%) from 59.676%
25447908521

Pull #1645

github

web-flow
Merge da9b43160 into 706e3ead7
Pull Request #1645: Fix TS2322: implement client-side QuickPlot PDF generation

3167 of 3888 branches covered (81.46%)

52 of 63 new or added lines in 3 files covered. (82.54%)

2 existing lines in 1 file now uncovered.

15766 of 26403 relevant lines covered (59.71%)

13.04 hits per line

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

88.86
/src/containers/quick-plot.tsx
1
import * as React from "react";
1✔
2
import { getFusionRoot } from "../api/runtime";
1✔
3
import { tr as xlate, tr } from "../api/i18n";
1✔
4
import { RuntimeMap } from "../api/contracts/runtime-map";
5
import {
1✔
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";
1✔
15
import { useActiveMapName, useActiveMapView, useActiveMapExternalBaseLayers, useViewerLocale, useAvailableMaps, usePrevious } from './hooks';
1✔
16
import { setViewRotation, setViewRotationEnabled } from '../actions/map';
1✔
17
import { debug } from '../utils/logger';
1✔
18
import { useActiveMapState } from './hooks-mapguide';
1✔
19
import { useMapProviderContext, useReduxDispatch } from "../components/map-providers/context";
1✔
20
import { TypedSelect, useElementContext } from "../components/elements/element-context";
1✔
21
import { IMapProviderContext } from "../components/map-providers/base";
22

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

31
const DPIS = [
1✔
32
    { value: "96", label: "96" },
1✔
33
    { value: "150", label: "150" },
1✔
34
    { value: "300", label: "300" },
1✔
35
    { value: "600", label: "600" }
1✔
36
];
1✔
37

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

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

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

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

96
    const margins = getMargin();
8✔
97
    size.h = size.h - margins.top - margins.buttom;
8✔
98
    size.w = size.w - margins.left - margins.right;
8✔
99

100
    return size;
8✔
101
}
8✔
102

103
const _mapCapturers: MapCapturerContext[] = [];
1✔
104

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

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

158
export interface IQuickPlotContainerOwnProps {
159
    /**
160
     * When set to "true", the QuickPlot component operates in fully client-side mode
161
     * and generates the PDF locally without requiring a MapGuide Server connection.
162
     * This value is read from the widget's Extension.ClientSide property in the appdef.
163
     *
164
     * @since 0.15
165
     */
166
    clientSide?: string;
167
}
168

169
export interface IQuickPlotContainerConnectedState {
170
    config: IConfigurationReducerState;
171
    map: RuntimeMap;
172
    view: IMapView;
173
    externalBaseLayers: IExternalBaseLayer[];
174
    mapNames: string[];
175
}
176

177
export interface IQuickPlotContainerDispatch {
178
    setViewRotation: (rotation: number) => void;
179
    setViewRotationEnabled: (enabled: boolean) => void;
180
}
181

182
/**
183
 * @since 0.15
184
 */
185
export type Orientation = "L" | "P";
186

187
export interface IQuickPlotContainerState {
188
    title: string;
189
    subTitle: string;
190
    showLegend: boolean;
191
    showNorthBar: boolean;
192
    showCoordinates: boolean;
193
    showScaleBar: boolean;
194
    showDisclaimer: boolean;
195
    showAdvanced: boolean;
196
    orientation: Orientation;
197
    paperSize: string;
198
    scale: string;
199
    dpi: string;
200
    rotation: number;
201
    box: string;
202
    normalizedBox: string;
203
}
204

205
export const QuickPlotContainer = (props: IQuickPlotContainerOwnProps) => {
1✔
206
    const isClientSide = props.clientSide === "true";
18✔
207
    const { Slider, Callout, Button, Select, FormGroup, InputGroup, Checkbox } = useElementContext();
18✔
208
    const [title, setTitle] = React.useState("");
18✔
209
    const [subTitle, setSubTitle] = React.useState("");
18✔
210
    const [showLegend, setShowLegend] = React.useState(false);
18✔
211
    const [showNorthBar, setShowNorthBar] = React.useState(false);
18✔
212
    const [showCoordinates, setShowCoordinates] = React.useState(false);
18✔
213
    const [showScaleBar, setShowScaleBar] = React.useState(false);
18✔
214
    const [showDisclaimer, setShowDisclaimer] = React.useState(false);
18✔
215
    const [showAdvanced, setShowAdvanced] = React.useState(false);
18✔
216
    const [orientation, setOrientation] = React.useState<Orientation>("P");
18✔
217
    const [paperSize, setPaperSize] = React.useState("210.0,297.0,A4");
18✔
218
    const [scale, setScale] = React.useState("5000");
18✔
219
    const [dpi, setDpi] = React.useState("96");
18✔
220
    const [rotation, setRotation] = React.useState(0);
18✔
221
    const [box, setBox] = React.useState("");
18✔
222
    const [normalizedBox, setNormalizedBox] = React.useState("");
18✔
223
    const [isGenerating, setIsGenerating] = React.useState(false);
18✔
224

225
    const viewer = useMapProviderContext();
18✔
226
    const locale = useViewerLocale();
18✔
227
    const activeMapName = useActiveMapName();
18✔
228
    const mapNames = useAvailableMaps()?.map(m => m.value);
18✔
229
    const map = useActiveMapState();
18✔
230
    const view = useActiveMapView();
18✔
231
    const externalBaseLayers = useActiveMapExternalBaseLayers()?.filter(ebl => isVisualBaseLayer(ebl));
18✔
232
    const dispatch = useReduxDispatch();
18✔
233
    const setViewRotationAction = (rotation: number) => dispatch(setViewRotation(rotation));
18✔
234
    const setViewRotationEnabledAction = (enabled: boolean) => dispatch(setViewRotationEnabled(enabled));
18✔
235

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