Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2d70861
Revert "Remove APP_SERVED_BY_OMERO and Luts display logic"
will-moore Feb 2, 2026
e04f3f2
Add gh-pages workflow to build and deploy
will-moore Feb 11, 2025
85cbe85
Include .nojekyll in gh-pages deployment
will-moore Feb 11, 2025
454e8b0
Use ./ build path for gh-pages for ./assets as sibling to index.html
will-moore Feb 11, 2025
daa7e3b
Support /figure/?file=http://url.figure.json
will-moore Mar 4, 2025
96841c8
Handle invalid file url
will-moore Mar 4, 2025
f429237
Save if no OMERO downloads figure.json
will-moore Mar 6, 2025
2dff3bb
vite.config.js base:'/figure/' to match gh-pages
will-moore Mar 6, 2025
355cf52
Update vite.config base:omero-figure
will-moore Mar 6, 2025
eb0cb66
File > Open loads from local. 'base' url is 'omero-figure'
will-moore Mar 7, 2025
e626fd5
set unsaved:false on download
will-moore Mar 13, 2025
db92fa2
Hide omero-only UI elements, e.g. 'Delete' OR standalone UI elements
will-moore Mar 28, 2025
3d30310
flake8 fix
will-moore Feb 9, 2026
e0d8778
Use BASE_URL of 'omero-figure' only for github-pages deploy
will-moore Feb 10, 2026
b9ab0e6
Handle Export-Figure button for standalone app
will-moore Feb 10, 2026
c495cf3
Fix /assets links with base-url NOT omero-figure
will-moore Feb 12, 2026
870a04c
Try to get BASE_URL right under all conditions
will-moore Feb 25, 2026
b646835
Merge remote-tracking branch 'origin/master' into standalone_app
will-moore Mar 16, 2026
38438c1
Update demoUrl to valid figure - fix whitespace in url and no LUT images
will-moore Mar 17, 2026
4adf6e0
Format and indent File > Save downloaded json
will-moore Mar 17, 2026
ab12d66
Fix Add Images input to allow URLs, not just numbers
will-moore Mar 17, 2026
b6a1d93
Merge remote-tracking branch 'origin/master' into standalone_app
will-moore Mar 26, 2026
63c9d0d
Add link to README local figure export instructions
will-moore Mar 26, 2026
bf4649f
fix colorbar LUT when not served by OMERO
will-moore Mar 26, 2026
6642429
Handle local file-picker; open JSON
will-moore Mar 26, 2026
29e2ec6
Tweak wording in File > Open dialog
will-moore Mar 31, 2026
63d760f
Add deployment of standalone app info to README
will-moore Apr 1, 2026
72eada3
Add standalone app info to the top of README
will-moore Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ demo/omero_figure/
demo/figure.js
demo/index.html
node_modules/
gh_pages/
_site
dist
omero_figure.egg-info
Expand Down
7 changes: 7 additions & 0 deletions deploy_ghpages.sh
Original file line number Diff line number Diff line change
@@ -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
8 changes: 6 additions & 2 deletions omero_figure/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,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")
Expand Down Expand Up @@ -154,8 +156,10 @@ 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])

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
"description": "OMERO figure creation app",
"main": "index.js",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "vite build --emptyOutDir && ./deploy_build.sh",
"ghpages": "vite build --outDir ../gh_pages && ./deploy_ghpages.sh",
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "watch 'npm run build' ./src"
},
Expand Down
3 changes: 2 additions & 1 deletion src/css/figure.css
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,7 @@
text-align: left;
}

