nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit 7afc05a2bd3da5b14f9a3b5dbce8f3d281a4c664
parent 2034e4329dd63ccafdcdd66f19fd25a31caf9be3
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Mon,  7 Nov 2016 11:07:30 +0300

replaced react with knockout

Diffstat:
Msrc/lib/control.printPages/control.js | 100+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Asrc/lib/control.printPages/form.html | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/control.printPages/form.js | 197-------------------------------------------------------------------------------
Asrc/lib/knockout.component.progress/progress.js | 19+++++++++++++++++++
Asrc/lib/knockout.component.progress/spinner.gif | 0
Asrc/lib/knockout.component.progress/style.css | 33+++++++++++++++++++++++++++++++++
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">&nbsp;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>&nbsp;x&nbsp;<span data-bind="text: pageHeight"></span>&nbsp;mm,<br/> + <span data-bind="text: resolution"></span>&nbsp;dpi, + zoom&nbsp;<span data-bind="text: zoomLevel"></span> + <!-- ko if: zoomLevel()=== "auto" --> + (<span title="Zoom for satellite and scanned imagery" data-bind="text: autoZoomLevels().satZoom"></span>&nbsp; + /&nbsp;<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')} - />&nbsp;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')}/> - &nbsp;x&nbsp; - <input type="text" maxLength="4" title="height" placeholder="height" className="page-size" - value={this.state.pageHeight.toString()} - onChange={this.handleNumericChange.bind(null, 'pageHeight')}/> - &nbsp;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')}/>&nbsp;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')}/>&nbsp;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> - &nbsp;/&nbsp; - <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% ; +}