Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Neuroglancer Auto Import Settings
- // @namespace eyewire-elite
- // @version 2.1.0
- // @description Imports saved settings only when a Neuroglancer link is first opened, not when the page is refreshed.
- // @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
- });
- /*
- * Do not auto-import after:
- *
- * - Refresh
- * - Ctrl+R
- * - F5
- * - Ctrl+F5
- * - Browser reload button
- *
- * A newly opened link or normal navigation has type "navigate" and is
- * allowed to continue.
- */
- if (isReloadNavigation()) {
- console.log(
- '[Auto import] Page reload detected. ' +
- 'Automatic settings import was skipped.'
- );
- return;
- }
- /*
- * If no settings have been exported, leave the viewer unchanged.
- */
- 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
- );
- /**
- * Determines whether this page load resulted from a refresh.
- */
- function isReloadNavigation() {
- try {
- const navigationEntries =
- performance.getEntriesByType('navigation');
- if (
- Array.isArray(navigationEntries) &&
- navigationEntries.length > 0
- ) {
- return navigationEntries[0].type === 'reload';
- }
- } catch (error) {
- console.warn(
- '[Auto import] Could not read Navigation Timing data:',
- error
- );
- }
- /*
- * Fallback for older browsers.
- *
- * TYPE_RELOAD has the numeric value 1.
- */
- try {
- if (
- performance.navigation &&
- performance.navigation.type === 1
- ) {
- return true;
- }
- } catch (error) {
- // Treat an unavailable fallback as a normal navigation.
- }
- return false;
- }
- 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,
- 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