From 1a4b14004d909bdba7e55e70d46c63eb4df467e6 Mon Sep 17 00:00:00 2001 From: John Arban Lewis Date: Tue, 5 May 2026 20:30:05 -0400 Subject: [PATCH 1/4] add foreground/background picker with v-select image selector Replaces the single-select Gallery UI with a ForegroundBackgroundPicker that lets the user independently set foreground and background imageset layers. AltGalleryItemSelector uses a Vuetify v-select with thumbnail previews in both the closed field and dropdown. Gallery gains useLayerSelectionOrder to keep layer z-order in sync. BubblingGalaxies wires the two selections into separate DetailSummary labels, each opening the info sheet for that specific place. GalleryItemSelector (button-based picker) is kept commented out as an alternative implementation. Built in collab with Claude --- docs/foreground-background-gallery.md | 175 ++++++ src/BubblingGalaxies.vue | 141 ++++- src/components/AltGalleryItemSelector.vue | 318 +++++++++++ src/components/ForegroundBackgroundPicker.vue | 518 ++++++++++++++++++ src/components/Gallery.vue | 30 + src/components/GalleryItemSelector.vue | 205 +++++++ 6 files changed, 1357 insertions(+), 30 deletions(-) create mode 100644 docs/foreground-background-gallery.md create mode 100644 src/components/AltGalleryItemSelector.vue create mode 100644 src/components/ForegroundBackgroundPicker.vue create mode 100644 src/components/GalleryItemSelector.vue diff --git a/docs/foreground-background-gallery.md b/docs/foreground-background-gallery.md new file mode 100644 index 0000000..52fe1a5 --- /dev/null +++ b/docs/foreground-background-gallery.md @@ -0,0 +1,175 @@ +# Foreground / Background Gallery Notes + +This documents the current foreground/background gallery work and the style changes made while getting here. + +## Component Shape + +The gallery is still the component that loads WTML places, creates WWT imageset layers, syncs selected places to layer visibility, and optionally orders selected layers. The foreground/background picker does not load WTML and does not talk directly to `selectedPlaces`. + +The current pieces are: + +- `Gallery.vue`: hidden loader/controller for WTML places, imageset layers, selected layer visibility, opacity emitted from gallery items, and optional layer order. +- `ForegroundBackgroundPicker.vue`: visible foreground/background picker UI. It receives the list of places from the parent, owns the closed/open picker states, and sets only `foreground` or `background`. +- `GalleryItemSelector.vue`: larger open-mode control for one selected slot. It opens the picker for that slot, shows a small thumbnail swatch, and handles that slot's opacity slider. +- `GalleryItem.vue`: still the reusable thumbnail/label/opacity item. It emits `update:opacity` and does not set WWT opacity itself. +- `BubblingGalaxies.vue`: coordinates foreground/background selection with the hidden `Gallery` by converting `[background, foreground]` into `selectedGalleryItems`. + +## Data Flow + +`Gallery` loads `galleryPlaces` from `ngc628_datasets.wtml`. + +`ForegroundBackgroundPicker` gets those places through `v-model:places`, then lets the user choose: + +- `foregroundGalleryItem` +- `backgroundGalleryItem` + +The parent watches those refs and updates the gallery selection: + +```ts +selectedGalleryItems.value = [background, foreground].filter((place): place is Place => place !== null); +``` + +That order is intentional: background first, foreground second. The hidden gallery receives this array through `v-model:selected-places`. + +The default foreground is `"Infrared Stars & Dust (JWST)"`. The background starts as `null`. + +## Layer Ordering + +`Gallery.vue` now has a `useLayerSelectionOrder` prop. + +When enabled, the selected imageset layers are ordered by `selectedPlaces` order. The persistent layer is kept at order `0`, then selected items are pushed to the top of the current imageset layer stack in array order. + +For this foreground/background flow, the parent passes: + +```vue +use-layer-selection-order +``` + +Since the parent writes `selectedGalleryItems` as `[background, foreground]`, this pushes the background up first, then the foreground above it. + +## Opacity + +`GalleryItem.vue` emits opacity changes with `update:opacity`. + +`Gallery.vue` listens for those events in the normal gallery path and applies opacity to its known layer map. + +`ForegroundBackgroundPicker.vue` owns the foreground/background opacity values so the closed preview sliders and open selector sliders stay in sync. + +`ForegroundBackgroundPicker.vue` and `GalleryItemSelector.vue` use the WWT engine store for the foreground/background controls. They find the matching imageset layer by comparing the selected place's imageset URL or name against active WWT imageset layers. Our imageset names and URLs are assumed unique, so this is intentionally simple. + +Opacity is stored by imageset URL/name and applied with: + +```ts +imagesetLayer.value?.set_opacity(opacity); +``` + +The closed view uses `GalleryItem` with `show-opacity`, while the open view passes the same opacity value into each `GalleryItemSelector`. + +## Picker Behavior + +`ForegroundBackgroundPicker` has two visual modes: + +- closed mode: compact `Choose Views` display using `GalleryItem` thumbnails for only the currently selected places +- open mode: expanded selector surface with the two `GalleryItemSelector` controls along the bottom; the mini gallery opens above only after one selector is clicked + +The picker has a simple active-slot model: + +- clicking the foreground selector makes the mini gallery choose the foreground +- clicking the background selector makes the mini gallery choose the background +- selecting a `GalleryItem` writes to whichever slot is active + +The picker does not pass Gallery's `selected` prop through as shared selection state. Instead, it highlights the active slot's current place itself. + +The picker also hides the persistent layer when requested. It computes `shownPlaces` from `places`, `persist`, and `hidePersisted`, then renders only those places. + +## Labels And Info + +The selected place name is no longer shown inside the selector buttons. + +The visible labels are stacked in the parent using `DetailSummary`: + +- foreground label first +- background label second, only when a background is selected + +Opening either label sets `infoGalleryItem` and shows the info sheet for that specific place. Simulation mode still uses the simulation label. + +## Style Changes + +The foreground/background picker CSS was intentionally shaped to look like it came from the existing Gallery CSS: + +- same `--gallery-width`, `--gallery-max-height`, `--gallery-item-height`, `--column-count`, and `--selected-color` variables +- same nested LESS style as `Gallery.vue` +- same blurred glass treatment: transparent base, `rgba(0,0,0,0.5)` when visible, `box-shadow`, and `backdrop-filter: blur(6px)` +- same compact border radius language: mostly `3px` and `5px` +- copied gallery item sizing for the mini picker: `width: var(--gallery-width)`, `--image-width: 96px`, image height `45px` +- retained commented-out gallery CSS where it helped preserve the "copied from Gallery" feel + +The closed foreground/background display is narrowed to the gallery width: + +```css +width: calc(var(--gallery-width) + 10px); +``` + +The app currently passes `width="105px"`, so closed mode is small. Open mode uses `--fg-picker-open-width`, currently defaulting to `240px`, so the selector can expand without making the mini gallery thumbnails larger. + +## Selector Button Styling + +`GalleryItemSelector` uses `gallery-item-selector` as the wrapper class and `gls__*` for internals. + +The button was simplified after the thumbnail-background version proved hard to read: + +- thumbnail is a small swatch on the side +- text is just `Foreground`, `Background`, or `Select Foreground/Background` +- no selected place title inside the button +- selected state uses a subtle `1px` grey border +- active state uses a darker grey outline +- the button is larger in open select mode +- the thumbnail uses `background-size: 200%` so the preview appears zoomed without changing the swatch size + +The opacity sliders are only shown when that slot has a selected place. + +## Current Parent Wiring + +The visible picker is: + +```vue + +``` + +The hidden gallery is still present to load and manage layers: + +```vue + +``` + +## Design Intent + +The current result keeps the original gallery mostly intact and uses it as the layer system. The new picker is just a small foreground/background UI that borrows Gallery's visual language without inheriting all of Gallery's state logic. diff --git a/src/BubblingGalaxies.vue b/src/BubblingGalaxies.vue index a9ff2fa..573d116 100644 --- a/src/BubblingGalaxies.vue +++ b/src/BubblingGalaxies.vue @@ -155,19 +155,34 @@ > About - - - + + --> - - - + + {{ simulationTime.toFixed(1) }} million years - + + + + + + + + +
+ {{ label }} opacity + +
+ + + + + + diff --git a/src/components/ForegroundBackgroundPicker.vue b/src/components/ForegroundBackgroundPicker.vue new file mode 100644 index 0000000..f2903bc --- /dev/null +++ b/src/components/ForegroundBackgroundPicker.vue @@ -0,0 +1,518 @@ + + + + + diff --git a/src/components/Gallery.vue b/src/components/Gallery.vue index 14e5f26..2a27624 100644 --- a/src/components/Gallery.vue +++ b/src/components/Gallery.vue @@ -130,6 +130,8 @@ export interface GalleryProps { defaultStarting?: string | null; /** prevent the gallery from being opened by user interaction */ disabled?: boolean; + /** order layers by selection order */ + useLayerSelectionOrder?: boolean; } @@ -152,6 +154,7 @@ const props = withDefaults(defineProps(), { collapseOnSelect: false, defaultStarting: null, disabled: false, + useLayerSelectionOrder: false, }); const defaultThumbnailUrl = "https://cdn.worldwidetelescope.org/wwtweb/thumbnail.aspx?name=test"; @@ -394,11 +397,38 @@ function setSelectedImagesetVisibility(places: Place[], selectedPlaces: Place[]) } } +function syncSelectedLayerOrder() { + if (!props.useLayerSelectionOrder) return; + if (props.persist !== null) { + const persistedPlace = places.value.find(p => isPersistantLayer(p)); + if (persistedPlace) { + const layer = getImagesetLayerForPlace(persistedPlace); + if (layer) { + store.setImageSetLayerOrder({ + id: layer.id.toString(), + order: 0, + }); + } + } + } + + const topOrder = Object.keys(store.imagesetLayers).length; + selectedPlaces.value.forEach((place, index) => { + const layer = getImagesetLayerForPlace(place); + if (!layer) return; + store.setImageSetLayerOrder({ + id: layer.id.toString(), + order: topOrder + index, + }); + }); +} + function syncSelectedLayerVisibility() { if (props.hideGalleryLayers) return; nextTick(async () => { await setSelectedImagesetVisibility(places.value, selectedPlaces.value); selectedPlaces.value.forEach(applySelectedPlaceOpacity); + syncSelectedLayerOrder(); }); } diff --git a/src/components/GalleryItemSelector.vue b/src/components/GalleryItemSelector.vue new file mode 100644 index 0000000..16f66ba --- /dev/null +++ b/src/components/GalleryItemSelector.vue @@ -0,0 +1,205 @@ + + + + + From a2fbde0782de000c1547565ec3fdccf4ce2ed6b8 Mon Sep 17 00:00:00 2001 From: John Arban Lewis Date: Tue, 5 May 2026 21:22:56 -0400 Subject: [PATCH 2/4] update label-stack styles and make select input clearable --- src/BubblingGalaxies.vue | 6 +++--- src/components/AltGalleryItemSelector.vue | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/BubblingGalaxies.vue b/src/BubblingGalaxies.vue index 573d116..48b8ebb 100644 --- a/src/BubblingGalaxies.vue +++ b/src/BubblingGalaxies.vue @@ -1120,9 +1120,9 @@ and remember, position:absolute is still a positioned parent, so children can be } .label-stack { - display: flex; - flex-direction: column; - gap: 4px; + align-self: flex-start; + margin-bottom: -999px; + overflow: visible; pointer-events: auto; max-width: calc(100% - 7em); } diff --git a/src/components/AltGalleryItemSelector.vue b/src/components/AltGalleryItemSelector.vue index 96ff6b8..c44e960 100644 --- a/src/components/AltGalleryItemSelector.vue +++ b/src/components/AltGalleryItemSelector.vue @@ -4,6 +4,7 @@ v-model="selectedKey" :items="selectItems" :placeholder="`Select ${label}`" + :clearable="true" :disabled="disabled" density="compact" variant="outlined" From 752d6a6c0c10dec7bdfc8235467ab754f106fd1f Mon Sep 17 00:00:00 2001 From: John Arban Lewis Date: Tue, 5 May 2026 21:32:42 -0400 Subject: [PATCH 3/4] get back split screen behavior --- src/BubblingGalaxies.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BubblingGalaxies.vue b/src/BubblingGalaxies.vue index 48b8ebb..550cf78 100644 --- a/src/BubblingGalaxies.vue +++ b/src/BubblingGalaxies.vue @@ -904,6 +904,7 @@ watch(showImageCard, (showing) => { showSimulation.value = false; showInfoSheet.value = false; galleryOpen.value = false; + backgroundGalleryItem.value = null; goToGalleryItem("Infrared Stars & Dust (JWST)"); } }); From 75cc3c45187ef94563f913f71f849a74314c7bf8 Mon Sep 17 00:00:00 2001 From: John Arban Lewis Date: Tue, 5 May 2026 21:34:01 -0400 Subject: [PATCH 4/4] get rid of old ai docs --- docs/foreground-background-gallery.md | 175 -------------------------- 1 file changed, 175 deletions(-) delete mode 100644 docs/foreground-background-gallery.md diff --git a/docs/foreground-background-gallery.md b/docs/foreground-background-gallery.md deleted file mode 100644 index 52fe1a5..0000000 --- a/docs/foreground-background-gallery.md +++ /dev/null @@ -1,175 +0,0 @@ -# Foreground / Background Gallery Notes - -This documents the current foreground/background gallery work and the style changes made while getting here. - -## Component Shape - -The gallery is still the component that loads WTML places, creates WWT imageset layers, syncs selected places to layer visibility, and optionally orders selected layers. The foreground/background picker does not load WTML and does not talk directly to `selectedPlaces`. - -The current pieces are: - -- `Gallery.vue`: hidden loader/controller for WTML places, imageset layers, selected layer visibility, opacity emitted from gallery items, and optional layer order. -- `ForegroundBackgroundPicker.vue`: visible foreground/background picker UI. It receives the list of places from the parent, owns the closed/open picker states, and sets only `foreground` or `background`. -- `GalleryItemSelector.vue`: larger open-mode control for one selected slot. It opens the picker for that slot, shows a small thumbnail swatch, and handles that slot's opacity slider. -- `GalleryItem.vue`: still the reusable thumbnail/label/opacity item. It emits `update:opacity` and does not set WWT opacity itself. -- `BubblingGalaxies.vue`: coordinates foreground/background selection with the hidden `Gallery` by converting `[background, foreground]` into `selectedGalleryItems`. - -## Data Flow - -`Gallery` loads `galleryPlaces` from `ngc628_datasets.wtml`. - -`ForegroundBackgroundPicker` gets those places through `v-model:places`, then lets the user choose: - -- `foregroundGalleryItem` -- `backgroundGalleryItem` - -The parent watches those refs and updates the gallery selection: - -```ts -selectedGalleryItems.value = [background, foreground].filter((place): place is Place => place !== null); -``` - -That order is intentional: background first, foreground second. The hidden gallery receives this array through `v-model:selected-places`. - -The default foreground is `"Infrared Stars & Dust (JWST)"`. The background starts as `null`. - -## Layer Ordering - -`Gallery.vue` now has a `useLayerSelectionOrder` prop. - -When enabled, the selected imageset layers are ordered by `selectedPlaces` order. The persistent layer is kept at order `0`, then selected items are pushed to the top of the current imageset layer stack in array order. - -For this foreground/background flow, the parent passes: - -```vue -use-layer-selection-order -``` - -Since the parent writes `selectedGalleryItems` as `[background, foreground]`, this pushes the background up first, then the foreground above it. - -## Opacity - -`GalleryItem.vue` emits opacity changes with `update:opacity`. - -`Gallery.vue` listens for those events in the normal gallery path and applies opacity to its known layer map. - -`ForegroundBackgroundPicker.vue` owns the foreground/background opacity values so the closed preview sliders and open selector sliders stay in sync. - -`ForegroundBackgroundPicker.vue` and `GalleryItemSelector.vue` use the WWT engine store for the foreground/background controls. They find the matching imageset layer by comparing the selected place's imageset URL or name against active WWT imageset layers. Our imageset names and URLs are assumed unique, so this is intentionally simple. - -Opacity is stored by imageset URL/name and applied with: - -```ts -imagesetLayer.value?.set_opacity(opacity); -``` - -The closed view uses `GalleryItem` with `show-opacity`, while the open view passes the same opacity value into each `GalleryItemSelector`. - -## Picker Behavior - -`ForegroundBackgroundPicker` has two visual modes: - -- closed mode: compact `Choose Views` display using `GalleryItem` thumbnails for only the currently selected places -- open mode: expanded selector surface with the two `GalleryItemSelector` controls along the bottom; the mini gallery opens above only after one selector is clicked - -The picker has a simple active-slot model: - -- clicking the foreground selector makes the mini gallery choose the foreground -- clicking the background selector makes the mini gallery choose the background -- selecting a `GalleryItem` writes to whichever slot is active - -The picker does not pass Gallery's `selected` prop through as shared selection state. Instead, it highlights the active slot's current place itself. - -The picker also hides the persistent layer when requested. It computes `shownPlaces` from `places`, `persist`, and `hidePersisted`, then renders only those places. - -## Labels And Info - -The selected place name is no longer shown inside the selector buttons. - -The visible labels are stacked in the parent using `DetailSummary`: - -- foreground label first -- background label second, only when a background is selected - -Opening either label sets `infoGalleryItem` and shows the info sheet for that specific place. Simulation mode still uses the simulation label. - -## Style Changes - -The foreground/background picker CSS was intentionally shaped to look like it came from the existing Gallery CSS: - -- same `--gallery-width`, `--gallery-max-height`, `--gallery-item-height`, `--column-count`, and `--selected-color` variables -- same nested LESS style as `Gallery.vue` -- same blurred glass treatment: transparent base, `rgba(0,0,0,0.5)` when visible, `box-shadow`, and `backdrop-filter: blur(6px)` -- same compact border radius language: mostly `3px` and `5px` -- copied gallery item sizing for the mini picker: `width: var(--gallery-width)`, `--image-width: 96px`, image height `45px` -- retained commented-out gallery CSS where it helped preserve the "copied from Gallery" feel - -The closed foreground/background display is narrowed to the gallery width: - -```css -width: calc(var(--gallery-width) + 10px); -``` - -The app currently passes `width="105px"`, so closed mode is small. Open mode uses `--fg-picker-open-width`, currently defaulting to `240px`, so the selector can expand without making the mini gallery thumbnails larger. - -## Selector Button Styling - -`GalleryItemSelector` uses `gallery-item-selector` as the wrapper class and `gls__*` for internals. - -The button was simplified after the thumbnail-background version proved hard to read: - -- thumbnail is a small swatch on the side -- text is just `Foreground`, `Background`, or `Select Foreground/Background` -- no selected place title inside the button -- selected state uses a subtle `1px` grey border -- active state uses a darker grey outline -- the button is larger in open select mode -- the thumbnail uses `background-size: 200%` so the preview appears zoomed without changing the swatch size - -The opacity sliders are only shown when that slot has a selected place. - -## Current Parent Wiring - -The visible picker is: - -```vue - -``` - -The hidden gallery is still present to load and manage layers: - -```vue - -``` - -## Design Intent - -The current result keeps the original gallery mostly intact and uses it as the layer system. The new picker is just a small foreground/background UI that borrows Gallery's visual language without inheriting all of Gallery's state logic.