.lutOption span {
.lutOption span, .lutOption img {
width: 85px;
display: inline-block;
}
Expand All @@ -1074,6 +1074,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 */
}
Expand Down
80 changes: 76 additions & 4 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OMERO.figure</title>
<script>
window.BASE_URL = "";
// 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 = "";
Expand Down Expand Up @@ -715,12 +718,15 @@ <h5 class="modal-title">Add Images</h5>
</div>
<form class="addImagesForm" role="form">
<div class="modal-body">
<p>
<p class="omero_only_element">
Add images to the figure by entering their IDs, separated by
commas.<br />
Hint: You can also select images in the webclient, click the
link in right panel, copy the URL and paste it here:
</p>
<p class="standalone_only_element">
Add OME-Zarr images to the figure by entering their full URL:
</p>
<div
class="form-group"
style="margin-top: 15px; margin-bottom: 0px"
Expand Down Expand Up @@ -1017,7 +1023,7 @@ <h4>Legend</h4>
</div>
</div>
</div>
<!-- File Open Modal -->
<!-- OMERO File Open Modal -->
<div
class="modal"
id="openFigureModal"
Expand Down Expand Up @@ -1147,6 +1153,71 @@ <h5 class="modal-title">Open</h5>
</div>
</div>
</div>
<!-- Open local File or http://url_to/figure.json -->
<div
class="modal"
id="openLocalFileModal"
tabindex="-1"
role="dialog"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Open File</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<form class="openLocalFileForm" role="form">
<div class="modal-body">
<p>
Enter a URL to an OMERO.figure JSON file...
</p>
<div
class="form-group"
style="margin-top: 15px; margin-bottom: 0px"
>
<input
type="text"
class="form-control figureFileUrl"
placeholder="URL to a figure.json file"
/>
</div>
<div
class="form-group"
style="margin-top: 15px; margin-bottom: 0px"
>
<p>
...or select a local file to open:
</p>
<input type="file" />
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-bs-dismiss="modal"
>
Close
</button>
<button
type="submit"
class="btn btn-primary"
data-bs-dismiss="modal"
disabled="disabled"
>
Open
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Labels from Map Annotations Modal -->
<div
class="modal"
Expand Down Expand Up @@ -1266,10 +1337,11 @@ <h5>Example Label</h5>
<li class="import_json">
<a class="dropdown-item" href="#">Import from JSON...</a>
</li>
<li class="delete_figure">
<!-- omero_only_element will be hidden if not APP_SERVED_BY_OMERO -->
<li class="delete_figure omero_only_element">
<a class="dropdown-item" href="#">Delete</a>
</li>
<li class="chgrp_figure">
<li class="chgrp_figure omero_only_element">
<a class="dropdown-item" href="#">Move Figure to Group...</a>
</li>
<li class="paper_setup">
Expand Down
36 changes: 28 additions & 8 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const figureModel = new FigureModel();
// make this global so we can access it from the browser console
window.figureModel = figureModel;

// This will be '/' unless deployed from gh-pages, when it will be '/omero-figure/'
console.log("import.meta.env", import.meta.env)
window.BASE_URL = import.meta.env.BASE_URL.slice(1); // remove leading slash
console.log("BASE_URL", BASE_URL);
const RELEASE_VERSION = import.meta.env.VITE_VERSION;
console.log("RELEASE_VERSION", RELEASE_VERSION);
document.getElementById("release_version").innerHTML = RELEASE_VERSION;
Expand Down Expand Up @@ -73,17 +77,32 @@ var undoManager = new UndoManager({ figureModel: figureModel }),
// Finally, start listening for changes to panels
undoManager.listenToCollection(figureModel.panels);

// All routes based on BASE_URL, which is either '/' or '/omero-figure/' depending on deployment
let routes = {};
routes[`${BASE_URL}(/)`] = "index";
routes[`${BASE_URL}new(/)`] = "newFigure";
routes[`${BASE_URL}recover(/)`] = "recoverFigure";
routes[`${BASE_URL}open(/)`] = "openFigure";
routes[`${BASE_URL}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")) {
const file = searchParams.get("file");
var cb = function () {
figureModel.load_from_url(file);
};
figureModel.checkSaveAndClear(cb);
return;
}
}
hideModals();
var cb = () => {
showModal("welcomeModal");
Expand Down Expand Up @@ -161,7 +180,8 @@ $(document).on("click", "a", function (ev) {
// check that links are 'internal' to this app
if (href.substring(0, BASE_WEBFIGURE_URL.length) === BASE_WEBFIGURE_URL) {
ev.preventDefault();
href = href.replace(BASE_WEBFIGURE_URL, "/");
let baseUrl = APP_SERVED_BY_OMERO ? "/" : "/omero-figure/";
href = href.replace(BASE_WEBFIGURE_URL, baseUrl);
app.navigate(href, { trigger: true });
}
});
25 changes: 25 additions & 0 deletions src/js/models/figure_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {PanelList, Panel} from "./panel_model";
import { recoverFigureFromStorage,
clearFigureFromStorage,
downloadAsFile,
figureConfirmDialog,
getJsonWithCredentials,
saveFigureToStorage,
Expand Down Expand Up @@ -373,6 +374,18 @@
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', false);
}.bind(this))
.fail(function(){
alert("Failed to load figure from URL: " + url);
});
},

figure_fromJSON: function(data) {
var parsed = JSON.parse(data);
delete parsed.fileId;
Expand All @@ -396,6 +409,18 @@
}
},

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(), null, 2);
downloadAsFile(jsonText, "application/json", fileName + ".json");
this.set({unsaved: false});
},

save_to_OMERO: function(options) {

var self = this,
Expand Down
15 changes: 11 additions & 4 deletions src/js/views/channel_slider_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
Loading
Loading