Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Neuroglancer Auto Import Settings
- // @namespace eyewire-elite
- // @version 2.2.0
- // @description Safely auto-imports named Neuroglancer presets on newly opened links after the viewer stabilizes, but never on refresh.
- // @match https://spelunker.cave-explorer.org/*
- // @run-at document-idle
- // @grant unsafeWindow
- // ==/UserScript==
- (function () {
- 'use strict';
- const CONFIG = Object.freeze({
- /*
- * API exposed by the matched Import/export settings script.
- */
- importerApiName:
- 'NGSettingsPresets',
- /*
- * New three-slot storage.
- */
- settingsStorageKey:
- 'ng-settings-3-slots',
- /*
- * Never import immediately.
- */
- minimumWaitMilliseconds:
- 3000,
- /*
- * Viewer state must remain unchanged
- * for this long.
- */
- requiredStableMilliseconds:
- 2500,
- /*
- * How often readiness is checked.
- */
- checkIntervalMilliseconds:
- 250,
- /*
- * Stop waiting for the actual viewer
- * after this amount of time.
- */
- maximumWaitMilliseconds:
- 45000,
- /*
- * Once the viewer is stable, give the
- * Import/Export userscript a little extra
- * time to expose its API if necessary.
- */
- importerApiWaitMilliseconds:
- 5000
- });
- // ==========================================================================
- // RELOAD PROTECTION
- // ==========================================================================
- /*
- * Never auto-import after:
- *
- * - F5
- * - Ctrl+F5
- * - Ctrl+R
- * - browser Reload button
- *
- * A genuinely newly opened link is allowed.
- */
- if (
- isReloadNavigation()
- ) {
- console.log(
- '[Auto import] Page reload detected. Automatic settings import skipped.'
- );
- return;
- }
- // ==========================================================================
- // SAVED-PRESET CHECK
- // ==========================================================================
- /*
- * Look specifically for the new three-slot store.
- *
- * The Import/Export script itself handles migration
- * from old ng-settings if needed.
- */
- if (
- !hasSavedPresets()
- ) {
- console.log(
- '[Auto import] No saved named presets found. Viewer will remain unchanged.'
- );
- return;
- }
- const startedAt =
- performance.now();
- let lastSnapshot =
- null;
- let stableSince =
- null;
- let importTriggered =
- false;
- const intervalId =
- window.setInterval(
- checkViewer,
- CONFIG
- .checkIntervalMilliseconds
- );
- // ==========================================================================
- // NAVIGATION TYPE
- // ==========================================================================
- function isReloadNavigation() {
- try {
- const entries =
- performance
- .getEntriesByType(
- 'navigation'
- );
- if (
- Array.isArray(entries) &&
- entries.length > 0
- ) {
- return (
- entries[0].type ===
- 'reload'
- );
- }
- } catch (error) {
- console.warn(
- '[Auto import] Could not read Navigation Timing data:',
- error
- );
- }
- try {
- if (
- performance.navigation &&
- performance.navigation.type === 1
- ) {
- return true;
- }
- } catch (_) {
- // Ignore old-browser fallback failure.
- }
- return false;
- }
- // ==========================================================================
- // SAVED PRESETS
- // ==========================================================================
- function hasSavedPresets() {
- try {
- const text =
- localStorage.getItem(
- CONFIG
- .settingsStorageKey
- );
- if (!text) {
- /*
- * The three-slot script may not yet have had a
- * chance to migrate the old single ng-settings
- * value during this page load.
- *
- * If the legacy key exists, allow Auto Import
- * to continue waiting. The importer will migrate it.
- */
- return Boolean(
- localStorage.getItem(
- 'ng-settings'
- )
- );
- }
- const parsed =
- JSON.parse(text);
- return Boolean(
- parsed &&
- Array.isArray(
- parsed.slots
- ) &&
- parsed.slots.some(
- slot =>
- Boolean(
- slot &&
- slot.settings
- )
- )
- );
- } catch (error) {
- console.warn(
- '[Auto import] Could not inspect saved presets:',
- error
- );
- return false;
- }
- }
- // ==========================================================================
- // VIEWER STABILITY
- // ==========================================================================
- function checkViewer() {
- if (
- importTriggered
- ) {
- window.clearInterval(
- intervalId
- );
- return;
- }
- const now =
- performance.now();
- const totalElapsed =
- now -
- startedAt;
- if (
- totalElapsed >=
- CONFIG.maximumWaitMilliseconds
- ) {
- window.clearInterval(
- intervalId
- );
- console.warn(
- '[Auto import] Viewer did not reach a stable state within ' +
- Math.round(
- CONFIG.maximumWaitMilliseconds /
- 1000
- ) +
- ' seconds. Automatic import skipped; press / manually.'
- );
- return;
- }
- if (
- totalElapsed <
- CONFIG.minimumWaitMilliseconds
- ) {
- return;
- }
- const pageViewer =
- getPageViewer();
- if (
- !isViewerReady(
- pageViewer
- )
- ) {
- resetStability();
- return;
- }
- if (
- isLoginPromptVisible()
- ) {
- resetStability();
- return;
- }
- let snapshot;
- try {
- snapshot =
- createMeaningfulSnapshot(
- pageViewer
- );
- } catch (_) {
- resetStability();
- return;
- }
- if (
- snapshot !==
- lastSnapshot
- ) {
- lastSnapshot =
- snapshot;
- stableSince =
- now;
- return;
- }
- if (
- stableSince === null
- ) {
- stableSince =
- now;
- return;
- }
- const stableElapsed =
- now -
- stableSince;
- if (
- stableElapsed >=
- CONFIG.requiredStableMilliseconds
- ) {
- /*
- * Stop the stability monitor BEFORE calling
- * the importer so it can never trigger twice.
- */
- importTriggered =
- true;
- window.clearInterval(
- intervalId
- );
- console.log(
- '[Auto import] Viewer stabilized after ' +
- (
- totalElapsed /
- 1000
- ).toFixed(2) +
- ' seconds.'
- );
- callImporterWhenReady();
- }
- }
- // ==========================================================================
- // DIRECT IMPORTER API
- // ==========================================================================
- async function callImporterWhenReady() {
- const deadline =
- performance.now() +
- CONFIG
- .importerApiWaitMilliseconds;
- let api =
- getImporterApi();
- while (
- !api &&
- performance.now() <
- deadline
- ) {
- await sleep(100);
- api =
- getImporterApi();
- }
- if (
- !api ||
- typeof api.importSettings !==
- 'function'
- ) {
- console.warn(
- '[Auto import] Import/export preset API was not available. Press / manually.'
- );
- return;
- }
- let presets =
- [];
- try {
- if (
- typeof api.getPresets ===
- 'function'
- ) {
- presets =
- api.getPresets() ||
- [];
- }
- } catch (error) {
- console.warn(
- '[Auto import] Could not read preset list:',
- error
- );
- }
- if (
- presets.length === 0
- ) {
- console.log(
- '[Auto import] No presets remain to import.'
- );
- return;
- }
- if (
- presets.length === 1
- ) {
- console.log(
- '[Auto import] One preset exists. Automatically importing "' +
- presets[0].name +
- '".'
- );
- } else {
- console.log(
- '[Auto import] ' +
- presets.length +
- ' presets exist. Asking which state to import.'
- );
- }
- /*
- * IMPORTANT:
- *
- * Direct API call.
- *
- * We DO NOT synthesize the "/" key anymore.
- * This means exactly one restore operation is requested.
- */
- try {
- const result =
- api.importSettings({
- source:
- 'auto',
- quietWhenEmpty:
- true
- });
- if (
- result &&
- result.ok
- ) {
- console.log(
- '[Auto import] Settings import completed from "' +
- result.name +
- '".'
- );
- } else {
- console.log(
- '[Auto import] No automatic settings import was performed.',
- result
- );
- }
- } catch (error) {
- console.error(
- '[Auto import] Import API call failed:',
- error
- );
- }
- }
- function getImporterApi() {
- /*
- * The Import/Export script uses @grant none,
- * so its API exists in the actual page window.
- */
- try {
- if (
- typeof unsafeWindow !==
- 'undefined' &&
- unsafeWindow &&
- unsafeWindow[
- CONFIG.importerApiName
- ]
- ) {
- return unsafeWindow[
- CONFIG.importerApiName
- ];
- }
- } catch (_) {
- // Continue to window fallback.
- }
- try {
- return (
- window[
- CONFIG.importerApiName
- ] ||
- null
- );
- } catch (_) {
- return null;
- }
- }
- // ==========================================================================
- // VIEWER ACCESS
- // ==========================================================================
- function getPageViewer() {
- try {
- if (
- typeof unsafeWindow !==
- 'undefined' &&
- unsafeWindow &&
- unsafeWindow.viewer
- ) {
- return unsafeWindow.viewer;
- }
- } catch (_) {
- // Continue.
- }
- try {
- if (
- window.viewer
- ) {
- return window.viewer;
- }
- } catch (_) {
- // Viewer not ready.
- }
- return null;
- }
- function isViewerReady(
- pageViewer
- ) {
- try {
- if (
- !pageViewer ||
- !pageViewer.state ||
- typeof pageViewer
- .state
- .toJSON !==
- 'function'
- ) {
- return false;
- }
- const state =
- pageViewer
- .state
- .toJSON();
- if (
- !state ||
- !Array.isArray(
- state.layers
- ) ||
- state.layers.length === 0
- ) {
- return false;
- }
- const hasSegmentation =
- state.layers.some(
- layer =>
- Boolean(
- layer &&
- layer.type ===
- 'segmentation' &&
- layer.source
- )
- );
- if (
- !hasSegmentation
- ) {
- return false;
- }
- const topBar =
- document.querySelector(
- '.neuroglancer-viewer-top-row'
- );
- const renderedPanel =
- document.querySelector(
- '.neuroglancer-rendered-data-panel, canvas'
- );
- return Boolean(
- topBar &&
- renderedPanel
- );
- } catch (_) {
- return false;
- }
- }
- // ==========================================================================
- // STABILITY SNAPSHOT
- // ==========================================================================
- function createMeaningfulSnapshot(
- pageViewer
- ) {
- const state =
- pageViewer
- .state
- .toJSON();
- const layers =
- state.layers.map(
- layer => {
- const result = {
- name:
- layer.name ||
- '',
- type:
- layer.type ||
- '',
- source:
- normalizeSource(
- layer.source
- ),
- visible:
- Object.prototype
- .hasOwnProperty.call(
- layer,
- 'visible'
- )
- ? layer.visible
- : null
- };
- if (
- layer.type ===
- 'segmentation'
- ) {
- result.segments =
- Array.isArray(
- layer.segments
- )
- ? layer.segments.map(
- String
- )
- : [];
- result.segmentCount =
- result
- .segments
- .length;
- }
- if (
- layer.type ===
- 'annotation'
- ) {
- result.annotationCount =
- Array.isArray(
- layer.annotations
- )
- ? layer
- .annotations
- .length
- : 0;
- }
- return result;
- }
- );
- const snapshot = {
- url:
- window.location.href,
- dimensions:
- Object.prototype
- .hasOwnProperty.call(
- state,
- 'dimensions'
- )
- ? state.dimensions
- : null,
- position:
- Object.prototype
- .hasOwnProperty.call(
- state,
- 'position'
- )
- ? state.position
- : null,
- crossSectionScale:
- Object.prototype
- .hasOwnProperty.call(
- state,
- 'crossSectionScale'
- )
- ? state.crossSectionScale
- : null,
- projectionScale:
- Object.prototype
- .hasOwnProperty.call(
- state,
- 'projectionScale'
- )
- ? state.projectionScale
- : null,
- projectionDepth:
- Object.prototype
- .hasOwnProperty.call(
- state,
- 'projectionDepth'
- )
- ? state.projectionDepth
- : null,
- projectionOrientation:
- Object.prototype
- .hasOwnProperty.call(
- state,
- 'projectionOrientation'
- )
- ? state.projectionOrientation
- : null,
- layerCount:
- layers.length,
- layers,
- canvasCount:
- document.querySelectorAll(
- 'canvas'
- ).length,
- renderedPanelCount:
- document.querySelectorAll(
- '.neuroglancer-rendered-data-panel'
- ).length
- };
- return JSON.stringify(
- snapshot
- );
- }
- function normalizeSource(
- source
- ) {
- if (
- source === undefined ||
- source === null
- ) {
- return null;
- }
- if (
- typeof source ===
- 'string'
- ) {
- return source;
- }
- try {
- return JSON.stringify(
- source
- );
- } catch (_) {
- return String(
- source
- );
- }
- }
- // ==========================================================================
- // LOGIN / STABILITY HELPERS
- // ==========================================================================
- function isLoginPromptVisible() {
- const text =
- document.body
- ? document.body
- .innerText
- .toLowerCase()
- : '';
- return (
- text.includes(
- 'login required'
- ) ||
- text.includes(
- 'request login'
- )
- );
- }
- function resetStability() {
- lastSnapshot =
- null;
- stableSince =
- null;
- }
- function sleep(
- milliseconds
- ) {
- return new Promise(
- resolve => {
- window.setTimeout(
- resolve,
- milliseconds
- );
- }
- );
- }
- })();
Add Comment
Please, Sign In to add comment