Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Import/export settings
- // @namespace http://tampermonkey.net/
- // @version 2026-08-09.5
- // @description Safely imports portable Neuroglancer rendering settings while preserving coordinates, zoom, layout, segments, annotations, and all annotation colors.
- // @author KrzysztofKruk + modifications
- // @match https://spelunker.cave-explorer.org/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=cave-explorer.org
- // @grant none
- // ==/UserScript==
- /* global viewer */
- (function () {
- 'use strict';
- const STORAGE_KEY = 'ng-settings';
- const FORMAT_VERSION = 5;
- /******************************************************************
- * IMPORTANT
- *
- * annotationColor is DELIBERATELY NOT included anywhere below.
- *
- * Soma / True End / Can't Fix / Hits Edge / Notes colors therefore
- * remain exactly as specified by the newly opened link.
- ******************************************************************/
- const SAFE_TOP_LEVEL_KEYS = [
- 'showDefaultAnnotations',
- 'showSlices',
- 'systemMemoryLimit',
- 'concurrentDownloads'
- ];
- const SAFE_LAYER_KEYS = {
- image: [
- 'tab',
- 'opacity',
- 'blend',
- 'shader',
- 'shaderControls',
- 'hideInactiveShaderControls',
- 'volumeRendering',
- 'volumeRenderingGain',
- 'volumeRenderingDepthSamples',
- 'crossSectionRenderScale'
- ],
- segmentation: [
- 'tab',
- /*
- * Rendering controls we specifically tested.
- */
- 'objectAlpha',
- 'ignoreNullVisibleSet',
- 'colorSeed',
- 'hideSegmentZero',
- 'hoverHighlight',
- 'baseSegmentColoring',
- 'selectedAlpha',
- 'notSelectedAlpha',
- 'saturation',
- 'crossSectionRenderScale',
- 'meshRenderScale',
- 'meshSilhouetteRendering',
- 'segmentDefaultColor'
- ],
- annotation: [
- /*
- * Keep the tool/tab behavior if desired,
- * but NEVER touch annotationColor.
- */
- 'tab',
- 'tool'
- ]
- };
- /******************************************************************
- * HELPERS
- ******************************************************************/
- function clone(value) {
- if (value === undefined) {
- return undefined;
- }
- return JSON.parse(
- JSON.stringify(value)
- );
- }
- function hasOwn(object, key) {
- return Boolean(
- object &&
- Object.prototype.hasOwnProperty.call(
- object,
- key
- )
- );
- }
- function getViewerState() {
- if (
- typeof viewer === 'undefined' ||
- !viewer ||
- !viewer.state ||
- typeof viewer.state.toJSON !== 'function'
- ) {
- throw new Error(
- 'Neuroglancer viewer state is not available.'
- );
- }
- return viewer.state.toJSON();
- }
- /******************************************************************
- * EXPORT
- ******************************************************************/
- function exportSettings() {
- let currentState;
- try {
- currentState =
- getViewerState();
- } catch (error) {
- console.error(
- '[Import/export settings] Export failed:',
- error
- );
- return;
- }
- const portable = {
- version:
- FORMAT_VERSION,
- topLevel: {},
- layers: []
- };
- /****************************************************************
- * TOP LEVEL
- ****************************************************************/
- for (
- const key of
- SAFE_TOP_LEVEL_KEYS
- ) {
- if (
- hasOwn(
- currentState,
- key
- )
- ) {
- portable.topLevel[key] =
- clone(
- currentState[key]
- );
- }
- }
- /****************************************************************
- * LAYERS
- ****************************************************************/
- if (
- Array.isArray(
- currentState.layers
- )
- ) {
- for (
- const layer of
- currentState.layers
- ) {
- if (
- !layer ||
- !layer.name ||
- !layer.type
- ) {
- continue;
- }
- const allowedKeys =
- SAFE_LAYER_KEYS[
- layer.type
- ];
- if (
- !Array.isArray(
- allowedKeys
- )
- ) {
- continue;
- }
- const savedLayer = {
- name:
- layer.name,
- type:
- layer.type,
- settings: {}
- };
- /**************************************************************
- * SEGMENTATION DEFAULT NORMALIZATION
- *
- * Neuroglancer omits these values from JSON when they equal
- * their defaults.
- **************************************************************/
- if (
- layer.type ===
- 'segmentation'
- ) {
- /*
- * Opacity (3d)
- *
- * Default is 1.
- */
- savedLayer.settings.objectAlpha =
- hasOwn(
- layer,
- 'objectAlpha'
- )
- ? Number(
- layer.objectAlpha
- )
- : 1;
- /*
- * Show all by default
- *
- * Default is true.
- *
- * Your test showed:
- *
- * unchecked ->
- * ignoreNullVisibleSet: false
- */
- savedLayer.settings.ignoreNullVisibleSet =
- hasOwn(
- layer,
- 'ignoreNullVisibleSet'
- )
- ? Boolean(
- layer.ignoreNullVisibleSet
- )
- : true;
- }
- for (
- const key of
- allowedKeys
- ) {
- if (
- layer.type ===
- 'segmentation' &&
- (
- key ===
- 'objectAlpha' ||
- key ===
- 'ignoreNullVisibleSet'
- )
- ) {
- continue;
- }
- if (
- hasOwn(
- layer,
- key
- )
- ) {
- savedLayer
- .settings[key] =
- clone(
- layer[key]
- );
- }
- }
- /*
- * Extra safety.
- *
- * annotationColor must NEVER make it into the saved portable
- * settings even if a future edit accidentally introduces it.
- */
- delete savedLayer
- .settings
- .annotationColor;
- portable.layers.push(
- savedLayer
- );
- }
- }
- try {
- localStorage.setItem(
- STORAGE_KEY,
- JSON.stringify(
- portable
- )
- );
- console.log(
- '[Import/export settings] Safe settings exported. Annotation colors were deliberately excluded.',
- portable
- );
- } catch (error) {
- console.error(
- '[Import/export settings] Could not save settings:',
- error
- );
- }
- }
- /******************************************************************
- * READ + SANITIZE SAVED SETTINGS
- ******************************************************************/
- function readSavedSettings() {
- const text =
- localStorage.getItem(
- STORAGE_KEY
- );
- if (!text) {
- return null;
- }
- let saved;
- try {
- saved =
- JSON.parse(text);
- } catch (error) {
- throw new Error(
- 'Saved ng-settings value is invalid JSON.'
- );
- }
- /****************************************************************
- * CURRENT SAFE FORMAT
- ****************************************************************/
- if (
- saved &&
- saved.version ===
- FORMAT_VERSION &&
- saved.topLevel &&
- Array.isArray(
- saved.layers
- )
- ) {
- sanitizeAnnotationColors(
- saved
- );
- return saved;
- }
- /****************************************************************
- * MIGRATE v3 / v4 SAFE FORMAT
- ****************************************************************/
- if (
- saved &&
- (
- saved.version === 3 ||
- saved.version === 4
- ) &&
- saved.topLevel &&
- Array.isArray(
- saved.layers
- )
- ) {
- const migrated =
- clone(saved);
- migrated.version =
- FORMAT_VERSION;
- sanitizeAnnotationColors(
- migrated
- );
- for (
- const layer of
- migrated.layers
- ) {
- if (
- !layer ||
- layer.type !==
- 'segmentation'
- ) {
- continue;
- }
- if (!layer.settings) {
- layer.settings = {};
- }
- if (
- !hasOwn(
- layer.settings,
- 'objectAlpha'
- )
- ) {
- layer.settings.objectAlpha =
- 1;
- }
- if (
- !hasOwn(
- layer.settings,
- 'ignoreNullVisibleSet'
- )
- ) {
- layer.settings.ignoreNullVisibleSet =
- true;
- }
- }
- console.log(
- `[Import/export settings] Version ${saved.version} settings migrated safely to version ${FORMAT_VERSION}. Annotation colors removed.`
- );
- return migrated;
- }
- /****************************************************************
- * MIGRATE ORIGINAL FULL-STATE FORMAT
- ****************************************************************/
- const migrated = {
- version:
- FORMAT_VERSION,
- topLevel: {},
- layers: []
- };
- for (
- const key of
- SAFE_TOP_LEVEL_KEYS
- ) {
- if (
- hasOwn(
- saved,
- key
- )
- ) {
- migrated.topLevel[key] =
- clone(
- saved[key]
- );
- }
- }
- if (
- Array.isArray(
- saved.layers
- )
- ) {
- for (
- const layer of
- saved.layers
- ) {
- if (
- !layer ||
- !layer.name ||
- !layer.type
- ) {
- continue;
- }
- const allowedKeys =
- SAFE_LAYER_KEYS[
- layer.type
- ];
- if (
- !Array.isArray(
- allowedKeys
- )
- ) {
- continue;
- }
- const migratedLayer = {
- name:
- layer.name,
- type:
- layer.type,
- settings: {}
- };
- if (
- layer.type ===
- 'segmentation'
- ) {
- migratedLayer.settings.objectAlpha =
- hasOwn(
- layer,
- 'objectAlpha'
- )
- ? Number(
- layer.objectAlpha
- )
- : 1;
- migratedLayer.settings.ignoreNullVisibleSet =
- hasOwn(
- layer,
- 'ignoreNullVisibleSet'
- )
- ? Boolean(
- layer.ignoreNullVisibleSet
- )
- : true;
- }
- for (
- const key of
- allowedKeys
- ) {
- if (
- layer.type ===
- 'segmentation' &&
- (
- key ===
- 'objectAlpha' ||
- key ===
- 'ignoreNullVisibleSet'
- )
- ) {
- continue;
- }
- if (
- hasOwn(
- layer,
- key
- )
- ) {
- migratedLayer
- .settings[key] =
- clone(
- layer[key]
- );
- }
- }
- /*
- * Never migrate annotationColor.
- */
- delete migratedLayer
- .settings
- .annotationColor;
- migrated.layers.push(
- migratedLayer
- );
- }
- }
- sanitizeAnnotationColors(
- migrated
- );
- console.log(
- '[Import/export settings] Old settings format safely migrated. Annotation colors were discarded.',
- migrated
- );
- return migrated;
- }
- function sanitizeAnnotationColors(
- portable
- ) {
- if (
- !portable ||
- !Array.isArray(
- portable.layers
- )
- ) {
- return;
- }
- for (
- const layer of
- portable.layers
- ) {
- if (
- !layer ||
- !layer.settings
- ) {
- continue;
- }
- delete layer
- .settings
- .annotationColor;
- }
- }
- /******************************************************************
- * TOP-LEVEL IMPORT
- ******************************************************************/
- function importTopLevelSettings(
- portable
- ) {
- const patch = {};
- for (
- const key of
- SAFE_TOP_LEVEL_KEYS
- ) {
- if (
- hasOwn(
- portable.topLevel,
- key
- )
- ) {
- patch[key] =
- clone(
- portable
- .topLevel[key]
- );
- }
- }
- if (
- Object.keys(
- patch
- ).length === 0
- ) {
- return;
- }
- /*
- * Only safe top-level values.
- *
- * No:
- * - layers
- * - layout
- * - coordinates
- * - selection
- * - segments
- * - annotations
- */
- viewer.state.restoreState(
- patch
- );
- }
- /******************************************************************
- * FIND LIVE LAYERS
- ******************************************************************/
- function getManagedLayers() {
- const candidates = [
- viewer &&
- viewer.layerManager &&
- viewer.layerManager
- .managedLayers,
- viewer &&
- viewer.layerManager &&
- viewer.layerManager
- .layers,
- viewer &&
- viewer.rootLayers &&
- viewer.rootLayers
- .managedLayers
- ];
- for (
- const candidate of
- candidates
- ) {
- if (
- Array.isArray(
- candidate
- )
- ) {
- return candidate;
- }
- if (
- candidate &&
- typeof candidate[
- Symbol.iterator
- ] === 'function'
- ) {
- try {
- return Array.from(
- candidate
- );
- } catch (_) {
- // Continue.
- }
- }
- }
- return [];
- }
- function getManagedLayerName(
- managedLayer
- ) {
- const candidates = [
- managedLayer &&
- managedLayer.name,
- managedLayer &&
- managedLayer.layer &&
- managedLayer.layer.name
- ];
- for (
- const value of
- candidates
- ) {
- if (
- typeof value ===
- 'string' &&
- value
- ) {
- return value;
- }
- if (
- value &&
- typeof value.value ===
- 'string'
- ) {
- return value.value;
- }
- }
- return '';
- }
- function getUserLayer(
- managedLayer
- ) {
- if (!managedLayer) {
- return null;
- }
- return (
- managedLayer.layer ||
- managedLayer.userLayer ||
- null
- );
- }
- /******************************************************************
- * TRACKABLE SETTERS
- ******************************************************************/
- function setTrackable(
- property,
- value
- ) {
- if (!property) {
- return false;
- }
- if (
- typeof property ===
- 'object' &&
- 'value' in property
- ) {
- try {
- property.value =
- clone(value);
- return true;
- } catch (_) {
- // Continue.
- }
- }
- if (
- typeof property.restoreState ===
- 'function'
- ) {
- try {
- property.restoreState(
- clone(value)
- );
- return true;
- } catch (_) {
- // Continue.
- }
- }
- return false;
- }
- function setPropertyIfTrackable(
- target,
- key,
- value
- ) {
- if (!target) {
- return false;
- }
- let property;
- try {
- property =
- target[key];
- } catch (_) {
- return false;
- }
- return setTrackable(
- property,
- value
- );
- }
- /******************************************************************
- * SEGMENTATION DISPLAY TARGETS
- ******************************************************************/
- function getSegmentationDisplayTargets(
- liveLayer
- ) {
- const targets = [];
- if (!liveLayer) {
- return targets;
- }
- if (
- liveLayer.displayState
- ) {
- targets.push(
- liveLayer.displayState
- );
- }
- if (
- liveLayer
- .segmentationDisplayState
- ) {
- targets.push(
- liveLayer
- .segmentationDisplayState
- );
- }
- targets.push(
- liveLayer
- );
- return targets;
- }
- /******************************************************************
- * OPACITY (3D)
- ******************************************************************/
- function setObjectAlpha(
- liveLayer,
- value
- ) {
- const normalized =
- Number(value);
- if (
- !Number.isFinite(
- normalized
- )
- ) {
- return false;
- }
- for (
- const target of
- getSegmentationDisplayTargets(
- liveLayer
- )
- ) {
- if (
- setPropertyIfTrackable(
- target,
- 'objectAlpha',
- normalized
- )
- ) {
- return true;
- }
- }
- return false;
- }
- /******************************************************************
- * SHOW ALL BY DEFAULT
- ******************************************************************/
- function setIgnoreNullVisibleSet(
- liveLayer,
- value
- ) {
- const normalized =
- Boolean(value);
- for (
- const target of
- getSegmentationDisplayTargets(
- liveLayer
- )
- ) {
- if (
- setPropertyIfTrackable(
- target,
- 'ignoreNullVisibleSet',
- normalized
- )
- ) {
- return true;
- }
- }
- try {
- const groupState =
- liveLayer &&
- liveLayer.displayState &&
- liveLayer.displayState
- .segmentationGroupState &&
- liveLayer.displayState
- .segmentationGroupState
- .value;
- if (
- groupState &&
- setPropertyIfTrackable(
- groupState,
- 'ignoreNullVisibleSet',
- normalized
- )
- ) {
- return true;
- }
- } catch (_) {
- // Ignore.
- }
- return false;
- }
- /******************************************************************
- * GENERIC SAFE LAYER PROPERTY
- ******************************************************************/
- function setGenericLayerSetting(
- liveLayer,
- key,
- value
- ) {
- const targets = [
- liveLayer,
- liveLayer &&
- liveLayer.displayState,
- liveLayer &&
- liveLayer
- .segmentationDisplayState,
- liveLayer &&
- liveLayer.renderingOptions
- ];
- for (
- const target of
- targets
- ) {
- if (
- setPropertyIfTrackable(
- target,
- key,
- value
- )
- ) {
- return true;
- }
- }
- return false;
- }
- /******************************************************************
- * APPLY ONE LAYER
- ******************************************************************/
- function applyLayerSettings(
- liveLayer,
- savedLayer
- ) {
- const settings =
- savedLayer.settings ||
- {};
- /*
- * Absolute protection against old saved data.
- */
- delete settings
- .annotationColor;
- let applied = 0;
- let skipped = 0;
- /****************************************************************
- * SEGMENTATION
- ****************************************************************/
- if (
- savedLayer.type ===
- 'segmentation'
- ) {
- if (
- hasOwn(
- settings,
- 'objectAlpha'
- )
- ) {
- if (
- setObjectAlpha(
- liveLayer,
- settings.objectAlpha
- )
- ) {
- applied++;
- } else {
- skipped++;
- }
- }
- if (
- hasOwn(
- settings,
- 'ignoreNullVisibleSet'
- )
- ) {
- if (
- setIgnoreNullVisibleSet(
- liveLayer,
- settings
- .ignoreNullVisibleSet
- )
- ) {
- applied++;
- } else {
- skipped++;
- }
- }
- const keys = [
- 'colorSeed',
- 'hideSegmentZero',
- 'hoverHighlight',
- 'baseSegmentColoring',
- 'selectedAlpha',
- 'notSelectedAlpha',
- 'saturation',
- 'crossSectionRenderScale',
- 'meshRenderScale',
- 'meshSilhouetteRendering',
- 'segmentDefaultColor'
- ];
- for (
- const key of
- keys
- ) {
- if (
- !hasOwn(
- settings,
- key
- )
- ) {
- continue;
- }
- if (
- setGenericLayerSetting(
- liveLayer,
- key,
- settings[key]
- )
- ) {
- applied++;
- } else {
- skipped++;
- }
- }
- }
- /****************************************************************
- * IMAGE
- ****************************************************************/
- if (
- savedLayer.type ===
- 'image'
- ) {
- const keys = [
- 'opacity',
- 'blend',
- 'shader',
- 'shaderControls',
- 'hideInactiveShaderControls',
- 'volumeRendering',
- 'volumeRenderingGain',
- 'volumeRenderingDepthSamples',
- 'crossSectionRenderScale'
- ];
- for (
- const key of
- keys
- ) {
- if (
- !hasOwn(
- settings,
- key
- )
- ) {
- continue;
- }
- if (
- setGenericLayerSetting(
- liveLayer,
- key,
- settings[key]
- )
- ) {
- applied++;
- } else {
- skipped++;
- }
- }
- }
- /****************************************************************
- * TAB
- ****************************************************************/
- if (
- hasOwn(
- settings,
- 'tab'
- )
- ) {
- if (
- setGenericLayerSetting(
- liveLayer,
- 'tab',
- settings.tab
- )
- ) {
- applied++;
- } else {
- skipped++;
- }
- }
- /****************************************************************
- * ANNOTATION TOOL
- *
- * This does NOT alter annotation color.
- ****************************************************************/
- if (
- savedLayer.type ===
- 'annotation' &&
- hasOwn(
- settings,
- 'tool'
- )
- ) {
- if (
- setGenericLayerSetting(
- liveLayer,
- 'tool',
- settings.tool
- )
- ) {
- applied++;
- } else {
- skipped++;
- }
- }
- try {
- if (
- liveLayer &&
- liveLayer
- .specificationChanged &&
- typeof liveLayer
- .specificationChanged
- .dispatch ===
- 'function'
- ) {
- liveLayer
- .specificationChanged
- .dispatch();
- }
- } catch (_) {
- // Harmless.
- }
- return {
- applied,
- skipped
- };
- }
- /******************************************************************
- * IMPORT
- ******************************************************************/
- function importSettings() {
- let portable;
- try {
- portable =
- readSavedSettings();
- } catch (error) {
- console.error(
- '[Import/export settings] Import failed:',
- error
- );
- return;
- }
- if (!portable) {
- console.log(
- '[Import/export settings] Saved settings not found.'
- );
- return;
- }
- /*
- * One last hard guarantee.
- */
- sanitizeAnnotationColors(
- portable
- );
- let before;
- try {
- before =
- getViewerState();
- } catch (error) {
- console.error(
- '[Import/export settings] Could not read viewer state:',
- error
- );
- return;
- }
- /****************************************************************
- * TOP LEVEL
- ****************************************************************/
- try {
- importTopLevelSettings(
- portable
- );
- } catch (error) {
- console.warn(
- '[Import/export settings] Top-level import warning:',
- error
- );
- }
- /****************************************************************
- * LIVE LAYERS
- ****************************************************************/
- const managedLayers =
- getManagedLayers();
- const managedByName =
- new Map();
- for (
- const managedLayer of
- managedLayers
- ) {
- const name =
- getManagedLayerName(
- managedLayer
- );
- if (name) {
- managedByName.set(
- name,
- managedLayer
- );
- }
- }
- let matchingLayers = 0;
- let missingLayers = 0;
- let appliedSettings = 0;
- let skippedSettings = 0;
- for (
- const savedLayer of
- portable.layers
- ) {
- const managedLayer =
- managedByName.get(
- savedLayer.name
- );
- if (!managedLayer) {
- missingLayers++;
- continue;
- }
- const liveLayer =
- getUserLayer(
- managedLayer
- );
- if (!liveLayer) {
- missingLayers++;
- continue;
- }
- matchingLayers++;
- const result =
- applyLayerSettings(
- liveLayer,
- savedLayer
- );
- appliedSettings +=
- result.applied;
- skippedSettings +=
- result.skipped;
- }
- /****************************************************************
- * DIAGNOSTICS
- ****************************************************************/
- let after = null;
- try {
- after =
- getViewerState();
- } catch (_) {
- // Diagnostic only.
- }
- console.log(
- '[Import/export settings] Safe import complete. Annotation colors were NOT touched.',
- {
- matchingLayers,
- missingLayers,
- appliedSettings,
- skippedSettings,
- positionUnchanged:
- after
- ? (
- JSON.stringify(
- before.position
- ) ===
- JSON.stringify(
- after.position
- )
- )
- : 'unknown',
- layoutUnchanged:
- after
- ? (
- JSON.stringify(
- before.layout
- ) ===
- JSON.stringify(
- after.layout
- )
- )
- : 'unknown',
- annotationColorsPreserved:
- after
- ? compareAnnotationColors(
- before,
- after
- )
- : 'unknown',
- savedObjectAlpha:
- getSavedSegmentationSetting(
- portable,
- 'objectAlpha'
- ),
- resultingObjectAlpha:
- after
- ? getCurrentSegmentationSetting(
- after,
- 'objectAlpha',
- 1
- )
- : 'unknown',
- savedIgnoreNullVisibleSet:
- getSavedSegmentationSetting(
- portable,
- 'ignoreNullVisibleSet'
- ),
- resultingIgnoreNullVisibleSet:
- after
- ? getCurrentSegmentationSetting(
- after,
- 'ignoreNullVisibleSet',
- true
- )
- : 'unknown'
- }
- );
- }
- /******************************************************************
- * DIAGNOSTICS
- ******************************************************************/
- function compareAnnotationColors(
- before,
- after
- ) {
- function getMap(
- state
- ) {
- const result =
- new Map();
- if (
- !state ||
- !Array.isArray(
- state.layers
- )
- ) {
- return result;
- }
- for (
- const layer of
- state.layers
- ) {
- if (
- !layer ||
- layer.type !==
- 'annotation'
- ) {
- continue;
- }
- result.set(
- layer.name,
- hasOwn(
- layer,
- 'annotationColor'
- )
- ? JSON.stringify(
- layer.annotationColor
- )
- : '__DEFAULT__'
- );
- }
- return result;
- }
- const a =
- getMap(before);
- const b =
- getMap(after);
- if (
- a.size !== b.size
- ) {
- return false;
- }
- for (
- const [
- name,
- color
- ] of a
- ) {
- if (
- b.get(name) !==
- color
- ) {
- return false;
- }
- }
- return true;
- }
- function getSavedSegmentationSetting(
- portable,
- key
- ) {
- const layer =
- portable.layers.find(
- item =>
- item &&
- item.type ===
- 'segmentation'
- );
- if (
- !layer ||
- !layer.settings
- ) {
- return undefined;
- }
- return layer
- .settings[key];
- }
- function getCurrentSegmentationSetting(
- state,
- key,
- defaultValue
- ) {
- if (
- !state ||
- !Array.isArray(
- state.layers
- )
- ) {
- return defaultValue;
- }
- const layer =
- state.layers.find(
- item =>
- item &&
- item.type ===
- 'segmentation'
- );
- if (!layer) {
- return defaultValue;
- }
- return hasOwn(
- layer,
- key
- )
- ? layer[key]
- : defaultValue;
- }
- /******************************************************************
- * MENU
- ******************************************************************/
- let target;
- function addMenu() {
- const menu =
- document.createElement(
- 'div'
- );
- menu.style.position =
- 'fixed';
- menu.style.background =
- '#222';
- menu.style.color =
- '#fff';
- menu.style.border =
- '1px solid #555';
- menu.style.padding =
- '5px 0';
- menu.style.display =
- 'none';
- menu.style.zIndex =
- '10000';
- menu.style.minWidth =
- '140px';
- menu.style.fontSize =
- '13px';
- menu.style.fontFamily =
- 'Arial, sans-serif';
- function createItem(
- text,
- onClick
- ) {
- const item =
- document.createElement(
- 'div'
- );
- item.textContent =
- text;
- item.style.padding =
- '7px 10px';
- item.style.cursor =
- 'pointer';
- item.addEventListener(
- 'mouseenter',
- () => {
- item.style.background =
- '#444';
- }
- );
- item.addEventListener(
- 'mouseleave',
- () => {
- item.style.background =
- 'transparent';
- }
- );
- item.addEventListener(
- 'click',
- event => {
- event.stopPropagation();
- onClick();
- hideMenu();
- }
- );
- return item;
- }
- function hideMenu() {
- menu.style.display =
- 'none';
- }
- menu.appendChild(
- createItem(
- 'Export settings',
- () => {
- if (
- window.confirm(
- 'Export current portable rendering settings?\n\nAnnotation colors will NOT be saved or changed.'
- )
- ) {
- exportSettings();
- }
- }
- )
- );
- menu.appendChild(
- createItem(
- 'Import settings',
- () => {
- importSettings();
- }
- )
- );
- document.body.appendChild(
- menu
- );
- target.addEventListener(
- 'contextmenu',
- event => {
- event.preventDefault();
- menu.style.left =
- event.clientX +
- 'px';
- menu.style.top =
- event.clientY +
- 'px';
- menu.style.display =
- 'block';
- }
- );
- document.addEventListener(
- 'click',
- hideMenu
- );
- window.addEventListener(
- 'blur',
- hideMenu
- );
- /****************************************************************
- * X = export
- * / = import
- ****************************************************************/
- document.body.addEventListener(
- 'keyup',
- event => {
- const element =
- event.target;
- const tagName =
- element &&
- element.tagName
- ? element.tagName
- .toLowerCase()
- : '';
- const typing =
- tagName ===
- 'input' ||
- tagName ===
- 'textarea' ||
- (
- element &&
- element.isContentEditable
- );
- if (typing) {
- return;
- }
- switch (
- event.key
- ) {
- case 'x':
- case 'X':
- exportSettings();
- break;
- case '/':
- importSettings();
- break;
- }
- }
- );
- }
- /******************************************************************
- * STARTUP
- ******************************************************************/
- const waitForTopBar =
- window.setInterval(
- () => {
- target =
- document.querySelector(
- '.neuroglancer-viewer-top-row'
- );
- if (!target) {
- return;
- }
- window.clearInterval(
- waitForTopBar
- );
- addMenu();
- },
- 100
- );
- })();
Advertisement
Add Comment
Please, Sign In to add comment