Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 61 additions & 51 deletions modules/behavior/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,71 @@ import { select as d3_select } from 'd3-selection';
import { geoSphericalDistance } from '../geo';
import { modeBrowse } from '../modes/browse';
import { modeSelect, modeSelectNote } from '../modes';
import { utilObjectOmit, utilQsString, utilStringQs } from '../util';
import { utilQsString, utilStringQs } from '../util';
import { utilArrayIdentical } from '../util/array';
import { utilDisplayLabel } from '../util/utilDisplayLabel';
import { localizer, t } from '../core/localizer';
import { prefs } from '../core/preferences';

function getNewHash(arg) {
const original = utilStringQs(window.location.hash);
const update = typeof arg === 'function' ? arg(original) : arg;
if (!update || typeof update !== 'object') return;

const updated = { ...original, ...update };
Object.keys(update)
.filter(key => update[key] === null || update[key] === undefined)
.forEach(key => delete updated[key]);

return '#' + utilQsString(updated, true);
}

/**
* Updates the URL hash by applying a partial patch.
*
* Keys with nullish values will be removed from the hash.
*
* @param {(?Object<string, any>|function (Object<string, any>): Object<string, any>)} updater Either
* - a plain object of key/value pairs to merge into the hash, or
* - a function `(currentHash) => patchObject` that returns such an object.
* @returns {boolean} Whether the hash was updated.
*/
export function patchHash(updater) {
if (!updater || !['function', 'object'].includes(typeof updater)) return false;

const latestHash = getNewHash(updater);
if (!latestHash || window.location.hash === latestHash) return false;

// Update the URL hash without affecting the browser navigation stack,
// though unavoidably creating a browser history entry
window.history.replaceState(null, '', latestHash);

// save last used map location for future
const { map } = utilStringQs(latestHash);
if (map) prefs('map-location', map);
return true;
}

