From 2d7086100149b90a5f7744ba765fbf4a8fa7707e Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 2 Feb 2026 20:31:00 +0000 Subject: [PATCH 01/29] Revert "Remove APP_SERVED_BY_OMERO and Luts display logic" This reverts commit 89c41f0cca9aac7f51ac48d54b88134e2ddb4fa0. --- omero_figure/views.py | 2 ++ src/css/figure.css | 3 +- src/index.html | 2 ++ src/js/views/channel_slider_view.js | 15 +++++++--- src/js/views/lutpicker.js | 34 ++++++++++++++++++---- src/templates/channel_slider.template.html | 4 +-- src/templates/lut_picker.template.html | 6 +++- 7 files changed, 53 insertions(+), 13 deletions(-) diff --git a/omero_figure/views.py b/omero_figure/views.py index c62992a49..c0b65bb73 100644 --- a/omero_figure/views.py +++ b/omero_figure/views.py @@ -113,6 +113,8 @@ def index(request, file_id=None, conn=None, **kwargs): # Load the template html and replace OMEROWEB_INDEX template = loader.get_template("omero_figure/index.html") html = template.render({}, request) + html = html.replace('const APP_SERVED_BY_OMERO = false;', + 'const APP_SERVED_BY_OMERO = true;') omeroweb_index = reverse("index") figure_index = reverse("figure_index") ping_url = reverse("keepalive_ping") diff --git a/src/css/figure.css b/src/css/figure.css index 96a58f15f..dd9f6f4e2 100644 --- a/src/css/figure.css +++ b/src/css/figure.css @@ -1037,7 +1037,7 @@ text-align: left; } - .lutOption span { + .lutOption span, .lutOption img { width: 85px; display: inline-block; } @@ -1052,6 +1052,7 @@ /* NB: when updating png, consider using different name to avoid cache */ background-size: 100% var(--pngHeight); background-image: var(--lutPng); + background-position: var(--bgPos); background-repeat: no-repeat; image-rendering: pixelated; /* Universal support since 2021 */ } diff --git a/src/index.html b/src/index.html index 3d1ff3471..808e7f6d2 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,8 @@ // For dev, use this server to load/save figures (needs CORS enabled) const dev_omeroweb_url = "http://localhost:4080/"; // These are updated by views.py when serving this page... + // Flag for knowing if app is served from OMERO (or standalone) + const APP_SERVED_BY_OMERO = false; const BASE_OMEROWEB_URL = dev_omeroweb_url; const EXPORT_ENABLED = false; const SCRIPT_VERSION = ""; diff --git a/src/js/views/channel_slider_view.js b/src/js/views/channel_slider_view.js index a5253b9b6..7aefeb056 100644 --- a/src/js/views/channel_slider_view.js +++ b/src/js/views/channel_slider_view.js @@ -401,10 +401,17 @@ var ChannelSliderView = Backbone.View.extend({ var active = actives.reduce(allEqualFn, actives[0]); var style = {'background-position': '0 0'} var lutBgPos = FigureLutPicker.getLutBackgroundPosition(color); - if (color.endsWith('.lut')) { - style['background-position'] = lutBgPos; + var lutPng = FigureLutPicker.getLutPng(color); + console.log("lutPng", lutPng); + var lutBgCss = '--bgPos: 0 0;'; + if (color.endsWith('.lut') && lutPng) { + // lutPng means we have LUT from ome-zarr.js. Apply without offset + lutBgCss = `--bgPos: 0 0; --lutPng: url('${lutPng}'); --pngHeight: 100%;`; color = "ccc"; - } else if (color.toUpperCase() === "FFFFFF") { + } else { + lutBgCss = `--bgPos: ${lutBgPos};` + } + if (color.toUpperCase() === "FFFFFF") { color = "ccc"; // white slider would be invisible } if (color == "FFFFFF") color = "ccc"; // white slider would be invisible @@ -423,7 +430,7 @@ var ChannelSliderView = Backbone.View.extend({ 'max': max, 'step': (max - min > SLIDER_INCR_CUTOFF) ? 1 : 0.01, 'active': active, - 'lutBgPos': lutBgPos, + 'lutBgCss': lutBgCss, 'reverse': reverse, 'color': color, 'isDark': this.isDark(color) diff --git a/src/js/views/lutpicker.js b/src/js/views/lutpicker.js index 304eb137e..fd44c9e23 100644 --- a/src/js/views/lutpicker.js +++ b/src/js/views/lutpicker.js @@ -21,6 +21,7 @@ import Backbone from "backbone"; import $ from "jquery"; import _ from 'underscore'; import * as bootstrap from "bootstrap" +import * as omezarr from "ome-zarr.js"; import lut_picker_template from '../../templates/lut_picker.template.html?raw'; import { showModal } from "./util"; @@ -38,6 +39,10 @@ var LutPickerView = Backbone.View.extend({ initialize:function () { this.lutModal = new bootstrap.Modal('#lutpickerModal'); this.lut_names = []; + + // These will be overwritten with LUTs from OMERO if served by OMERO + this.luts = omezarr.getLuts(); + this.loadLuts(); }, @@ -58,13 +63,24 @@ var LutPickerView = Backbone.View.extend({ this.pickedLut = lutName; // Update preview to show LUT - var bgPos = this.getLutBackgroundPosition(lutName); - $(".lutPreview", this.el).css({'background-position': bgPos, 'background-image': `url(${this.lutsPngUrl})`}); + var lut = this.luts.find(lut => lut.name == lutName); + if (lut.png) { + $(".lutPreview", this.el).css({'background-position': '0 0', 'background-image': "url('" + lut.png + "')", 'background-size': '100% 100%'}); + } else { + var bgPos = this.getLutBackgroundPosition(lutName); + $(".lutPreview", this.el).css({'background-position': bgPos, 'background-image': `url(${this.lutsPngUrl})`}); + } // Enable OK button $("button[type='submit']", this.el).removeAttr('disabled'); }, loadLuts: async function(force_reload = false) { + if (!APP_SERVED_BY_OMERO) { + $(":root").css({ + "--pngHeight": "100%" + }); + return; + } var url = WEBGATEWAYINDEX + 'luts/'; let cors_headers = { mode: 'cors', credentials: 'include' }; if (force_reload || this.lut_names === undefined || this.lutsPngUrl === undefined || this.luts === undefined) { @@ -100,6 +116,14 @@ var LutPickerView = Backbone.View.extend({ return this.lutsPngUrl }, + getLutPng(lutName) { + // This will return undefined if the LUTs are loaded from OMERO + var lut = this.luts.find(lut => lut.name == lutName); + if (lut && lut.png) { + return lut.png; + } + }, + getLutBackgroundPosition: function(lutName) { var lutIndex = this.lut_names.indexOf(lutName); if (lutIndex > -1) { @@ -125,8 +149,7 @@ var LutPickerView = Backbone.View.extend({ if (options.success) { this.success = options.success; } - - this.loadLuts().then(() => this.render()); + this.render(); }, render:function() { @@ -140,6 +163,7 @@ var LutPickerView = Backbone.View.extend({ // Add css background-position to each lut to offset luts_10.png return {'bgPos': this.getLutBackgroundPosition(lut.name), 'name': lut.name, + 'png': lut.png, // if LUT is from omezarr.js, we have png in hand 'displayName': this.formatLutName(lut.name)}; }.bind(this)); @@ -151,4 +175,4 @@ var LutPickerView = Backbone.View.extend({ const FigureLutPicker = new LutPickerView(); -export default FigureLutPicker +export default FigureLutPicker; diff --git a/src/templates/channel_slider.template.html b/src/templates/channel_slider.template.html index 9ec01ade3..9d71d1e23 100644 --- a/src/templates/channel_slider.template.html +++ b/src/templates/channel_slider.template.html @@ -7,7 +7,7 @@ title="<%= _.escape(label) %>" class="btn btn-default channel-btn lutBg <% if (isDark) { print('font-white') } %>" data-index="<%= idx %>" - style="background-color:#<%= color %>; background-position: <%= lutBgPos %>;"> + style="background-color:#<%= color %>; <%= lutBgCss %>"> <%= _.escape(label) %>
 
@@ -72,7 +72,7 @@ min="<%= min %>" max="<%= max %>" step="<%= step %>" type="range" value="<%= startAvg %>" style="background-color:#<%=color%>; - --bgPos: <%= lutBgPos %>; + <%= lutBgCss %> --scaleX: <%= reverse ? -1 : 1 %>"/> <% if (i === parseInt(luts.length/2)) { %> From e04f3f2117b2949df1b19f63cb13a9283a0d4dfc Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 11 Feb 2025 13:29:15 +0000 Subject: [PATCH 02/29] Add gh-pages workflow to build and deploy --- .github/workflows/pages.yml | 28 ++++++++++++++++++++++++++++ .gitignore | 1 + package.json | 1 + 3 files changed, 30 insertions(+) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 000000000..3e417baa2 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,28 @@ +name: Build and Deploy +on: + push: + branches: + - master + workflow_dispatch: +permissions: + contents: write +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v4.2.2 + with: + persist-credentials: false + + - name: Install and Build 🔧 + run: | + npm install + npm run ghpages + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@v4.7.2 + with: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: gh_pages + CLEAN: true diff --git a/.gitignore b/.gitignore index d3d7b7c0e..b2655ec38 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ demo/omero_figure/ demo/figure.js demo/index.html node_modules/ +gh_pages/ _site dist omero_figure.egg-info diff --git a/package.json b/package.json index 168c3241b..e4cf1e142 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "start": "vite", "build": "vite build --emptyOutDir && ./deploy_build.sh", + "ghpages": "vite build --outDir ../gh_pages", "test": "echo \"Error: no test specified\" && exit 1", "watch": "watch 'npm run build' ./src" }, From 85cbe85c178691ebbf9fd9cad587c7152c9cd690 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 11 Feb 2025 13:48:37 +0000 Subject: [PATCH 03/29] Include .nojekyll in gh-pages deployment --- deploy_ghpages.sh | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100755 deploy_ghpages.sh diff --git a/deploy_ghpages.sh b/deploy_ghpages.sh new file mode 100755 index 000000000..79b75a458 --- /dev/null +++ b/deploy_ghpages.sh @@ -0,0 +1,7 @@ + +# To ensure that the gh-pages job doesn't try to build a jekyll site +# (and ignore files in /assets), we create a .nojekyll file in the +# ./gh_pages/ output dir. The pages.yml workflow copies all of that dir +# to the gh-pages branch. + +touch gh_pages/.nojekyll diff --git a/package.json b/package.json index e4cf1e142..51ebf3c8d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "vite", "build": "vite build --emptyOutDir && ./deploy_build.sh", - "ghpages": "vite build --outDir ../gh_pages", + "ghpages": "vite build --outDir ../gh_pages && ./deploy_ghpages.sh", "test": "echo \"Error: no test specified\" && exit 1", "watch": "watch 'npm run build' ./src" }, From 454e8b05d112e06a92600bc61b63d6474392cf7d Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 11 Feb 2025 14:57:54 +0000 Subject: [PATCH 04/29] Use ./ build path for gh-pages for ./assets as sibling to index.html --- omero_figure/views.py | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/omero_figure/views.py b/omero_figure/views.py index c0b65bb73..c403b5a25 100644 --- a/omero_figure/views.py +++ b/omero_figure/views.py @@ -148,8 +148,8 @@ def index(request, file_id=None, conn=None, **kwargs): # update links to static files static_dir = static.static('omero_figure/') - html = html.replace('href="/', 'href="%s' % static_dir) - html = html.replace('src="/', 'src="%s' % static_dir) + html = html.replace('href="/assets', 'href="%sassets' % static_dir) + html = html.replace('src="/assets', 'src="%sassets' % static_dir) html = html.replace('const STATIC_DIR = "";', 'const STATIC_DIR = "%s";' % static_dir[0:-1]) diff --git a/package.json b/package.json index 51ebf3c8d..2ecec5562 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "vite", "build": "vite build --emptyOutDir && ./deploy_build.sh", - "ghpages": "vite build --outDir ../gh_pages && ./deploy_ghpages.sh", + "ghpages": "vite build --base=./ --outDir ../gh_pages && ./deploy_ghpages.sh", "test": "echo \"Error: no test specified\" && exit 1", "watch": "watch 'npm run build' ./src" }, From daa7e3b226f13626cff62ec97660cf49d0d6fd06 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 4 Mar 2025 15:30:31 +0000 Subject: [PATCH 05/29] Support /figure/?file=http://url.figure.json --- src/js/main.js | 9 +++++++++ src/js/models/figure_model.js | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/js/main.js b/src/js/main.js index 48ff23495..2697027e8 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -84,6 +84,15 @@ var FigureRouter = Backbone.Router.extend({ index: function () { console.log("index"); + // Check for ?file=http://...json + if (window.location.search.length > 1) { + const searchParams = new URLSearchParams(window.location.search.substring(1)); + if (searchParams.has("file")) { + const file = searchParams.get("file"); + figureModel.load_from_url(file); + return; + } + } hideModals(); var cb = () => { showModal("welcomeModal"); diff --git a/src/js/models/figure_model.js b/src/js/models/figure_model.js index 728f6c9a1..b9efe9261 100644 --- a/src/js/models/figure_model.js +++ b/src/js/models/figure_model.js @@ -358,6 +358,15 @@ return figureJSON; }, + load_from_url: function(url) { + // load content from a URL + console.log("load_from_url...", url); + $.getJSON(url, function(data){ + this.load_from_JSON(data); + this.set('unsaved', true); + }.bind(this)); + }, + figure_fromJSON: function(data) { var parsed = JSON.parse(data); delete parsed.fileId; From 96841c8852023ca8a46978e00d1a75f914ce5da6 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 4 Mar 2025 15:43:29 +0000 Subject: [PATCH 06/29] Handle invalid file url --- src/js/models/figure_model.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/models/figure_model.js b/src/js/models/figure_model.js index b9efe9261..390550b62 100644 --- a/src/js/models/figure_model.js +++ b/src/js/models/figure_model.js @@ -364,7 +364,10 @@ $.getJSON(url, function(data){ this.load_from_JSON(data); this.set('unsaved', true); - }.bind(this)); + }.bind(this)) + .fail(function(){ + alert("Failed to load figure from URL: " + url); + }); }, figure_fromJSON: function(data) { From f429237cc867e4ea1bcf8ad4986ca9c49a76135d Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 6 Mar 2025 13:45:38 +0000 Subject: [PATCH 07/29] Save if no OMERO downloads figure.json --- src/js/models/figure_model.js | 12 ++++++++++++ src/js/views/figure_view.js | 16 +++++++++++----- src/js/views/util.js | 13 +++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/js/models/figure_model.js b/src/js/models/figure_model.js index 390550b62..2bbb85b2f 100644 --- a/src/js/models/figure_model.js +++ b/src/js/models/figure_model.js @@ -6,6 +6,7 @@ import {PanelList, Panel} from "./panel_model"; import { recoverFigureFromStorage, clearFigureFromStorage, + downloadAsFile, figureConfirmDialog, getJsonWithCredentials, saveFigureToStorage} from "../views/util"; @@ -393,6 +394,17 @@ } }, + save_to_download: function(options) { + // Downloads the FigureJSON as a file + let figureJSON = this.figure_toJSON(); + if (options.figureName) { + figureJSON.figureName = options.figureName; + } + let fileName = figureJSON.figureName || "figure"; + let jsonText = JSON.stringify(this.figure_toJSON()); + downloadAsFile(jsonText, "application/json", fileName + ".json"); + }, + save_to_OMERO: function(options) { var self = this, diff --git a/src/js/views/figure_view.js b/src/js/views/figure_view.js index 3482a5d16..2404b7c66 100644 --- a/src/js/views/figure_view.js +++ b/src/js/views/figure_view.js @@ -575,20 +575,26 @@ var self = this; options = options || {}; - var defaultName = this.model.get('figureName'); - if (!defaultName) { - defaultName = this.model.getDefaultFigureName(); + var figureName = this.model.get('figureName'); + if (!figureName) { + var defaultName = this.model.getDefaultFigureName(); + figureName = prompt("Enter Figure Name", defaultName); } - var figureName = prompt("Enter Figure Name", defaultName); var nav = function(data){ - console.log("nav", data, self.app); + // Update URL to new file (with ID) self.app.navigate("file/"+data); // in case you've Saved a copy of a file you can't edit self.model.set('canEdit', true); }; if (figureName) { options.figureName = figureName; + + if (!APP_SERVED_BY_OMERO) { + this.model.set('figureName', figureName); + this.model.save_to_download(options); + return; + } // On save, go to newly saved page, unless we have callback already options.success = options.success || nav; // Save diff --git a/src/js/views/util.js b/src/js/views/util.js index 35ee9345c..58fb80179 100644 --- a/src/js/views/util.js +++ b/src/js/views/util.js @@ -355,3 +355,16 @@ export function updateRoiIds(panelsJson) { return updatedPanels; } + +export function downloadAsFile(text, fileType, fileName) { + // https://javascript.plainenglish.io/javascript-create-file-c36f8bccb3be + const file = new File([text], fileName, {type: fileType}) + const link = document.createElement('a') + const url = URL.createObjectURL(file) + link.href = url + link.download = file.name + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(url) +} From 2dff3bb5d5d23f2ce1d2f374a46f95c715945fb5 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 6 Mar 2025 14:38:50 +0000 Subject: [PATCH 08/29] vite.config.js base:'/figure/' to match gh-pages --- src/js/main.js | 31 ++++++++++++++++++++++++------- vite.config.js | 3 ++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 2697027e8..97417e86d 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -73,18 +73,35 @@ var undoManager = new UndoManager({ figureModel: figureModel }), // Finally, start listening for changes to panels undoManager.listenToCollection(figureModel.panels); + +let routes = { + "": "index", + "new(/)": "newFigure", + "recover(/)": "recoverFigure", + "open(/)": "openFigure", + "file/:id(/)": "loadFigure", +}; + +if (!APP_SERVED_BY_OMERO) { + // vite.config.js has base: "/figure/" + // so we deploy with gh-pages to https://ome.github.io/figure/ + routes = { + "figure(/)": "index", + "figure/new(/)": "newFigure", + "figure/recover(/)": "recoverFigure", + "figure/open(/)": "openFigure", + // only for development + "figure/file/:id(/)": "loadFigure", + }; +} + var FigureRouter = Backbone.Router.extend({ - routes: { - "": "index", - "new(/)": "newFigure", - "recover(/)": "recoverFigure", - "open(/)": "openFigure", - "file/:id(/)": "loadFigure", - }, + routes: routes, index: function () { console.log("index"); // Check for ?file=http://...json + // TODO: do we ONLY want to do this on index? if (window.location.search.length > 1) { const searchParams = new URLSearchParams(window.location.search.substring(1)); if (searchParams.has("file")) { diff --git a/vite.config.js b/vite.config.js index 27d0dc16b..4560c070a 100755 --- a/vite.config.js +++ b/vite.config.js @@ -23,5 +23,6 @@ export default { outDir: "../omero_figure/static/omero_figure/" }, - assetsInclude: ["**/*.template.html"] + assetsInclude: ["**/*.template.html"], + base: "/figure/" } From 355cf5277a4286333eba640827fbfd83185cf2f4 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 6 Mar 2025 14:57:21 +0000 Subject: [PATCH 09/29] Update vite.config base:omero-figure --- src/js/main.js | 14 +++++++------- vite.config.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 97417e86d..534b60dd5 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -83,15 +83,15 @@ let routes = { }; if (!APP_SERVED_BY_OMERO) { - // vite.config.js has base: "/figure/" - // so we deploy with gh-pages to https://ome.github.io/figure/ + // vite.config.js has base: "/omero-figure/" + // so we deploy with gh-pages to https://ome.github.io/omero-figure/ routes = { - "figure(/)": "index", - "figure/new(/)": "newFigure", - "figure/recover(/)": "recoverFigure", - "figure/open(/)": "openFigure", + "omero-figure(/)": "index", + "omero-figure/new(/)": "newFigure", + "omero-figure/recover(/)": "recoverFigure", + "omero-figure/open(/)": "openFigure", // only for development - "figure/file/:id(/)": "loadFigure", + "omero-figure/file/:id(/)": "loadFigure", }; } diff --git a/vite.config.js b/vite.config.js index 4560c070a..4366b95e8 100755 --- a/vite.config.js +++ b/vite.config.js @@ -24,5 +24,5 @@ export default { }, assetsInclude: ["**/*.template.html"], - base: "/figure/" + base: "/omero-figure/" } From eb0cb6659662768c23bf6121b74ee52c3cc9e82d Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 7 Mar 2025 10:22:31 +0000 Subject: [PATCH 10/29] File > Open loads from local. 'base' url is 'omero-figure' To support deployment under /omero-figure/ on gh-pages --- omero_figure/views.py | 4 +- package.json | 1 + src/index.html | 67 +++++++++++++++++++++++- src/js/main.js | 8 ++- src/js/models/figure_model.js | 2 +- src/js/views/figure_view.js | 11 +++- src/js/views/open_local_file_modal.js | 73 +++++++++++++++++++++++++++ 7 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 src/js/views/open_local_file_modal.js diff --git a/omero_figure/views.py b/omero_figure/views.py index c403b5a25..239f667a0 100644 --- a/omero_figure/views.py +++ b/omero_figure/views.py @@ -148,8 +148,8 @@ def index(request, file_id=None, conn=None, **kwargs): # update links to static files static_dir = static.static('omero_figure/') - html = html.replace('href="/assets', 'href="%sassets' % static_dir) - html = html.replace('src="/assets', 'src="%sassets' % static_dir) + html = html.replace('href="/omero-figure/assets', 'href="%sassets' % static_dir) + html = html.replace('src="/omero-figure/assets', 'src="%sassets' % static_dir) html = html.replace('const STATIC_DIR = "";', 'const STATIC_DIR = "%s";' % static_dir[0:-1]) diff --git a/package.json b/package.json index 2ecec5562..70c5bedf6 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "description": "OMERO figure creation app", "main": "index.js", "scripts": { + "dev": "vite", "start": "vite", "build": "vite build --emptyOutDir && ./deploy_build.sh", "ghpages": "vite build --base=./ --outDir ../gh_pages && ./deploy_ghpages.sh", diff --git a/src/index.html b/src/index.html index 808e7f6d2..740bce0e7 100644 --- a/src/index.html +++ b/src/index.html @@ -995,7 +995,7 @@

Legend

- + + +