Guest User

Untitled

a guest
Aug 9th, 2026
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 34.98 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Import/export settings
  3. // @namespace http://tampermonkey.net/
  4. // @version 2026-08-09.5
  5. // @description Safely imports portable Neuroglancer rendering settings while preserving coordinates, zoom, layout, segments, annotations, and all annotation colors.
  6. // @author KrzysztofKruk + modifications
  7. // @match https://spelunker.cave-explorer.org/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=cave-explorer.org
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. /* global viewer */
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. const STORAGE_KEY = 'ng-settings';
  18. const FORMAT_VERSION = 5;
  19.  
  20.  
  21. /******************************************************************
  22. * IMPORTANT
  23. *
  24. * annotationColor is DELIBERATELY NOT included anywhere below.
  25. *
  26. * Soma / True End / Can't Fix / Hits Edge / Notes colors therefore
  27. * remain exactly as specified by the newly opened link.
  28. ******************************************************************/
  29.  
  30. const SAFE_TOP_LEVEL_KEYS = [
  31. 'showDefaultAnnotations',
  32. 'showSlices',
  33. 'systemMemoryLimit',
  34. 'concurrentDownloads'
  35. ];
  36.  
  37.  
  38. const SAFE_LAYER_KEYS = {
  39. image: [
  40. 'tab',
  41. 'opacity',
  42. 'blend',
  43. 'shader',
  44. 'shaderControls',
  45. 'hideInactiveShaderControls',
  46. 'volumeRendering',
  47. 'volumeRenderingGain',
  48. 'volumeRenderingDepthSamples',
  49. 'crossSectionRenderScale'
  50. ],
  51.  
  52. segmentation: [
  53. 'tab',
  54.  
  55. /*
  56. * Rendering controls we specifically tested.
  57. */
  58. 'objectAlpha',
  59. 'ignoreNullVisibleSet',
  60.  
  61. 'colorSeed',
  62. 'hideSegmentZero',
  63. 'hoverHighlight',
  64. 'baseSegmentColoring',
  65. 'selectedAlpha',
  66. 'notSelectedAlpha',
  67. 'saturation',
  68. 'crossSectionRenderScale',
  69. 'meshRenderScale',
  70. 'meshSilhouetteRendering',
  71. 'segmentDefaultColor'
  72. ],
  73.  
  74. annotation: [
  75. /*
  76. * Keep the tool/tab behavior if desired,
  77. * but NEVER touch annotationColor.
  78. */
  79. 'tab',
  80. 'tool'
  81. ]
  82. };
  83.  
  84.  
  85. /******************************************************************
  86. * HELPERS
  87. ******************************************************************/
  88.  
  89. function clone(value) {
  90. if (value === undefined) {
  91. return undefined;
  92. }
  93.  
  94. return JSON.parse(
  95. JSON.stringify(value)
  96. );
  97. }
  98.  
  99.  
  100. function hasOwn(object, key) {
  101. return Boolean(
  102. object &&
  103. Object.prototype.hasOwnProperty.call(
  104. object,
  105. key
  106. )
  107. );
  108. }
  109.  
  110.  
  111. function getViewerState() {
  112. if (
  113. typeof viewer === 'undefined' ||
  114. !viewer ||
  115. !viewer.state ||
  116. typeof viewer.state.toJSON !== 'function'
  117. ) {
  118. throw new Error(
  119. 'Neuroglancer viewer state is not available.'
  120. );
  121. }
  122.  
  123. return viewer.state.toJSON();
  124. }
  125.  
  126.  
  127. /******************************************************************
  128. * EXPORT
  129. ******************************************************************/
  130.  
  131. function exportSettings() {
  132. let currentState;
  133.  
  134. try {
  135. currentState =
  136. getViewerState();
  137.  
  138. } catch (error) {
  139. console.error(
  140. '[Import/export settings] Export failed:',
  141. error
  142. );
  143.  
  144. return;
  145. }
  146.  
  147. const portable = {
  148. version:
  149. FORMAT_VERSION,
  150.  
  151. topLevel: {},
  152.  
  153. layers: []
  154. };
  155.  
  156.  
  157. /****************************************************************
  158. * TOP LEVEL
  159. ****************************************************************/
  160.  
  161. for (
  162. const key of
  163. SAFE_TOP_LEVEL_KEYS
  164. ) {
  165. if (
  166. hasOwn(
  167. currentState,
  168. key
  169. )
  170. ) {
  171. portable.topLevel[key] =
  172. clone(
  173. currentState[key]
  174. );
  175. }
  176. }
  177.  
  178.  
  179. /****************************************************************
  180. * LAYERS
  181. ****************************************************************/
  182.  
  183. if (
  184. Array.isArray(
  185. currentState.layers
  186. )
  187. ) {
  188. for (
  189. const layer of
  190. currentState.layers
  191. ) {
  192. if (
  193. !layer ||
  194. !layer.name ||
  195. !layer.type
  196. ) {
  197. continue;
  198. }
  199.  
  200. const allowedKeys =
  201. SAFE_LAYER_KEYS[
  202. layer.type
  203. ];
  204.  
  205. if (
  206. !Array.isArray(
  207. allowedKeys
  208. )
  209. ) {
  210. continue;
  211. }
  212.  
  213. const savedLayer = {
  214. name:
  215. layer.name,
  216.  
  217. type:
  218. layer.type,
  219.  
  220. settings: {}
  221. };
  222.  
  223.  
  224. /**************************************************************
  225. * SEGMENTATION DEFAULT NORMALIZATION
  226. *
  227. * Neuroglancer omits these values from JSON when they equal
  228. * their defaults.
  229. **************************************************************/
  230.  
  231. if (
  232. layer.type ===
  233. 'segmentation'
  234. ) {
  235. /*
  236. * Opacity (3d)
  237. *
  238. * Default is 1.
  239. */
  240. savedLayer.settings.objectAlpha =
  241. hasOwn(
  242. layer,
  243. 'objectAlpha'
  244. )
  245. ? Number(
  246. layer.objectAlpha
  247. )
  248. : 1;
  249.  
  250.  
  251. /*
  252. * Show all by default
  253. *
  254. * Default is true.
  255. *
  256. * Your test showed:
  257. *
  258. * unchecked ->
  259. * ignoreNullVisibleSet: false
  260. */
  261. savedLayer.settings.ignoreNullVisibleSet =
  262. hasOwn(
  263. layer,
  264. 'ignoreNullVisibleSet'
  265. )
  266. ? Boolean(
  267. layer.ignoreNullVisibleSet
  268. )
  269. : true;
  270. }
  271.  
  272.  
  273. for (
  274. const key of
  275. allowedKeys
  276. ) {
  277. if (
  278. layer.type ===
  279. 'segmentation' &&
  280. (
  281. key ===
  282. 'objectAlpha' ||
  283. key ===
  284. 'ignoreNullVisibleSet'
  285. )
  286. ) {
  287. continue;
  288. }
  289.  
  290. if (
  291. hasOwn(
  292. layer,
  293. key
  294. )
  295. ) {
  296. savedLayer
  297. .settings[key] =
  298. clone(
  299. layer[key]
  300. );
  301. }
  302. }
  303.  
  304.  
  305. /*
  306. * Extra safety.
  307. *
  308. * annotationColor must NEVER make it into the saved portable
  309. * settings even if a future edit accidentally introduces it.
  310. */
  311. delete savedLayer
  312. .settings
  313. .annotationColor;
  314.  
  315.  
  316. portable.layers.push(
  317. savedLayer
  318. );
  319. }
  320. }
  321.  
  322.  
  323. try {
  324. localStorage.setItem(
  325. STORAGE_KEY,
  326. JSON.stringify(
  327. portable
  328. )
  329. );
  330.  
  331. console.log(
  332. '[Import/export settings] Safe settings exported. Annotation colors were deliberately excluded.',
  333. portable
  334. );
  335.  
  336. } catch (error) {
  337. console.error(
  338. '[Import/export settings] Could not save settings:',
  339. error
  340. );
  341. }
  342. }
  343.  
  344.  
  345. /******************************************************************
  346. * READ + SANITIZE SAVED SETTINGS
  347. ******************************************************************/
  348.  
  349. function readSavedSettings() {
  350. const text =
  351. localStorage.getItem(
  352. STORAGE_KEY
  353. );
  354.  
  355. if (!text) {
  356. return null;
  357. }
  358.  
  359. let saved;
  360.  
  361. try {
  362. saved =
  363. JSON.parse(text);
  364.  
  365. } catch (error) {
  366. throw new Error(
  367. 'Saved ng-settings value is invalid JSON.'
  368. );
  369. }
  370.  
  371.  
  372. /****************************************************************
  373. * CURRENT SAFE FORMAT
  374. ****************************************************************/
  375.  
  376. if (
  377. saved &&
  378. saved.version ===
  379. FORMAT_VERSION &&
  380. saved.topLevel &&
  381. Array.isArray(
  382. saved.layers
  383. )
  384. ) {
  385. sanitizeAnnotationColors(
  386. saved
  387. );
  388.  
  389. return saved;
  390. }
  391.  
  392.  
  393. /****************************************************************
  394. * MIGRATE v3 / v4 SAFE FORMAT
  395. ****************************************************************/
  396.  
  397. if (
  398. saved &&
  399. (
  400. saved.version === 3 ||
  401. saved.version === 4
  402. ) &&
  403. saved.topLevel &&
  404. Array.isArray(
  405. saved.layers
  406. )
  407. ) {
  408. const migrated =
  409. clone(saved);
  410.  
  411. migrated.version =
  412. FORMAT_VERSION;
  413.  
  414. sanitizeAnnotationColors(
  415. migrated
  416. );
  417.  
  418.  
  419. for (
  420. const layer of
  421. migrated.layers
  422. ) {
  423. if (
  424. !layer ||
  425. layer.type !==
  426. 'segmentation'
  427. ) {
  428. continue;
  429. }
  430.  
  431. if (!layer.settings) {
  432. layer.settings = {};
  433. }
  434.  
  435. if (
  436. !hasOwn(
  437. layer.settings,
  438. 'objectAlpha'
  439. )
  440. ) {
  441. layer.settings.objectAlpha =
  442. 1;
  443. }
  444.  
  445. if (
  446. !hasOwn(
  447. layer.settings,
  448. 'ignoreNullVisibleSet'
  449. )
  450. ) {
  451. layer.settings.ignoreNullVisibleSet =
  452. true;
  453. }
  454. }
  455.  
  456.  
  457. console.log(
  458. `[Import/export settings] Version ${saved.version} settings migrated safely to version ${FORMAT_VERSION}. Annotation colors removed.`
  459. );
  460.  
  461. return migrated;
  462. }
  463.  
  464.  
  465. /****************************************************************
  466. * MIGRATE ORIGINAL FULL-STATE FORMAT
  467. ****************************************************************/
  468.  
  469. const migrated = {
  470. version:
  471. FORMAT_VERSION,
  472.  
  473. topLevel: {},
  474.  
  475. layers: []
  476. };
  477.  
  478.  
  479. for (
  480. const key of
  481. SAFE_TOP_LEVEL_KEYS
  482. ) {
  483. if (
  484. hasOwn(
  485. saved,
  486. key
  487. )
  488. ) {
  489. migrated.topLevel[key] =
  490. clone(
  491. saved[key]
  492. );
  493. }
  494. }
  495.  
  496.  
  497. if (
  498. Array.isArray(
  499. saved.layers
  500. )
  501. ) {
  502. for (
  503. const layer of
  504. saved.layers
  505. ) {
  506. if (
  507. !layer ||
  508. !layer.name ||
  509. !layer.type
  510. ) {
  511. continue;
  512. }
  513.  
  514. const allowedKeys =
  515. SAFE_LAYER_KEYS[
  516. layer.type
  517. ];
  518.  
  519. if (
  520. !Array.isArray(
  521. allowedKeys
  522. )
  523. ) {
  524. continue;
  525. }
  526.  
  527. const migratedLayer = {
  528. name:
  529. layer.name,
  530.  
  531. type:
  532. layer.type,
  533.  
  534. settings: {}
  535. };
  536.  
  537.  
  538. if (
  539. layer.type ===
  540. 'segmentation'
  541. ) {
  542. migratedLayer.settings.objectAlpha =
  543. hasOwn(
  544. layer,
  545. 'objectAlpha'
  546. )
  547. ? Number(
  548. layer.objectAlpha
  549. )
  550. : 1;
  551.  
  552.  
  553. migratedLayer.settings.ignoreNullVisibleSet =
  554. hasOwn(
  555. layer,
  556. 'ignoreNullVisibleSet'
  557. )
  558. ? Boolean(
  559. layer.ignoreNullVisibleSet
  560. )
  561. : true;
  562. }
  563.  
  564.  
  565. for (
  566. const key of
  567. allowedKeys
  568. ) {
  569. if (
  570. layer.type ===
  571. 'segmentation' &&
  572. (
  573. key ===
  574. 'objectAlpha' ||
  575. key ===
  576. 'ignoreNullVisibleSet'
  577. )
  578. ) {
  579. continue;
  580. }
  581.  
  582. if (
  583. hasOwn(
  584. layer,
  585. key
  586. )
  587. ) {
  588. migratedLayer
  589. .settings[key] =
  590. clone(
  591. layer[key]
  592. );
  593. }
  594. }
  595.  
  596.  
  597. /*
  598. * Never migrate annotationColor.
  599. */
  600. delete migratedLayer
  601. .settings
  602. .annotationColor;
  603.  
  604.  
  605. migrated.layers.push(
  606. migratedLayer
  607. );
  608. }
  609. }
  610.  
  611.  
  612. sanitizeAnnotationColors(
  613. migrated
  614. );
  615.  
  616.  
  617. console.log(
  618. '[Import/export settings] Old settings format safely migrated. Annotation colors were discarded.',
  619. migrated
  620. );
  621.  
  622. return migrated;
  623. }
  624.  
  625.  
  626. function sanitizeAnnotationColors(
  627. portable
  628. ) {
  629. if (
  630. !portable ||
  631. !Array.isArray(
  632. portable.layers
  633. )
  634. ) {
  635. return;
  636. }
  637.  
  638. for (
  639. const layer of
  640. portable.layers
  641. ) {
  642. if (
  643. !layer ||
  644. !layer.settings
  645. ) {
  646. continue;
  647. }
  648.  
  649. delete layer
  650. .settings
  651. .annotationColor;
  652. }
  653. }
  654.  
  655.  
  656. /******************************************************************
  657. * TOP-LEVEL IMPORT
  658. ******************************************************************/
  659.  
  660. function importTopLevelSettings(
  661. portable
  662. ) {
  663. const patch = {};
  664.  
  665. for (
  666. const key of
  667. SAFE_TOP_LEVEL_KEYS
  668. ) {
  669. if (
  670. hasOwn(
  671. portable.topLevel,
  672. key
  673. )
  674. ) {
  675. patch[key] =
  676. clone(
  677. portable
  678. .topLevel[key]
  679. );
  680. }
  681. }
  682.  
  683. if (
  684. Object.keys(
  685. patch
  686. ).length === 0
  687. ) {
  688. return;
  689. }
  690.  
  691. /*
  692. * Only safe top-level values.
  693. *
  694. * No:
  695. * - layers
  696. * - layout
  697. * - coordinates
  698. * - selection
  699. * - segments
  700. * - annotations
  701. */
  702. viewer.state.restoreState(
  703. patch
  704. );
  705. }
  706.  
  707.  
  708. /******************************************************************
  709. * FIND LIVE LAYERS
  710. ******************************************************************/
  711.  
  712. function getManagedLayers() {
  713. const candidates = [
  714. viewer &&
  715. viewer.layerManager &&
  716. viewer.layerManager
  717. .managedLayers,
  718.  
  719. viewer &&
  720. viewer.layerManager &&
  721. viewer.layerManager
  722. .layers,
  723.  
  724. viewer &&
  725. viewer.rootLayers &&
  726. viewer.rootLayers
  727. .managedLayers
  728. ];
  729.  
  730. for (
  731. const candidate of
  732. candidates
  733. ) {
  734. if (
  735. Array.isArray(
  736. candidate
  737. )
  738. ) {
  739. return candidate;
  740. }
  741.  
  742. if (
  743. candidate &&
  744. typeof candidate[
  745. Symbol.iterator
  746. ] === 'function'
  747. ) {
  748. try {
  749. return Array.from(
  750. candidate
  751. );
  752. } catch (_) {
  753. // Continue.
  754. }
  755. }
  756. }
  757.  
  758. return [];
  759. }
  760.  
  761.  
  762. function getManagedLayerName(
  763. managedLayer
  764. ) {
  765. const candidates = [
  766. managedLayer &&
  767. managedLayer.name,
  768.  
  769. managedLayer &&
  770. managedLayer.layer &&
  771. managedLayer.layer.name
  772. ];
  773.  
  774. for (
  775. const value of
  776. candidates
  777. ) {
  778. if (
  779. typeof value ===
  780. 'string' &&
  781. value
  782. ) {
  783. return value;
  784. }
  785.  
  786. if (
  787. value &&
  788. typeof value.value ===
  789. 'string'
  790. ) {
  791. return value.value;
  792. }
  793. }
  794.  
  795. return '';
  796. }
  797.  
  798.  
  799. function getUserLayer(
  800. managedLayer
  801. ) {
  802. if (!managedLayer) {
  803. return null;
  804. }
  805.  
  806. return (
  807. managedLayer.layer ||
  808. managedLayer.userLayer ||
  809. null
  810. );
  811. }
  812.  
  813.  
  814. /******************************************************************
  815. * TRACKABLE SETTERS
  816. ******************************************************************/
  817.  
  818. function setTrackable(
  819. property,
  820. value
  821. ) {
  822. if (!property) {
  823. return false;
  824. }
  825.  
  826.  
  827. if (
  828. typeof property ===
  829. 'object' &&
  830. 'value' in property
  831. ) {
  832. try {
  833. property.value =
  834. clone(value);
  835.  
  836. return true;
  837. } catch (_) {
  838. // Continue.
  839. }
  840. }
  841.  
  842.  
  843. if (
  844. typeof property.restoreState ===
  845. 'function'
  846. ) {
  847. try {
  848. property.restoreState(
  849. clone(value)
  850. );
  851.  
  852. return true;
  853. } catch (_) {
  854. // Continue.
  855. }
  856. }
  857.  
  858. return false;
  859. }
  860.  
  861.  
  862. function setPropertyIfTrackable(
  863. target,
  864. key,
  865. value
  866. ) {
  867. if (!target) {
  868. return false;
  869. }
  870.  
  871. let property;
  872.  
  873. try {
  874. property =
  875. target[key];
  876. } catch (_) {
  877. return false;
  878. }
  879.  
  880. return setTrackable(
  881. property,
  882. value
  883. );
  884. }
  885.  
  886.  
  887. /******************************************************************
  888. * SEGMENTATION DISPLAY TARGETS
  889. ******************************************************************/
  890.  
  891. function getSegmentationDisplayTargets(
  892. liveLayer
  893. ) {
  894. const targets = [];
  895.  
  896. if (!liveLayer) {
  897. return targets;
  898. }
  899.  
  900. if (
  901. liveLayer.displayState
  902. ) {
  903. targets.push(
  904. liveLayer.displayState
  905. );
  906. }
  907.  
  908. if (
  909. liveLayer
  910. .segmentationDisplayState
  911. ) {
  912. targets.push(
  913. liveLayer
  914. .segmentationDisplayState
  915. );
  916. }
  917.  
  918. targets.push(
  919. liveLayer
  920. );
  921.  
  922. return targets;
  923. }
  924.  
  925.  
  926. /******************************************************************
  927. * OPACITY (3D)
  928. ******************************************************************/
  929.  
  930. function setObjectAlpha(
  931. liveLayer,
  932. value
  933. ) {
  934. const normalized =
  935. Number(value);
  936.  
  937. if (
  938. !Number.isFinite(
  939. normalized
  940. )
  941. ) {
  942. return false;
  943. }
  944.  
  945. for (
  946. const target of
  947. getSegmentationDisplayTargets(
  948. liveLayer
  949. )
  950. ) {
  951. if (
  952. setPropertyIfTrackable(
  953. target,
  954. 'objectAlpha',
  955. normalized
  956. )
  957. ) {
  958. return true;
  959. }
  960. }
  961.  
  962. return false;
  963. }
  964.  
  965.  
  966. /******************************************************************
  967. * SHOW ALL BY DEFAULT
  968. ******************************************************************/
  969.  
  970. function setIgnoreNullVisibleSet(
  971. liveLayer,
  972. value
  973. ) {
  974. const normalized =
  975. Boolean(value);
  976.  
  977. for (
  978. const target of
  979. getSegmentationDisplayTargets(
  980. liveLayer
  981. )
  982. ) {
  983. if (
  984. setPropertyIfTrackable(
  985. target,
  986. 'ignoreNullVisibleSet',
  987. normalized
  988. )
  989. ) {
  990. return true;
  991. }
  992. }
  993.  
  994.  
  995. try {
  996. const groupState =
  997. liveLayer &&
  998. liveLayer.displayState &&
  999. liveLayer.displayState
  1000. .segmentationGroupState &&
  1001. liveLayer.displayState
  1002. .segmentationGroupState
  1003. .value;
  1004.  
  1005. if (
  1006. groupState &&
  1007. setPropertyIfTrackable(
  1008. groupState,
  1009. 'ignoreNullVisibleSet',
  1010. normalized
  1011. )
  1012. ) {
  1013. return true;
  1014. }
  1015.  
  1016. } catch (_) {
  1017. // Ignore.
  1018. }
  1019.  
  1020. return false;
  1021. }
  1022.  
  1023.  
  1024. /******************************************************************
  1025. * GENERIC SAFE LAYER PROPERTY
  1026. ******************************************************************/
  1027.  
  1028. function setGenericLayerSetting(
  1029. liveLayer,
  1030. key,
  1031. value
  1032. ) {
  1033. const targets = [
  1034. liveLayer,
  1035.  
  1036. liveLayer &&
  1037. liveLayer.displayState,
  1038.  
  1039. liveLayer &&
  1040. liveLayer
  1041. .segmentationDisplayState,
  1042.  
  1043. liveLayer &&
  1044. liveLayer.renderingOptions
  1045. ];
  1046.  
  1047.  
  1048. for (
  1049. const target of
  1050. targets
  1051. ) {
  1052. if (
  1053. setPropertyIfTrackable(
  1054. target,
  1055. key,
  1056. value
  1057. )
  1058. ) {
  1059. return true;
  1060. }
  1061. }
  1062.  
  1063. return false;
  1064. }
  1065.  
  1066.  
  1067. /******************************************************************
  1068. * APPLY ONE LAYER
  1069. ******************************************************************/
  1070.  
  1071. function applyLayerSettings(
  1072. liveLayer,
  1073. savedLayer
  1074. ) {
  1075. const settings =
  1076. savedLayer.settings ||
  1077. {};
  1078.  
  1079. /*
  1080. * Absolute protection against old saved data.
  1081. */
  1082. delete settings
  1083. .annotationColor;
  1084.  
  1085.  
  1086. let applied = 0;
  1087. let skipped = 0;
  1088.  
  1089.  
  1090. /****************************************************************
  1091. * SEGMENTATION
  1092. ****************************************************************/
  1093.  
  1094. if (
  1095. savedLayer.type ===
  1096. 'segmentation'
  1097. ) {
  1098. if (
  1099. hasOwn(
  1100. settings,
  1101. 'objectAlpha'
  1102. )
  1103. ) {
  1104. if (
  1105. setObjectAlpha(
  1106. liveLayer,
  1107. settings.objectAlpha
  1108. )
  1109. ) {
  1110. applied++;
  1111. } else {
  1112. skipped++;
  1113. }
  1114. }
  1115.  
  1116.  
  1117. if (
  1118. hasOwn(
  1119. settings,
  1120. 'ignoreNullVisibleSet'
  1121. )
  1122. ) {
  1123. if (
  1124. setIgnoreNullVisibleSet(
  1125. liveLayer,
  1126. settings
  1127. .ignoreNullVisibleSet
  1128. )
  1129. ) {
  1130. applied++;
  1131. } else {
  1132. skipped++;
  1133. }
  1134. }
  1135.  
  1136.  
  1137. const keys = [
  1138. 'colorSeed',
  1139. 'hideSegmentZero',
  1140. 'hoverHighlight',
  1141. 'baseSegmentColoring',
  1142. 'selectedAlpha',
  1143. 'notSelectedAlpha',
  1144. 'saturation',
  1145. 'crossSectionRenderScale',
  1146. 'meshRenderScale',
  1147. 'meshSilhouetteRendering',
  1148. 'segmentDefaultColor'
  1149. ];
  1150.  
  1151.  
  1152. for (
  1153. const key of
  1154. keys
  1155. ) {
  1156. if (
  1157. !hasOwn(
  1158. settings,
  1159. key
  1160. )
  1161. ) {
  1162. continue;
  1163. }
  1164.  
  1165. if (
  1166. setGenericLayerSetting(
  1167. liveLayer,
  1168. key,
  1169. settings[key]
  1170. )
  1171. ) {
  1172. applied++;
  1173. } else {
  1174. skipped++;
  1175. }
  1176. }
  1177. }
  1178.  
  1179.  
  1180. /****************************************************************
  1181. * IMAGE
  1182. ****************************************************************/
  1183.  
  1184. if (
  1185. savedLayer.type ===
  1186. 'image'
  1187. ) {
  1188. const keys = [
  1189. 'opacity',
  1190. 'blend',
  1191. 'shader',
  1192. 'shaderControls',
  1193. 'hideInactiveShaderControls',
  1194. 'volumeRendering',
  1195. 'volumeRenderingGain',
  1196. 'volumeRenderingDepthSamples',
  1197. 'crossSectionRenderScale'
  1198. ];
  1199.  
  1200.  
  1201. for (
  1202. const key of
  1203. keys
  1204. ) {
  1205. if (
  1206. !hasOwn(
  1207. settings,
  1208. key
  1209. )
  1210. ) {
  1211. continue;
  1212. }
  1213.  
  1214. if (
  1215. setGenericLayerSetting(
  1216. liveLayer,
  1217. key,
  1218. settings[key]
  1219. )
  1220. ) {
  1221. applied++;
  1222. } else {
  1223. skipped++;
  1224. }
  1225. }
  1226. }
  1227.  
  1228.  
  1229. /****************************************************************
  1230. * TAB
  1231. ****************************************************************/
  1232.  
  1233. if (
  1234. hasOwn(
  1235. settings,
  1236. 'tab'
  1237. )
  1238. ) {
  1239. if (
  1240. setGenericLayerSetting(
  1241. liveLayer,
  1242. 'tab',
  1243. settings.tab
  1244. )
  1245. ) {
  1246. applied++;
  1247. } else {
  1248. skipped++;
  1249. }
  1250. }
  1251.  
  1252.  
  1253. /****************************************************************
  1254. * ANNOTATION TOOL
  1255. *
  1256. * This does NOT alter annotation color.
  1257. ****************************************************************/
  1258.  
  1259. if (
  1260. savedLayer.type ===
  1261. 'annotation' &&
  1262. hasOwn(
  1263. settings,
  1264. 'tool'
  1265. )
  1266. ) {
  1267. if (
  1268. setGenericLayerSetting(
  1269. liveLayer,
  1270. 'tool',
  1271. settings.tool
  1272. )
  1273. ) {
  1274. applied++;
  1275. } else {
  1276. skipped++;
  1277. }
  1278. }
  1279.  
  1280.  
  1281. try {
  1282. if (
  1283. liveLayer &&
  1284. liveLayer
  1285. .specificationChanged &&
  1286. typeof liveLayer
  1287. .specificationChanged
  1288. .dispatch ===
  1289. 'function'
  1290. ) {
  1291. liveLayer
  1292. .specificationChanged
  1293. .dispatch();
  1294. }
  1295. } catch (_) {
  1296. // Harmless.
  1297. }
  1298.  
  1299.  
  1300. return {
  1301. applied,
  1302. skipped
  1303. };
  1304. }
  1305.  
  1306.  
  1307. /******************************************************************
  1308. * IMPORT
  1309. ******************************************************************/
  1310.  
  1311. function importSettings() {
  1312. let portable;
  1313.  
  1314. try {
  1315. portable =
  1316. readSavedSettings();
  1317.  
  1318. } catch (error) {
  1319. console.error(
  1320. '[Import/export settings] Import failed:',
  1321. error
  1322. );
  1323.  
  1324. return;
  1325. }
  1326.  
  1327.  
  1328. if (!portable) {
  1329. console.log(
  1330. '[Import/export settings] Saved settings not found.'
  1331. );
  1332.  
  1333. return;
  1334. }
  1335.  
  1336.  
  1337. /*
  1338. * One last hard guarantee.
  1339. */
  1340. sanitizeAnnotationColors(
  1341. portable
  1342. );
  1343.  
  1344.  
  1345. let before;
  1346.  
  1347. try {
  1348. before =
  1349. getViewerState();
  1350.  
  1351. } catch (error) {
  1352. console.error(
  1353. '[Import/export settings] Could not read viewer state:',
  1354. error
  1355. );
  1356.  
  1357. return;
  1358. }
  1359.  
  1360.  
  1361. /****************************************************************
  1362. * TOP LEVEL
  1363. ****************************************************************/
  1364.  
  1365. try {
  1366. importTopLevelSettings(
  1367. portable
  1368. );
  1369. } catch (error) {
  1370. console.warn(
  1371. '[Import/export settings] Top-level import warning:',
  1372. error
  1373. );
  1374. }
  1375.  
  1376.  
  1377. /****************************************************************
  1378. * LIVE LAYERS
  1379. ****************************************************************/
  1380.  
  1381. const managedLayers =
  1382. getManagedLayers();
  1383.  
  1384. const managedByName =
  1385. new Map();
  1386.  
  1387.  
  1388. for (
  1389. const managedLayer of
  1390. managedLayers
  1391. ) {
  1392. const name =
  1393. getManagedLayerName(
  1394. managedLayer
  1395. );
  1396.  
  1397. if (name) {
  1398. managedByName.set(
  1399. name,
  1400. managedLayer
  1401. );
  1402. }
  1403. }
  1404.  
  1405.  
  1406. let matchingLayers = 0;
  1407. let missingLayers = 0;
  1408. let appliedSettings = 0;
  1409. let skippedSettings = 0;
  1410.  
  1411.  
  1412. for (
  1413. const savedLayer of
  1414. portable.layers
  1415. ) {
  1416. const managedLayer =
  1417. managedByName.get(
  1418. savedLayer.name
  1419. );
  1420.  
  1421. if (!managedLayer) {
  1422. missingLayers++;
  1423. continue;
  1424. }
  1425.  
  1426.  
  1427. const liveLayer =
  1428. getUserLayer(
  1429. managedLayer
  1430. );
  1431.  
  1432. if (!liveLayer) {
  1433. missingLayers++;
  1434. continue;
  1435. }
  1436.  
  1437.  
  1438. matchingLayers++;
  1439.  
  1440.  
  1441. const result =
  1442. applyLayerSettings(
  1443. liveLayer,
  1444. savedLayer
  1445. );
  1446.  
  1447.  
  1448. appliedSettings +=
  1449. result.applied;
  1450.  
  1451. skippedSettings +=
  1452. result.skipped;
  1453. }
  1454.  
  1455.  
  1456. /****************************************************************
  1457. * DIAGNOSTICS
  1458. ****************************************************************/
  1459.  
  1460. let after = null;
  1461.  
  1462. try {
  1463. after =
  1464. getViewerState();
  1465. } catch (_) {
  1466. // Diagnostic only.
  1467. }
  1468.  
  1469.  
  1470. console.log(
  1471. '[Import/export settings] Safe import complete. Annotation colors were NOT touched.',
  1472. {
  1473. matchingLayers,
  1474. missingLayers,
  1475. appliedSettings,
  1476. skippedSettings,
  1477.  
  1478. positionUnchanged:
  1479. after
  1480. ? (
  1481. JSON.stringify(
  1482. before.position
  1483. ) ===
  1484. JSON.stringify(
  1485. after.position
  1486. )
  1487. )
  1488. : 'unknown',
  1489.  
  1490. layoutUnchanged:
  1491. after
  1492. ? (
  1493. JSON.stringify(
  1494. before.layout
  1495. ) ===
  1496. JSON.stringify(
  1497. after.layout
  1498. )
  1499. )
  1500. : 'unknown',
  1501.  
  1502. annotationColorsPreserved:
  1503. after
  1504. ? compareAnnotationColors(
  1505. before,
  1506. after
  1507. )
  1508. : 'unknown',
  1509.  
  1510. savedObjectAlpha:
  1511. getSavedSegmentationSetting(
  1512. portable,
  1513. 'objectAlpha'
  1514. ),
  1515.  
  1516. resultingObjectAlpha:
  1517. after
  1518. ? getCurrentSegmentationSetting(
  1519. after,
  1520. 'objectAlpha',
  1521. 1
  1522. )
  1523. : 'unknown',
  1524.  
  1525. savedIgnoreNullVisibleSet:
  1526. getSavedSegmentationSetting(
  1527. portable,
  1528. 'ignoreNullVisibleSet'
  1529. ),
  1530.  
  1531. resultingIgnoreNullVisibleSet:
  1532. after
  1533. ? getCurrentSegmentationSetting(
  1534. after,
  1535. 'ignoreNullVisibleSet',
  1536. true
  1537. )
  1538. : 'unknown'
  1539. }
  1540. );
  1541. }
  1542.  
  1543.  
  1544. /******************************************************************
  1545. * DIAGNOSTICS
  1546. ******************************************************************/
  1547.  
  1548. function compareAnnotationColors(
  1549. before,
  1550. after
  1551. ) {
  1552. function getMap(
  1553. state
  1554. ) {
  1555. const result =
  1556. new Map();
  1557.  
  1558. if (
  1559. !state ||
  1560. !Array.isArray(
  1561. state.layers
  1562. )
  1563. ) {
  1564. return result;
  1565. }
  1566.  
  1567.  
  1568. for (
  1569. const layer of
  1570. state.layers
  1571. ) {
  1572. if (
  1573. !layer ||
  1574. layer.type !==
  1575. 'annotation'
  1576. ) {
  1577. continue;
  1578. }
  1579.  
  1580. result.set(
  1581. layer.name,
  1582. hasOwn(
  1583. layer,
  1584. 'annotationColor'
  1585. )
  1586. ? JSON.stringify(
  1587. layer.annotationColor
  1588. )
  1589. : '__DEFAULT__'
  1590. );
  1591. }
  1592.  
  1593. return result;
  1594. }
  1595.  
  1596.  
  1597. const a =
  1598. getMap(before);
  1599.  
  1600. const b =
  1601. getMap(after);
  1602.  
  1603.  
  1604. if (
  1605. a.size !== b.size
  1606. ) {
  1607. return false;
  1608. }
  1609.  
  1610.  
  1611. for (
  1612. const [
  1613. name,
  1614. color
  1615. ] of a
  1616. ) {
  1617. if (
  1618. b.get(name) !==
  1619. color
  1620. ) {
  1621. return false;
  1622. }
  1623. }
  1624.  
  1625.  
  1626. return true;
  1627. }
  1628.  
  1629.  
  1630. function getSavedSegmentationSetting(
  1631. portable,
  1632. key
  1633. ) {
  1634. const layer =
  1635. portable.layers.find(
  1636. item =>
  1637. item &&
  1638. item.type ===
  1639. 'segmentation'
  1640. );
  1641.  
  1642.  
  1643. if (
  1644. !layer ||
  1645. !layer.settings
  1646. ) {
  1647. return undefined;
  1648. }
  1649.  
  1650.  
  1651. return layer
  1652. .settings[key];
  1653. }
  1654.  
  1655.  
  1656. function getCurrentSegmentationSetting(
  1657. state,
  1658. key,
  1659. defaultValue
  1660. ) {
  1661. if (
  1662. !state ||
  1663. !Array.isArray(
  1664. state.layers
  1665. )
  1666. ) {
  1667. return defaultValue;
  1668. }
  1669.  
  1670.  
  1671. const layer =
  1672. state.layers.find(
  1673. item =>
  1674. item &&
  1675. item.type ===
  1676. 'segmentation'
  1677. );
  1678.  
  1679.  
  1680. if (!layer) {
  1681. return defaultValue;
  1682. }
  1683.  
  1684.  
  1685. return hasOwn(
  1686. layer,
  1687. key
  1688. )
  1689. ? layer[key]
  1690. : defaultValue;
  1691. }
  1692.  
  1693.  
  1694. /******************************************************************
  1695. * MENU
  1696. ******************************************************************/
  1697.  
  1698. let target;
  1699.  
  1700.  
  1701. function addMenu() {
  1702. const menu =
  1703. document.createElement(
  1704. 'div'
  1705. );
  1706.  
  1707.  
  1708. menu.style.position =
  1709. 'fixed';
  1710.  
  1711. menu.style.background =
  1712. '#222';
  1713.  
  1714. menu.style.color =
  1715. '#fff';
  1716.  
  1717. menu.style.border =
  1718. '1px solid #555';
  1719.  
  1720. menu.style.padding =
  1721. '5px 0';
  1722.  
  1723. menu.style.display =
  1724. 'none';
  1725.  
  1726. menu.style.zIndex =
  1727. '10000';
  1728.  
  1729. menu.style.minWidth =
  1730. '140px';
  1731.  
  1732. menu.style.fontSize =
  1733. '13px';
  1734.  
  1735. menu.style.fontFamily =
  1736. 'Arial, sans-serif';
  1737.  
  1738.  
  1739. function createItem(
  1740. text,
  1741. onClick
  1742. ) {
  1743. const item =
  1744. document.createElement(
  1745. 'div'
  1746. );
  1747.  
  1748.  
  1749. item.textContent =
  1750. text;
  1751.  
  1752. item.style.padding =
  1753. '7px 10px';
  1754.  
  1755. item.style.cursor =
  1756. 'pointer';
  1757.  
  1758.  
  1759. item.addEventListener(
  1760. 'mouseenter',
  1761. () => {
  1762. item.style.background =
  1763. '#444';
  1764. }
  1765. );
  1766.  
  1767.  
  1768. item.addEventListener(
  1769. 'mouseleave',
  1770. () => {
  1771. item.style.background =
  1772. 'transparent';
  1773. }
  1774. );
  1775.  
  1776.  
  1777. item.addEventListener(
  1778. 'click',
  1779. event => {
  1780. event.stopPropagation();
  1781.  
  1782. onClick();
  1783.  
  1784. hideMenu();
  1785. }
  1786. );
  1787.  
  1788.  
  1789. return item;
  1790. }
  1791.  
  1792.  
  1793. function hideMenu() {
  1794. menu.style.display =
  1795. 'none';
  1796. }
  1797.  
  1798.  
  1799. menu.appendChild(
  1800. createItem(
  1801. 'Export settings',
  1802. () => {
  1803. if (
  1804. window.confirm(
  1805. 'Export current portable rendering settings?\n\nAnnotation colors will NOT be saved or changed.'
  1806. )
  1807. ) {
  1808. exportSettings();
  1809. }
  1810. }
  1811. )
  1812. );
  1813.  
  1814.  
  1815. menu.appendChild(
  1816. createItem(
  1817. 'Import settings',
  1818. () => {
  1819. importSettings();
  1820. }
  1821. )
  1822. );
  1823.  
  1824.  
  1825. document.body.appendChild(
  1826. menu
  1827. );
  1828.  
  1829.  
  1830. target.addEventListener(
  1831. 'contextmenu',
  1832. event => {
  1833. event.preventDefault();
  1834.  
  1835. menu.style.left =
  1836. event.clientX +
  1837. 'px';
  1838.  
  1839. menu.style.top =
  1840. event.clientY +
  1841. 'px';
  1842.  
  1843. menu.style.display =
  1844. 'block';
  1845. }
  1846. );
  1847.  
  1848.  
  1849. document.addEventListener(
  1850. 'click',
  1851. hideMenu
  1852. );
  1853.  
  1854.  
  1855. window.addEventListener(
  1856. 'blur',
  1857. hideMenu
  1858. );
  1859.  
  1860.  
  1861. /****************************************************************
  1862. * X = export
  1863. * / = import
  1864. ****************************************************************/
  1865.  
  1866. document.body.addEventListener(
  1867. 'keyup',
  1868. event => {
  1869. const element =
  1870. event.target;
  1871.  
  1872.  
  1873. const tagName =
  1874. element &&
  1875. element.tagName
  1876. ? element.tagName
  1877. .toLowerCase()
  1878. : '';
  1879.  
  1880.  
  1881. const typing =
  1882. tagName ===
  1883. 'input' ||
  1884. tagName ===
  1885. 'textarea' ||
  1886. (
  1887. element &&
  1888. element.isContentEditable
  1889. );
  1890.  
  1891.  
  1892. if (typing) {
  1893. return;
  1894. }
  1895.  
  1896.  
  1897. switch (
  1898. event.key
  1899. ) {
  1900. case 'x':
  1901. case 'X':
  1902. exportSettings();
  1903. break;
  1904.  
  1905. case '/':
  1906. importSettings();
  1907. break;
  1908. }
  1909. }
  1910. );
  1911. }
  1912.  
  1913.  
  1914. /******************************************************************
  1915. * STARTUP
  1916. ******************************************************************/
  1917.  
  1918. const waitForTopBar =
  1919. window.setInterval(
  1920. () => {
  1921. target =
  1922. document.querySelector(
  1923. '.neuroglancer-viewer-top-row'
  1924. );
  1925.  
  1926.  
  1927. if (!target) {
  1928. return;
  1929. }
  1930.  
  1931.  
  1932. window.clearInterval(
  1933. waitForTopBar
  1934. );
  1935.  
  1936.  
  1937. addMenu();
  1938. },
  1939. 100
  1940. );
  1941.  
  1942. })();
Advertisement
Add Comment
Please, Sign In to add comment