Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Neuroglancer Auto Import Settings
- // @namespace eyewire-elite
- // @version 2.0.0
- // @description Automatically imports saved Neuroglancer settings as soon as the viewer state has genuinely stabilized.
- // @match https://spelunker.cave-explorer.org/*
- // @run-at document-idle
- // @grant unsafeWindow
- // ==/UserScript==
- (function () {
- 'use strict';
- const CONFIG = Object.freeze({
- settingsStorageKey: 'ng-settings',
- /*
- * Never import immediately, even when the state initially appears stable.
- */
- minimumWaitMilliseconds: 3000,
- /*
- * The meaningful viewer state must remain unchanged for this long.
- */
- requiredStableMilliseconds: 2500,
- /*
- * How often readiness is checked.
- */
- checkIntervalMilliseconds: 250,
- /*
- * Stop trying after this amount of time.
- */
- maximumWaitMilliseconds: 45000
- });
- if (!localStorage.getItem(CONFIG.settingsStorageKey)) {
- console.log(
- '[Auto import] No saved ng-settings entry found. ' +
- 'The current 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
- );
- 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 was 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 (error) {
- resetStability();
- return;
- }
- if (snapshot !== lastSnapshot) {
- lastSnapshot = snapshot;
- stableSince = now;
- return;
- }
- if (stableSince === null) {
- stableSince = now;
- return;
- }
- const stableElapsed = now - stableSince;
- if (
- stableElapsed >= CONFIG.requiredStableMilliseconds
- ) {
- importTriggered = true;
- window.clearInterval(intervalId);
- triggerExistingImporter();
- console.log(
- '[Auto import] Viewer stabilized after ' +
- (totalElapsed / 1000).toFixed(2) +
- ' seconds. Settings import triggered.'
- );
- }
- }
- function getPageViewer() {
- try {
- if (
- typeof unsafeWindow !== 'undefined' &&
- unsafeWindow &&
- unsafeWindow.viewer
- ) {
- return unsafeWindow.viewer;
- }
- } catch (error) {
- // Continue to the normal window check.
- }
- try {
- if (window.viewer) {
- return window.viewer;
- }
- } catch (error) {
- // Viewer is not available yet.
- }
- 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 hasSegmentationLayer =
- state.layers.some(function (layer) {
- return Boolean(
- layer &&
- layer.type === 'segmentation' &&
- layer.source
- );
- });
- if (!hasSegmentationLayer) {
- return false;
- }
- const topBar = document.querySelector(
- '.neuroglancer-viewer-top-row'
- );
- const renderedPanel = document.querySelector(
- '.neuroglancer-rendered-data-panel, canvas'
- );
- return Boolean(topBar && renderedPanel);
- } catch (error) {
- return false;
- }
- }
- /**
- * Makes a snapshot of state values that commonly change during initial load.
- *
- * Large annotation arrays are reduced to counts so comparison stays cheap.
- */
- function createMeaningfulSnapshot(pageViewer) {
- const state = pageViewer.state.toJSON();
- const layers = state.layers.map(function (layer) {
- const snapshotLayer = {
- name: layer.name || '',
- type: layer.type || '',
- source: normalizeSource(layer.source),
- visible:
- Object.prototype.hasOwnProperty.call(
- layer,
- 'visible'
- )
- ? layer.visible
- : null
- };
- if (layer.type === 'segmentation') {
- snapshotLayer.segments =
- Array.isArray(layer.segments)
- ? layer.segments.map(String)
- : [];
- snapshotLayer.segmentCount =
- snapshotLayer.segments.length;
- }
- if (layer.type === 'annotation') {
- snapshotLayer.annotationCount =
- Array.isArray(layer.annotations)
- ? layer.annotations.length
- : 0;
- }
- return snapshotLayer;
- });
- 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: 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 (error) {
- return String(source);
- }
- }
- function isLoginPromptVisible() {
- const bodyText = document.body
- ? document.body.innerText.toLowerCase()
- : '';
- return (
- bodyText.includes('login required') ||
- bodyText.includes('request login')
- );
- }
- function resetStability() {
- lastSnapshot = null;
- stableSince = null;
- }
- /**
- * Triggers the "/" shortcut registered by the Import/export settings script.
- */
- function triggerExistingImporter() {
- const target = document.body || document.documentElement;
- const event = new KeyboardEvent('keyup', {
- key: '/',
- code: 'Slash',
- keyCode: 191,
- which: 191,
- bubbles: true,
- cancelable: true
- });
- target.dispatchEvent(event);
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment