simonw / datasette-cluster-map

Datasette plugin that shows a map for any data with latitude/longitude columns
Apache License 2.0
88 stars 16 forks source link

Update fragment hash in URL so you can link to zoomed map #13

Open simonw opened 4 years ago

simonw commented 4 years ago

Should this add to the history so that the back button works? Yes, maybe - but only if it's severely rare limited, maybe adding a history checkpoint every few seconds.

simonw commented 4 years ago

https://github.com/kluizeberg/Leaflet.Map-hash does exactly what I want here. It's not rate-limited but it doesn't affect browser history so that's not a problem.

simonw commented 4 years ago

I'm going to enable this by default but allow users to disable it using a plugin configuration setting.

simonw commented 4 years ago

One catch: that plugin doesn't play well with the changes datasette-vega makes to the URL.

simonw commented 4 years ago

datasette-vega has functions that specifically try to play well with other plugins through namespacing (the #g.xxx convention): https://github.com/simonw/datasette-vega/blob/1db45bd8f1c3c17f8b05aba7159542bcaffffad8/src/DatasetteVega.js#L5-L22

const serialize = (obj, prefix) => Object.keys(obj).filter(key => obj[key]).map(
  key => `${prefix}.${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`
).join('&');

const unserialize = (s, prefix) => {
  if (s && s[0] === '#') {
    s = s.slice(1);
  }
  if (!s) {
    return {};
  }
  var obj = {};
  s.split('&').filter(bit => bit.slice(0, prefix.length + 1) === `${prefix}.`).forEach(bit => {
    let pair = bit.split('=');
    obj[decodeURIComponent(pair[0]).replace(new RegExp(`^${prefix}\\.`), '')] = decodeURIComponent(pair[1]);
  });
  return obj;
};
simonw commented 4 years ago

I'm going to use a cm prefix (for cluster-map).

glasnt commented 3 years ago

I had a go at this (I have a datasette deployment with the hashmap working with some edits to the map-hash branch), but trying to get different plugins operating within the same document.location.hash is going to take edits.

Specifically in this case: datasette-vega may have namespacing, but it overrides the entire hashmap.

https://github.com/simonw/datasette-vega/blob/master/src/DatasetteVega.js#L168

document.location.hash = '#' + this.serializeState();

(where serializeState contains only vega-specific state)

I suspect alterations to the vendored leaflet-map-hash will have to also happen, as that script directly removes any hash data

https://github.com/simonw/datasette-cluster-map/commit/de8d788d0655a1c6e16742fdb99b34dd9ce2e6df#diff-887aaf7973cd28479fd3326e3777360fb835b5e8d1fe00f55b4a849d54b6dcbcR63-R67

window.history.replaceState(null, '', '#' + [ // no history
    'lng='  + center.lng.toFixed(decimals),
    'lat='  + center.lat.toFixed(decimals),
    'zoom=' + zoom
].join(';'));

I suspect changes to onPopStateChange will also need to happen, as you can no longer expect the hash to contain only the state of the current plugin.

https://github.com/simonw/datasette-vega/blob/1db45bd8f1c3c17f8b05aba7159542bcaffffad8/src/DatasetteVega.js#L125-L131

  onPopStateChange(ev) {
    window.lastPopEv = ev;
    const expected = '#' + this.serializeState();
    if (expected !== document.location.hash && this.state.mark) {
      this.setState(
        unserialize(document.location.hash, 'g'), this.renderGraph.bind(this)
      );
    }
  }

I can have a go at this, if this isn't in your immediate radar.

I suspect my implementation would require some alteration to the current serialise/unserialise methods, as I think there would have to be context-aware serialisation for the plugins to actually work together. (I'm also presuming datasette-vega is using g for graph, and datasette-cluster-map is using cm for cluster-map)