diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9a93687..a3b9c635 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+v0.6.42
+* Added "+ add field" button to web ui inspector in attribute editing mode.
+
+
v0.6.41
* Bug fixes
diff --git a/package-lock.json b/package-lock.json
index 0de1367d..0c8789e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mapshaper",
- "version": "0.6.41",
+ "version": "0.6.42",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mapshaper",
- "version": "0.6.41",
+ "version": "0.6.42",
"license": "MPL-2.0",
"dependencies": {
"@placemarkio/tokml": "^0.3.3",
diff --git a/package.json b/package.json
index 43c41cfa..deb9718f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mapshaper",
- "version": "0.6.41",
+ "version": "0.6.42",
"description": "A tool for editing vector datasets for mapping and GIS.",
"keywords": [
"shapefile",
diff --git a/src/cli/mapshaper-command-utils.mjs b/src/cli/mapshaper-command-utils.mjs
index 83209929..2bc97fd0 100644
--- a/src/cli/mapshaper-command-utils.mjs
+++ b/src/cli/mapshaper-command-utils.mjs
@@ -17,6 +17,16 @@ export function applyCommandToEachLayer(func, targetLayers) {
}, []);
}
+export function applyCommandToEachTarget(func, targets) {
+ var args = utils.toArray(arguments).slice(2);
+ targets.forEach(function(target) {
+ var result = func.apply(null, [target].concat(args));
+ if (result) {
+ error('Unexpected output from command');
+ }
+ });
+}
+
function isLayer(arg) {
return arg && (Array.isArray(arg.shapes) || !!arg.data || !!arg.geometry_type);
}
diff --git a/src/cli/mapshaper-run-command.mjs b/src/cli/mapshaper-run-command.mjs
index e220fbc6..d7b6b986 100644
--- a/src/cli/mapshaper-run-command.mjs
+++ b/src/cli/mapshaper-run-command.mjs
@@ -13,7 +13,7 @@ import { stop, error, UserError, verbose } from '../utils/mapshaper-logging';
import utils from '../utils/mapshaper-utils';
import cmd from '../mapshaper-cmd';
import { stashVar, clearStash } from '../mapshaper-stash';
-import { applyCommandToEachLayer } from '../cli/mapshaper-command-utils';
+import { applyCommandToEachLayer, applyCommandToEachTarget } from '../cli/mapshaper-command-utils';
import '../commands/mapshaper-add-shape';
import '../commands/mapshaper-affine';
@@ -92,7 +92,7 @@ import '../commands/mapshaper-subdivide';
function commandAcceptsMultipleTargetDatasets(name) {
return name == 'rotate' || name == 'info' || name == 'proj' || name == 'require' ||
name == 'drop' || name == 'target' || name == 'if' || name == 'elif' ||
- name == 'else' || name == 'endif' || name == 'run' || name == 'i';
+ name == 'else' || name == 'endif' || name == 'run' || name == 'i' || name == 'snap';
}
function commandAcceptsEmptyTarget(name) {
@@ -422,7 +422,8 @@ export async function runCommand(command, job) {
outputLayers = cmd.sliceLayers(targetLayers, source, targetDataset, opts);
} else if (name == 'snap') {
- cmd.snap(targetDataset, opts);
+ // cmd.snap(targetDataset, opts);
+ applyCommandToEachTarget(targets, opts);
} else if (name == 'sort') {
applyCommandToEachLayer(cmd.sortFeatures, targetLayers, arcs, opts);
diff --git a/src/commands/mapshaper-snap.mjs b/src/commands/mapshaper-snap.mjs
index 9dc77cf6..fad15286 100644
--- a/src/commands/mapshaper-snap.mjs
+++ b/src/commands/mapshaper-snap.mjs
@@ -7,9 +7,10 @@ import { stop, message } from '../utils/mapshaper-logging';
import cmd from '../mapshaper-cmd';
import utils from '../utils/mapshaper-utils';
-cmd.snap = function(dataset, opts) {
+cmd.snap = function(target, opts) {
var interval = 0;
var snapCount = 0;
+ var dataset = target.dataset;
var arcs = dataset.arcs;
var arcBounds = arcs && arcs.getBounds();
if (!arcBounds || !arcBounds.hasBounds()) {
diff --git a/src/gui/gui-alert.mjs b/src/gui/gui-alert.mjs
index b9d9242f..0a757ec4 100644
--- a/src/gui/gui-alert.mjs
+++ b/src/gui/gui-alert.mjs
@@ -4,21 +4,26 @@ export function showPopupAlert(msg, title) {
var self = {}, html = '';
var _cancel, _close;
var warningRxp = /^Warning: /;
- var el = El('div').appendTo('body').addClass('error-wrapper');
- var infoBox = El('div').appendTo(el).addClass('error-box info-box selectable');
+ var el = El('div').appendTo('body').addClass('alert-wrapper');
+ var infoBox = El('div').appendTo(el).addClass('alert-box info-box selectable');
+ El('div').appendTo(infoBox).addClass('close2-btn').on('click', function() {
+ if (_cancel) _cancel();
+ self.close();
+ });
+ var container = El('div').appendTo(infoBox);
if (!title && warningRxp.test(msg)) {
title = 'Warning';
msg = msg.replace(warningRxp, '');
}
if (title) {
- html += `
${title}
`;
+ El('div').addClass('alert-title').text(title).appendTo(container);
}
- html += `${msg}
`;
- El('div').appendTo(infoBox).addClass('close2-btn').on('click', function() {
- if (_cancel) _cancel();
- self.close();
- });
- El('div').appendTo(infoBox).addClass('error-content').html(html);
+ var content = El('div').appendTo(infoBox);
+ if (msg) {
+ content.html(`${msg}
`);
+ }
+
+ self.container = function() { return content; };
self.onCancel = function(cb) {
_cancel = cb;
diff --git a/src/gui/gui-popup.mjs b/src/gui/gui-popup.mjs
index 583313fd..6a1ce4ad 100644
--- a/src/gui/gui-popup.mjs
+++ b/src/gui/gui-popup.mjs
@@ -3,6 +3,7 @@ import { utils, internal } from './gui-core';
import { El } from './gui-el';
import { GUI } from './gui-lib';
import { ClickText2 } from './gui-elements';
+import { showPopupAlert } from './gui-alert';
// toNext, toPrev: trigger functions for switching between multiple records
export function Popup(gui, toNext, toPrev) {
@@ -35,8 +36,7 @@ export function Popup(gui, toNext, toPrev) {
// stash a function for refreshing the current popup when data changes
// while the popup is being displayed (e.g. while dragging a label)
refresh = function() {
- var rec = table && (editable ? table.getRecordAt(id) : table.getReadOnlyRecordAt(id)) || {};
- render(content, rec, table, editable);
+ render(content, id, table, editable);
};
refresh();
if (ids && ids.length > 1) {
@@ -75,7 +75,8 @@ export function Popup(gui, toNext, toPrev) {
tab.show();
}
- function render(el, rec, table, editable) {
+ function render(el, recId, table, editable) {
+ var rec = table && (editable ? table.getRecordAt(recId) : table.getReadOnlyRecordAt(recId)) || {};
var tableEl = El('table').addClass('selectable'),
rows = 0;
// self.hide(); // clean up if panel is already open
@@ -90,11 +91,6 @@ export function Popup(gui, toNext, toPrev) {
}
});
- // add new field form
- if (editable) {
- renderNewRow(tableEl, rec, table);
- }
-
tableEl.appendTo(el);
if (rows > 0) {
// tableEl.appendTo(el);
@@ -119,15 +115,58 @@ export function Popup(gui, toNext, toPrev) {
}
if (editable) {
+ // render "add field" button
var line = El('div').appendTo(el);
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
-
- }).text('add field');
+ // show "add field" dialog
+ renderAddFieldPopup(recId, table);
+ }).text('+ add field');
}
}
- function renderNewRow(el, rec, table) {
+ function renderAddFieldPopup(recId, table) {
+ var popup = showPopupAlert('', 'Add field');
+ var el = popup.container();
+ el.addClass('option-menu');
+ var html = `
+
+ Apply
assign value to all records`;
+ el.html(html);
+
+ var name = el.findChild('.field-name');
+ name.node().focus();
+ var val = el.findChild('.field-value');
+ var box = el.findChild('.all');
+ var btn = el.findChild('.btn').on('click', function() {
+ var all = box.node().checked;
+ var nameStr = name.node().value.trim();
+ if (!nameStr) return;
+ if (table.fieldExists(nameStr)) {
+ name.node().value = '';
+ return;
+ }
+ var valStr = val.node().value.trim();
+ var value = parseUnknownType(valStr);
+ // table.addField(nameStr, function(d) {
+ // // parse each time to avoid multiple references to objects
+ // return (all || d == rec) ? parseUnknownType(valStr) : null;
+ // });
+
+ var cmdStr = `-each "d['${nameStr}'] = `;
+ if (!all) {
+ cmdStr += `this.id != ${recId} ? null : `;
+ }
+ valStr = JSON.stringify(JSON.stringify(value));
+ cmdStr = valStr.replace('"', cmdStr);
+ gui.console.runMapshaperCommands(cmdStr, function(err) {
+ if (!err) {
+ popup.close();
+ } else {
+ console.error(err);
+ }
+ });
+ });
}
function renderRow(table, rec, key, type, editable) {
@@ -163,12 +202,10 @@ export function Popup(gui, toNext, toPrev) {
input.on('change', function(e) {
var val2 = parser(input.value()),
strval2 = formatInspectorValue(val2, type);
- if (strval == strval2) {
- // contents unchanged
- } else if (val2 === null && type != 'object') { // allow null objects
+ if (val2 === null && type != 'object') { // allow null objects
// invalid value; revert to previous value
input.value(strval);
- } else {
+ } else if (strval != strval2) {
// field content has changed
strval = strval2;
gui.dispatchEvent('data_preupdate', {FID: currId}); // for undo/redo
@@ -233,6 +270,14 @@ var inputParsers = {
}
};
+function parseUnknownType(str) {
+ var val = inputParsers.number(str);
+ if (val !== null) return val;
+ val = inputParsers.object(str);
+ if (val !== null) return val;
+ return str;
+}
+
function getInputParser(type) {
return inputParsers[type || 'multiple'];
}
diff --git a/www/index.html b/www/index.html
index 3c1a668a..1afb3425 100644
--- a/www/index.html
+++ b/www/index.html
@@ -172,7 +172,7 @@ File format
Export
- choose directory
+ choose directory
diff --git a/www/page.css b/www/page.css
index 02534cd6..ea315600 100644
--- a/www/page.css
+++ b/www/page.css
@@ -256,7 +256,7 @@ body {
/* --- Error message ---------- */
-.error-wrapper {
+.alert-wrapper {
z-index: 120;
text-align: center;
position: absolute;
@@ -266,29 +266,33 @@ body {
left: 0;
}
-.error-wrapper p.error-message {
+.alert-wrapper p.error-message {
margin: 1px 0 0 0;
}
-.error-wrapper .dialog-btn {
- margin-top: 8px;
-}
-
-.error-title {
+.alert-title {
line-height: 1.1;
font-weight: bold;
margin-bottom: 5px;
}
-div.error-box {
+div.alert-title, div.error-message {
+ font-size: 16px;
+}
+
+.alert-wrapper .alert-btn {
+ margin-top: 8px;
+}
+
+div.alert-box {
margin-top: 42px; /* 55px; */
overflow: auto;
max-height: 70%;
max-width: 400px;
- font-size: 16px;
}
+
/* --- Splash screen -------------- */
.splash-screen #splash-screen {
@@ -513,7 +517,7 @@ body.dragover #import-options-drop-area .drop-area {
text-align: left;
margin-top: 12px;
margin-right: 20px;
- padding: 12px 16px 12px 18px;
+ padding: 11px 16px 12px 18px;
vertical-align: top;
display: inline-block;
/* border: 1px solid #aaa; */
@@ -553,6 +557,7 @@ body.dragover #import-options-drop-area .drop-area {
top: 1px;
width: 12px;
height: 12px;
+ cursor: pointer;
}
.info-box .tip-button {
@@ -800,7 +805,7 @@ img.close-btn {
float: right;
height: 18px;
width: 18px;
- margin-top: 1px;
+ /* margin-top: 1px; */
margin-right: -3px;
cursor: pointer;
border-radius: 4px;
@@ -1227,13 +1232,16 @@ div.basemap-style-btn.active img {
top: 23px;
}
-#save-preference {
- display: none;
+.inline-checkbox {
position: relative;
top: 1px;
left: 5px;
}
+#save-preference {
+ /* display: none; */
+}
+
.nav-sub-menu {
position: absolute;
z-index: -1;
@@ -1316,25 +1324,32 @@ div.basemap-style-btn.active img {
background: white;
}
-.save-menu-btn {
+/*.save-menu-btn {
display: inline-block;
border-radius: 4px;
border: 1px solid #aaa;
font-size: 12px;
margin-left: 2px;
padding: 1px 2px 3px 2px;
-}
+}*/
-.save-menu-link,
.save-menu-btn {
- cursor: pointer;
+ display: inline-block;
+ font-size: 12px;
+ margin-top: 2px;
}
.save-menu-btn:hover {
- background: #e6f7ff;
color: black;
}
+.save-menu-link,
+.save-menu-btn {
+ cursor: pointer;
+}
+
+
+
.save-menu-link:hover {
/* background: #e6f7ff; */
font-weight: bold;