export function behaviorHash(context) {

// cached window.location.hash
var _cachedHash = null;
// allowable latitude range
var _latitudeLimit = 90 - 1e-8;

function computedHashParameters() {
function computeHashUpdate() {
if (context.inIntro()) return null;

var map = context.map();
var center = map.center();
var zoom = map.zoom();
var precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
var oldParams = utilObjectOmit(utilStringQs(window.location.hash),
['comment', 'source', 'hashtags', 'walkthrough']
);
var newParams = {};
const newParams = {
comment: null,
source: null,
hashtags: null,
walkthrough: null,
id: null
};

delete oldParams.id;
var selected = context.selectedIDs().filter(function(id) {
return context.hasEntity(id);
});
Expand All @@ -43,11 +83,7 @@ export function behaviorHash(context) {
'/' + center[1].toFixed(precision) +
'/' + center[0].toFixed(precision);

return Object.assign(oldParams, newParams);
}

function computedHash() {
return '#' + utilQsString(computedHashParameters(), true);
return newParams;
}

function computedTitle(includeChangeCount) {
Expand Down Expand Up @@ -91,7 +127,7 @@ export function behaviorHash(context) {
return baseTitle;
}

function updateTitle(includeChangeCount) {
function updateTitle(includeChangeCount = true) {
if (!context.setsDocumentTitle()) return;

var newTitle = computedTitle(includeChangeCount);
Expand All @@ -100,40 +136,14 @@ export function behaviorHash(context) {
}
}

function updateHashIfNeeded() {
if (context.inIntro()) return;

var latestHash = computedHash();
if (_cachedHash !== latestHash) {
_cachedHash = latestHash;

// Update the URL hash without affecting the browser navigation stack,
// though unavoidably creating a browser history entry
window.history.replaceState(null, '', latestHash);

// set the title we want displayed for the browser tab/window
updateTitle(true /* includeChangeCount */);

// save last used map location for future
const q = utilStringQs(latestHash);
if (q.map) {
prefs('map-location', q.map);
}
}
}

var _throttledUpdate = throttle(updateHashIfNeeded, 500);
var _throttledUpdateTitle = throttle(function() {
updateTitle(true /* includeChangeCount */);
var _throttledUpdate = throttle(() => {
patchHash(computeHashUpdate);
updateTitle();
}, 500);
var _throttledUpdateTitle = throttle(updateTitle, 500);

function hashchange() {
// ignore spurious hashchange events
if (window.location.hash === _cachedHash) return;

_cachedHash = window.location.hash;

var q = utilStringQs(_cachedHash);
var q = utilStringQs(window.location.hash);

if (q.theme) {
context.theme(q.theme);
Expand All @@ -147,11 +157,11 @@ export function behaviorHash(context) {
var mapArgs = (q.map || '').split('/').map(Number);
if (mapArgs.length < 3 || mapArgs.some(isNaN)) {
// replace bogus hash
updateHashIfNeeded();

patchHash(computeHashUpdate);
updateTitle();
} else {
// don't update if the new hash already reflects the state of iD
if (_cachedHash === computedHash()) return;
if (window.location.hash === getNewHash(computeHashUpdate)) return;

var mode = context.mode();

Expand Down Expand Up @@ -224,14 +234,14 @@ export function behaviorHash(context) {
const mapArgs = prefs('map-location').split('/').map(Number);
context.map().centerZoom([mapArgs[2], Math.min(_latitudeLimit, Math.max(-_latitudeLimit, mapArgs[1]))], mapArgs[0]);

updateHashIfNeeded();
patchHash(computeHashUpdate);

behavior.hadLocation = true;
}

hashchange();

updateTitle(false);
updateTitle(false /* includeChangeCount */);
}

behavior.off = function() {
Expand Down
2 changes: 1 addition & 1 deletion modules/behavior/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export { behaviorDrag } from './drag';
export { behaviorDrawWay } from './draw_way';
export { behaviorDraw } from './draw';
export { behaviorEdit } from './edit';
export { behaviorHash } from './hash';
export { behaviorHash, patchHash } from './hash';
export { behaviorHover } from './hover';
export { behaviorLasso } from './lasso';
export { behaviorOperation } from './operation';
Expand Down
29 changes: 8 additions & 21 deletions modules/renderer/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { fileFetcher } from '../core/file_fetcher';
import { geoMetersToOffset, geoOffsetToMeters, geoExtent } from '../geo';
import { rendererBackgroundSource } from './background_source';
import { rendererTileLayer } from './tile_layer';
import { utilQsString, utilStringQs } from '../util';
import { utilStringQs } from '../util';
import { utilRebind } from '../util/rebind';
import { patchHash } from '../behavior';


let _imageryIndex = null;
Expand Down Expand Up @@ -200,32 +201,18 @@ export function rendererBackground(context) {
const EPSILON = 0.01;
const x = +meters[0].toFixed(2);
const y = +meters[1].toFixed(2);
let hash = utilStringQs(window.location.hash);
const notableOffset = Math.abs(x) > EPSILON || Math.abs(y) > EPSILON;

let id = currSource.id;
if (id === 'custom') {
id = `custom:${currSource.template()}`;
}

if (id) {
hash.background = id;
} else {
delete hash.background;
}

if (o) {
hash.overlays = o;
} else {
delete hash.overlays;
}

if (Math.abs(x) > EPSILON || Math.abs(y) > EPSILON) {
hash.offset = `${x},${y}`;
} else {
delete hash.offset;
}

window.history.replaceState(null, '', '#' + utilQsString(hash, true));
patchHash({
background: id || null,
overlays: o || null,
offset: notableOffset ? `${x},${y}` : null
});

let imageryUsed = [];
let photoOverlaysUsed = [];
Expand Down
15 changes: 5 additions & 10 deletions modules/renderer/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { prefs } from '../core/preferences';
import { osmEntity } from '../osm';
import { osmLanduseTags, osmLifecyclePrefixes } from '../osm/tags.js';
import { utilRebind } from '../util/rebind';
import { utilArrayGroupBy, utilArrayUnion, utilQsString, utilStringQs } from '../util';
import { utilArrayGroupBy, utilArrayUnion, utilStringQs } from '../util';
import { isAddressPoint } from '../svg/labels';
import { patchHash } from '../behavior';


export function rendererFeatures(context) {
Expand Down Expand Up @@ -56,15 +57,9 @@ export function rendererFeatures(context) {


function update() {
const hash = utilStringQs(window.location.hash);
const disabled = features.disabled();
if (disabled.length) {
hash.disable_features = disabled.join(',');
} else {
delete hash.disable_features;
}
window.history.replaceState(null, '', '#' + utilQsString(hash, true));
prefs('disabled-features', disabled.join(','));
const disabled = features.disabled().join(',');
patchHash({ disable_features: disabled || null });
prefs('disabled-features', disabled);
_hidden = features.hidden();
dispatch.call('change');
dispatch.call('redraw');
Expand Down
21 changes: 4 additions & 17 deletions modules/renderer/photos.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { dispatch as d3_dispatch } from 'd3-dispatch';

import { services } from '../services';
import { utilRebind } from '../util/rebind';
import { utilQsString, utilStringQs } from '../util';
import { utilStringQs } from '../util';
import { patchHash } from '../behavior';


export function rendererPhotos(context) {
Expand All @@ -18,18 +19,12 @@ export function rendererPhotos(context) {
function photos() {}

function updateStorage() {
var hash = utilStringQs(window.location.hash);
var enabled = context.layers().all().filter(function(d) {
return _layerIDs.indexOf(d.id) !== -1 && d.layer && d.layer.supported() && d.layer.enabled();
}).map(function(d) {
return d.id;
});
if (enabled.length) {
hash.photo_overlay = enabled.join(',');
} else {
delete hash.photo_overlay;
}
window.history.replaceState(null, '', '#' + utilQsString(hash, true));
patchHash({ photo_overlay: enabled.join(',') || null });
}

/**
Expand Down Expand Up @@ -148,15 +143,7 @@ export function rendererPhotos(context) {
* @param {string} property Name of the value
*/
function setUrlFilterValue(property, val) {
const hash = utilStringQs(window.location.hash);
if (val) {
if (hash[property] === val) return;
hash[property] = val;
} else {
if (!(property in hash)) return;
delete hash[property];
}
window.history.replaceState(null, '', '#' + utilQsString(hash, true));
patchHash({ [property]: val || null });
}

function showsLayer(id) {
Expand Down
18 changes: 4 additions & 14 deletions modules/services/kartaview.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom';
import RBush from 'rbush';

import { geoExtent, geoScaleToZoom } from '../geo';
import { utilQsString, utilRebind, utilSetTransform, utilStringQs, utilTiler } from '../util';
import { utilQsString, utilRebind, utilSetTransform, utilTiler } from '../util';
import { services } from './';
import { searchLimited } from '../util/partition';
import { localeDateString } from '../util/date';
import { patchHash } from '../behavior';


var apibase = 'https://kartaview.org';
Expand Down Expand Up @@ -363,7 +364,7 @@ export default {
hideViewer: function(context) {
_oscSelectedImage = null;

this.updateUrlImage(null);
patchHash({ photo: null });

var viewer = context.container().select('.photoviewer');
if (!viewer.empty()) viewer.datum(null);
Expand All @@ -386,7 +387,7 @@ export default {

_oscSelectedImage = d;

this.updateUrlImage(imageKey);
patchHash({ photo: 'kartaview/' + imageKey });

var viewer = context.container().select('.photoviewer');
if (!viewer.empty()) viewer.datum(d);
Expand Down Expand Up @@ -517,17 +518,6 @@ export default {
},


updateUrlImage: function(imageKey) {
const hash = utilStringQs(window.location.hash);
if (imageKey) {
hash.photo = 'kartaview/' + imageKey;
} else {
delete hash.photo;
}
window.history.replaceState(null, '', '#' + utilQsString(hash, true));
},


cache: function() {
return _oscCache;
}
Expand Down
Loading