commit 7afc05a2bd3da5b14f9a3b5dbce8f3d281a4c664
parent 2034e4329dd63ccafdcdd66f19fd25a31caf9be3
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Mon, 7 Nov 2016 11:07:30 +0300
replaced react with knockout
Diffstat:
6 files changed, 208 insertions(+), 232 deletions(-)
diff --git a/src/lib/control.printPages/control.js b/src/lib/control.printPages/control.js
@@ -1,18 +1,51 @@
import L from 'leaflet'
-import React from 'react';
-import ReactDOM from 'react-dom';
+import ko from 'knockout';
+import '../knockout.component.progress/progress';
import '../controls-styles.css';
import './control.css';
-import PrintPagesForm from './form';
import PageFeature from './pageFeature';
import Contextmenu from '../contextmenu/contextmenu';
import {renderMap} from './map-render'
+import formHtml from './form.html';
+
+ko.extenders.checkNumberRange = function(target, range) {
+ return ko.pureComputed({
+ read: target, //always return the original observables value
+ write: function(newValue) {
+ newValue = parseFloat(newValue);
+ if (newValue >= range[0] && newValue <= range[1]) {
+ target(newValue);
+ } else {
+ target.notifySubscribers(target());
+ }
+ }
+ }
+ ).extend({notify: 'always'});
+};
L.Control.PrintPages = L.Control.extend({
options: {position: 'bottomleft'},
initialize: function(options) {
L.Control.prototype.initialize.call(this, options);
this.pages = [];
+ this.scale = ko.observable(500).extend({checkNumberRange: [1, 1000000]});
+ this.resolution = ko.observable(300).extend({checkNumberRange: [10, 9999]});
+ this.zoomLevel = ko.observable('auto');
+ this.pageWidth = ko.observable(210).extend({checkNumberRange: [10, 9999]});
+ this.pageHeight = ko.observable(297).extend({checkNumberRange: [10, 9999]});
+ this.settingsExpanded = ko.observable(false);
+ this.makingPdf = ko.observable(false);
+ this.downloadProgressRange = ko.observable(undefined);
+ this.downloadProgressDone = ko.observable(undefined);
+ this.marginLeft = ko.observable(3).extend({checkNumberRange: [0, 99]});
+ this.marginRight = ko.observable(3).extend({checkNumberRange: [0, 99]});
+ this.marginTop = ko.observable(3).extend({checkNumberRange: [0, 99]});
+ this.marginBottom = ko.observable(3).extend({checkNumberRange: [0, 99]});
+ this.autoZoomLevels = ko.observable({});
+ this.printSize = ko.pureComputed(this._printSize, this);
+ this.printSize.subscribe(this.onPageSizeChanged, this);
+ this.scale.subscribe(this.onPageSizeChanged, this);
+ this.resolution.subscribe(this.onPageSizeChanged, this);
},
onAdd: function(map) {
@@ -25,26 +58,19 @@ L.Control.PrintPages = L.Control.extend({
}
map.on('move', this.updateFormZooms, this);
- this.form = ReactDOM.render(<PrintPagesForm
- onAddLandscapePage={this.addLandscapePage.bind(this)}
- onAddPortraitPage={this.addPortraitPage.bind(this)}
- onRemovePages={this.removePages.bind(this)}
- onSavePdf={this.savePdf.bind(this)}
- onFormDataChanged={this.onFormDataChanged.bind(this)}
- />, container
- );
+ container.innerHTML = formHtml;
+ ko.applyBindings(this, container);
this.updateFormZooms();
return container;
},
- addPage: function(data, landsacape) {
- let {pageWidth, pageHeight, marginLeft, marginTop, marginRight, marginBottom} = data;
- if (landsacape) {
+ addPage: function(isLandsacape) {
+ let [pageWidth, pageHeight] = this.printSize();
+ if (isLandsacape) {
[pageWidth, pageHeight] = [pageHeight, pageWidth];
}
- const page = new PageFeature(this._map.getCenter(),
- [pageWidth - marginLeft - marginRight, pageHeight - marginTop - marginBottom],
- data.scale, (this.pages.length + 1).toString()
+ const page = new PageFeature(this._map.getCenter(), [pageWidth, pageHeight],
+ this.scale(), (this.pages.length + 1).toString()
);
page.addTo(this._map);
this.pages.push(page);
@@ -56,13 +82,13 @@ L.Control.PrintPages = L.Control.extend({
return page
},
- addLandscapePage: function(data) {
- const page = this.addPage(data, true);
+ addLandscapePage: function() {
+ const page = this.addPage(true);
page._rotated = true;
},
- addPortraitPage: function(data) {
- this.addPage(data, false);
+ addPortraitPage: function() {
+ this.addPage(false);
},
removePage: function(page) {
@@ -88,22 +114,21 @@ L.Control.PrintPages = L.Control.extend({
renderMap(this._map);
},
- onFormDataChanged: function(data) {
- let {pageWidth, pageHeight, marginLeft, marginTop, marginRight, marginBottom, scale} = data;
+ onPageSizeChanged: function() {
+ let [pageWidth, pageHeight] = this.printSize();
this.pages.forEach((page) => {
- let w = pageWidth - marginLeft - marginRight,
- h = pageHeight - marginTop - marginBottom;
+ let [w, h] = [pageWidth, pageHeight];
if (page._rotated) {
[w, h] = [h, w];
}
- page.setSize([w, h], scale);
+ page.setSize([w, h], this.scale());
}
);
this.updateFormZooms();
},
makePageContexmenuItems: function(page) {
- var items = [
+ const items = [
{text: 'Rotate', callback: this.rotatePage.bind(this, page)},
'-',
{text: 'Delete', callback: this.removePage.bind(this, page)},
@@ -144,9 +169,14 @@ L.Control.PrintPages = L.Control.extend({
}
},
+ _printSize: function() {
+ return [this.pageWidth() - this.marginLeft() - this.marginRight(),
+ this.pageHeight() - this.marginTop() - this.marginBottom()];
+ },
+
suggestZooms: function() {
- const scale = this.form.state.scale,
- resolution = this.form.state.resolution;
+ const scale = this.scale(),
+ resolution = this.resolution();
let referenceLat;
if (this.pages.length > 0) {
let absLats = this.pages.map((page) => {
@@ -160,18 +190,18 @@ L.Control.PrintPages = L.Control.extend({
}
referenceLat = this._map.getCenter().lat;
}
- var targetMetersPerPixel = scale / (resolution / 2.54);
- var mapUnitsPerPixel = targetMetersPerPixel / Math.cos(referenceLat * Math.PI / 180);
- var zoomSat = Math.ceil(Math.log(40075016.4 / 256 / mapUnitsPerPixel) / Math.LN2);
+ let targetMetersPerPixel = scale / (resolution / 2.54);
+ let mapUnitsPerPixel = targetMetersPerPixel / Math.cos(referenceLat * Math.PI / 180);
+ const satZoom = Math.ceil(Math.log(40075016.4 / 256 / mapUnitsPerPixel) / Math.LN2);
targetMetersPerPixel = scale / (90 / 2.54) / 1.5;
mapUnitsPerPixel = targetMetersPerPixel / Math.cos(referenceLat * Math.PI / 180);
- var zoomMap = Math.round(Math.log(40075016.4 / 256 / mapUnitsPerPixel) / Math.LN2);
- return {zoomMap, zoomSat};
+ const mapZoom = Math.round(Math.log(40075016.4 / 256 / mapUnitsPerPixel) / Math.LN2);
+ return {mapZoom, satZoom};
},
updateFormZooms: function() {
- this.form.setSuggestedZooms(this.suggestZooms());
+ this.autoZoomLevels(this.suggestZooms());
}
}
);
\ No newline at end of file
diff --git a/src/lib/control.printPages/form.html b/src/lib/control.printPages/form.html
@@ -0,0 +1,91 @@
+<table class="layout">
+ <tbody>
+ <tr><td colspan="2">
+ <a title="Add page in portrait orientation" class="button-add-page-vert image-button"
+ data-bind="click: addPortraitPage"></a>
+ <a title="Add page in landscape orientation" class="button-add-page-horiz image-button"
+ data-bind="click: addLandscapePage"></a>
+ <a title="Remove all pages" class="button-remove-pages image-button"
+ data-bind="click: removePages"></a>
+ </td></tr>
+ <tr>
+ <td class="label">Print scale</td>
+ <td>
+ <div class="preset-values" >
+ <a data-bind="click: function() {$root.scale(100)}">100 m</a>
+ <a data-bind="click: function() {$root.scale(500)}">500 m</a>
+ <a data-bind="click: function() {$root.scale(1000)}">1 km</a>
+ </div>
+ <input type="text" class="scale" maxlength="6" data-bind="value: scale"> m in 1 cm
+ </td>
+ </tr>
+ <tr data-bind="visible: settingsExpanded">
+ <td class="label">Page size</td>
+ <td>
+ <div class="preset-values">
+ <a data-bind="click: function() {$root.pageWidth(594); $root.pageHeight(841)}">A1</a>
+ <a data-bind="click: function() {$root.pageWidth(420); $root.pageHeight(594)}">A2</a>
+ <a data-bind="click: function() {$root.pageWidth(297); $root.pageHeight(420)}">A3</a>
+ <a data-bind="click: function() {$root.pageWidth(210); $root.pageHeight(297)}">A4</a>
+ <a data-bind="click: function() {$root.pageWidth(148); $root.pageHeight(210)}">A5</a>
+ </div>
+ <input type="text" maxlength="4" title="width" placeholder="width" class="page-size" data-bind="value: pageWidth">
+ x <input type="text" maxlength="4" title="height" placeholder="height" class="page-size" data-bind="value: pageHeight"> mm
+ </td>
+ </tr>
+ <tr data-bind="visible: settingsExpanded">
+ <td class="label-high">Margins</td>
+ <td>
+ <table class="margins">
+ <tr>
+ <td></td>
+ <td><input type="text" maxlength="2" value="3" data-bind="value: marginTop"></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td><input type="text" maxlength="2" value="3" data-bind="value: marginLeft"></td>
+ <td></td><td><input type="text" maxlength="2" value="3" data-bind="value: marginRight"> mm</td>
+ </tr>
+ <tr><td></td><td><input type="text" maxlength="2" value="3" data-bind="value: marginBottom"></td><td></td></tr>
+ </table>
+ </td>
+ </tr>
+ <tr data-bind="visible: settingsExpanded">
+ <td class="label">Resolution</td>
+ <td><input type="text" maxlength="4" class="resolution" data-bind="value: resolution"> dpi</td>
+ </tr>
+ <tr data-bind="visible: settingsExpanded">
+ <td class="label">Source zoom<br />level</td>
+ <td>
+ <select name="srczoom"
+ data-bind="options: ['auto', 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], value: zoomLevel">
+ </select>
+ </td>
+ </tr>
+ <tr><td colspan="2">
+ <a class="button-settings image-button" data-bind="click: function() {settingsExpanded(!settingsExpanded())}"></a>
+ <div class="settings-summary">
+ <span data-bind="text: pageWidth"></span> x <span data-bind="text: pageHeight"></span> mm,<br/>
+ <span data-bind="text: resolution"></span> dpi,
+ zoom <span data-bind="text: zoomLevel"></span>
+ <!-- ko if: zoomLevel()=== "auto" -->
+ (<span title="Zoom for satellite and scanned imagery" data-bind="text: autoZoomLevels().satZoom"></span>
+ / <span title="Zoom for maps like OSM and Google" data-bind="text: autoZoomLevels().mapZoom"></span>)
+ <!-- /ko -->
+ </div>
+ </td></tr>
+ <tr><td colspan="2">
+ <div class="download-button-row">
+ <a class="text-button button-save" data-bind="
+ click: savePdf,
+ visible: !makingPdf()">Save PDF</a>
+ <div data-bind="
+ component: {
+ name: 'progress-indicator',
+ params: {progressRange: downloadProgressRange, progressDone: downloadProgressDone}
+ },
+ visible: makingPdf()"></div>
+ </div>
+ </td></tr>
+ </tbody>
+</table>
diff --git a/src/lib/control.printPages/form.js b/src/lib/control.printPages/form.js
@@ -1,197 +0,0 @@
-import React, {Component} from 'react';
-
-export default class PrintPagesForm extends Component {
- constructor() {
- super();
- this.state = {
- scale: 500,
- pageWidth: 210,
- pageHeight: 297,
- marginLeft: 3,
- marginRight: 3,
- marginTop: 3,
- marginBottom: 3,
- resolution: 300,
- zoomLevel: 'auto',
- advancedSettingsExpanded: false
- }
- }
-
- render() {
- return (
- <table className="layout">
- <tbody>
- <tr>
- <td colSpan="2">
- <a title="Add page in portrait orientation" className="button-add-page-vert image-button"
- onClick={this.props.onAddPortraitPage.bind(null, this.state)}/>
- <a title="Add page in landscape orientation" className="button-add-page-horiz image-button"
- onClick={this.props.onAddLandscapePage.bind(null, this.state)}/>
- <a title="Remove all pages" className="button-remove-pages image-button"
- onClick={this.props.onRemovePages}/>
- </td>
- </tr>
- <tr>
- <td className="label">Print scale</td>
- <td>
- <div className="preset-values">
- <a onClick={this.handleScalePresetClicked.bind(null, 100)}>100 m</a>
- <a onClick={this.handleScalePresetClicked.bind(null, 500)}>500 m</a>
- <a onClick={this.handleScalePresetClicked.bind(null, 1000)}>1 km</a>
- </div>
- <input type="text" className="scale" maxLength="6"
- value={this.state.scale.toString()}
- onChange={this.handleNumericChange.bind(null, 'scale')}
- /> m in 1 cm
- </td>
- </tr>
- {this.state.advancedSettingsExpanded &&
- <tr>
- <td className="label">Page size</td>
- <td>
- <div className="preset-values">
- <a onClick={this.handleSizePresetClicked.bind(null, 594, 841)}>A1</a>
- <a onClick={this.handleSizePresetClicked.bind(null, 420, 594)}>A2</a>
- <a onClick={this.handleSizePresetClicked.bind(null, 297, 420)}>A3</a>
- <a onClick={this.handleSizePresetClicked.bind(null, 210, 297)}>A4</a>
- <a onClick={this.handleSizePresetClicked.bind(null, 148, 210)}>A5</a>
- </div>
- <input type="text" maxLength="4" title="width" placeholder="width" className="page-size"
- value={this.state.pageWidth.toString()}
- onChange={this.handleNumericChange.bind(null, 'pageWidth')}/>
- x
- <input type="text" maxLength="4" title="height" placeholder="height" className="page-size"
- value={this.state.pageHeight.toString()}
- onChange={this.handleNumericChange.bind(null, 'pageHeight')}/>
- mm
- </td>
- </tr>
- }
- {this.state.advancedSettingsExpanded &&
- <tr>
- <td className="label-high">Margins</td>
- <td>
- <table className="margins">
- <tbody>
- <tr>
- <td />
- <td>
- <input type="text" maxLength="2"
- value={this.state.marginTop.toString()}
- onChange={this.handleNumericChange.bind(null, 'marginTop')}/>
- </td>
- <td />
- </tr>
- <tr>
- <td>
- <input type="text" maxLength="2"
- value={this.state.marginLeft.toString()}
- onChange={this.handleNumericChange.bind(null, 'marginLeft')}/>
- </td>
- <td />
- <td>
- <input type="text" maxLength="2"
- value={this.state.marginRight.toString()}
- onChange={this.handleNumericChange.bind(null, 'marginRight')}/> mm
- </td>
- </tr>
- <tr>
- <td />
- <td>
- <input type="text" maxLength="2"
- value={this.state.marginBottom.toString()}
- onChange={this.handleNumericChange.bind(null, 'marginBottom')}/>
- </td>
- <td />
- </tr>
- </tbody>
- </table>
- </td>
- </tr>
- }
- {this.state.advancedSettingsExpanded &&
- <tr>
- <td className="label">Resolution</td>
- <td><input type="text" maxLength="4" className="resolution"
- value={this.state.resolution.toString()}
- onChange={this.handleNumericChange.bind(null, 'resolution')}/> dpi
- </td>
- </tr>
- }
- {this.state.advancedSettingsExpanded &&
- <tr>
- <td className="label">Source zoom<br/>level</td>
- <td>
- <select value={this.state.zoomLevel} onChange={this.handleZoomChange}>
- <option value="auto">auto</option>
- {[7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].map((i) =>
- <option value={i.toString()} key={i.toString()}>{i.toString()}</option>
- )}
- </select>
- </td>
- </tr>
- }
- <tr>
- <td colSpan="2">
- <a className="button-settings image-button"
- onClick={this.toggleSettingsExpanded}/>
- <div className="settings-summary">
- {this.state.pageWidth} x {this.state.pageHeight} mm, <br/>
- {this.state.resolution} dpi, zoom {this.state.zoomLevel}
- {this.state.zoomLevel === 'auto' &&
- <span> (
- <span title="Zoom for satellite and scanned imagery" >{this.state.zoomSat}</span>
- /
- <span title="Zoom for maps like OSM and Google" >{this.state.zoomMap}</span>
- )</span>
- }
- </div>
- </td>
- </tr>
- <tr>
- <td colSpan="2">
- <a className="text-button button-save" onClick={this.props.onSavePdf.bind(null, this.state)}>
- Save PDF</a>
- </td>
- </tr>
-
- </tbody>
- </table>
- )
- }
-
- onDataChanged() {
- setTimeout(() => {this.props.onFormDataChanged(this.state)}, 0);
- }
-
- handleNumericChange = (stateField, e) => {
- const value = parseInt(e.target.value, 10);
- if (!isNaN(value)) {
- this.setState({[stateField]: value});
- this.onDataChanged();
- }
- };
-
- handleZoomChange = (e) => {
- this.setState({zoomLevel: e.target.value});
- this.onDataChanged();
- };
-
- toggleSettingsExpanded = (e) => {
- this.setState({advancedSettingsExpanded: !this.state.advancedSettingsExpanded});
- };
-
- handleScalePresetClicked = (scale) => {
- this.setState({scale});
- this.onDataChanged();
- };
-
- handleSizePresetClicked = (pageWidth, pageHeight) => {
- this.setState({pageWidth, pageHeight});
- this.onDataChanged();
- };
-
- setSuggestedZooms(zooms) {
- this.setState(zooms);
- }
-}
diff --git a/src/lib/knockout.component.progress/progress.js b/src/lib/knockout.component.progress/progress.js
@@ -0,0 +1,19 @@
+import ko from 'knockout';
+import './style.css'
+
+ko.components.register('progress-indicator', {
+ template: `
+ <div class="ko-progress-unknown" data-bind="visible: progress() === undefined"></div>
+ <div class="ko-progress" data-bind="visible: progress() !== undefined">
+ <div class="ko-progress-bkg">' +
+ <div class="ko-progress-bar" data-bind="style: {width: progress() + '%'}"></div>
+ </div>
+ </div>`,
+ viewModel: function(params) {
+ this.progress = ko.pureComputed(function() {
+ var range = params.progressRange(),
+ done = params.progressDone();
+ return done === undefined ? undefined : done * 100 / range;
+ }.bind(this));
+ }
+});
diff --git a/src/lib/knockout.component.progress/spinner.gif b/src/lib/knockout.component.progress/spinner.gif
Binary files differ.
diff --git a/src/lib/knockout.component.progress/style.css b/src/lib/knockout.component.progress/style.css
@@ -0,0 +1,33 @@
+.ko-progress-unknown {
+ display: inline-block;
+ height: 26px;
+ width: 26px;
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ border-radius: 4px 4px 4px 4px;
+ border: 1px solid #ccc;
+ background-image: url('spinner.gif');
+ margin-bottom: -5px;
+}
+
+.ko-progress {
+ border-radius: 4px 4px 4px 4px;
+ border: 1px solid #ccc;
+ height: 26px;
+ line-height: 26px;
+ vertical-align: middle;
+ padding: 0 4px;
+}
+
+.ko-progress-bkg {
+ display: inline-block;
+ width: 100%;
+ background-color: #ccc;
+ height: 10px;
+}
+
+.ko-progress-bar {
+ width: 30%;
+ background-color: #888;
+ height: 100% ;
+}