Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4da05b9
feat(SaveCamera): Save State of the camera
SpliiT Apr 29, 2026
86a5090
change ui
SpliiT Apr 29, 2026
f94794e
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
SpliiT Apr 30, 2026
0f0bbee
Apply prepare changes
SpliiT Apr 30, 2026
a577140
passage par le front
SpliiT Apr 30, 2026
51b146c
Merge branch 'feat/SaveStatePosition' of https://github.com/Geode-sol…
SpliiT Apr 30, 2026
76865b8
Apply prepare changes
SpliiT Apr 30, 2026
de890f9
oxlint
SpliiT Apr 30, 2026
f532502
oxlint
SpliiT Apr 30, 2026
08db1a1
Apply prepare changes
SpliiT Apr 30, 2026
d360a1b
oxlint
SpliiT Apr 30, 2026
76336f9
Apply prepare changes
SpliiT Apr 30, 2026
523c1fb
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
SpliiT May 11, 2026
57e3e28
rename indentifiers
SpliiT May 11, 2026
021f3de
Apply prepare changes
SpliiT May 11, 2026
2330549
oxlint
SpliiT May 11, 2026
05c4dce
Merge branch 'feat/SaveStatePosition' of https://github.com/Geode-sol…
SpliiT May 11, 2026
a66fdb9
Apply prepare changes
SpliiT May 11, 2026
040234a
oxlint
SpliiT May 11, 2026
4822a35
Merge branches 'feat/SaveStatePosition' and 'feat/SaveStatePosition' …
SpliiT May 11, 2026
6f750d0
Apply prepare changes
SpliiT May 11, 2026
78b4a6c
new store
SpliiT May 11, 2026
75099a0
Merge branch 'feat/SaveStatePosition' of https://github.com/Geode-sol…
SpliiT May 11, 2026
5a0c870
rm u regex
SpliiT May 11, 2026
b75cbab
oxlint
SpliiT May 11, 2026
b815fd8
rm object_id
SpliiT May 11, 2026
c04a308
oxc
SpliiT May 11, 2026
334b29b
icon update
SpliiT May 11, 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
64 changes: 64 additions & 0 deletions app/assets/viewer_svgs/camera-bookmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion app/components/ActionButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ const emit = defineEmits(["click"]);
icon
@click="emit('click', $event)"
>
<v-icon :size="iconSize">{{ icon }}</v-icon>
<v-icon v-if="typeof icon === 'string' && icon.startsWith('mdi-')" :size="iconSize">{{
icon
}}</v-icon>
<v-img
v-else
:src="icon"
:height="iconSize"
:width="iconSize"
class="d-flex justify-center align-center"
/>
</v-btn>
</template>
39 changes: 39 additions & 0 deletions app/components/CameraManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup>
import GlassCard from "@ogw_front/components/GlassCard";
import List from "@ogw_front/components/CameraManager/List";
import Saver from "@ogw_front/components/CameraManager/Saver";

const emit = defineEmits(["close"]);

const { show_dialog, width } = defineProps({
show_dialog: { type: Boolean, required: true },
width: { type: Number, required: false, default: 450 },
});
</script>

<template>
<GlassCard
v-if="show_dialog"
@click.stop
title="Camera Positions"
:width="width"
:ripple="false"
variant="panel"
padding="pa-0"
class="position-absolute elevation-24"
style="z-index: 2; top: 90px; right: 55px"
>
<v-card-text class="pa-0">
<Saver />
<v-divider></v-divider>
<List />
</v-card-text>

<v-divider></v-divider>

<v-card-actions class="pa-4">
<v-spacer></v-spacer>
<v-btn variant="text" color="grey-darken-1" @click="emit('close')">Close</v-btn>
</v-card-actions>
</GlassCard>
</template>
114 changes: 114 additions & 0 deletions app/components/CameraManager/List.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script setup>
import { useCameraManagerStore } from "@ogw_front/stores/camera_manager";
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";

const cameraManagerStore = useCameraManagerStore();
const hybridViewerStore = useHybridViewerStore();

const savedPositions = cameraManagerStore.refAllCameraPositions();

const editingId = ref(undefined);
const editingName = ref("");

async function restorePosition(positionId) {
const position = await cameraManagerStore.getCameraPosition(positionId);
if (position) {
if (hybridViewerStore.genericRenderWindow) {
hybridViewerStore.setCamera(position.camera_options);
} else {
await cameraManagerStore.restoreCameraPosition(positionId);
}
}
}

async function deletePosition(positionId) {
await cameraManagerStore.deleteCameraPosition(positionId);
}

function startEditing(position) {
editingId.value = position.id;
editingName.value = position.name;
}

async function saveRename() {
if (editingName.value) {
await cameraManagerStore.renameCameraPosition(editingId.value, editingName.value);
}
editingId.value = undefined;
}
</script>

<template>
<v-list v-if="savedPositions.length > 0" class="bg-transparent pa-2">
<v-list-item
v-for="position in savedPositions"
:key="position.id"
class="rounded-lg mb-1"
:active="editingId === position.id"
active-color="primary"
>
<template #prepend>
<v-btn
icon
variant="tonal"
color="success"
size="small"
class="mr-2"
@click="restorePosition(position.id)"
>
<v-icon size="20">mdi-play</v-icon>
<v-tooltip activator="parent" location="top">Restore</v-tooltip>
</v-btn>
</template>

<v-list-item-title class="font-weight-bold">
<v-text-field
v-if="editingId === position.id"
v-model="editingName"
density="compact"
variant="underlined"
hide-details
autofocus
@keyup.enter="saveRename"
@blur="saveRename"
></v-text-field>
<span v-else>{{ position.name }}</span>
</v-list-item-title>

<template #append>
<div class="d-flex g-1">
<v-btn
icon
variant="text"
size="small"
color="grey-darken-1"
@click="startEditing(position)"
>
<v-icon size="18">mdi-pencil</v-icon>
<v-tooltip activator="parent" location="top">Rename</v-tooltip>
</v-btn>
<v-btn
icon
variant="text"
size="small"
color="error"
@click="deletePosition(position.id)"
>
<v-icon size="18">mdi-delete</v-icon>
<v-tooltip activator="parent" location="top">Delete</v-tooltip>
</v-btn>
</div>
</template>
</v-list-item>
</v-list>
<div v-else class="text-center text-grey-lighten-1 py-8 italic">
<v-icon size="48" class="mb-2 d-block mx-auto opacity-20">mdi-camera-off</v-icon>
No saved positions yet.
</div>
</template>

<style scoped>
:deep(.v-list-item__prepend) {
margin-inline-end: 12px !important;
}
</style>
50 changes: 50 additions & 0 deletions app/components/CameraManager/Saver.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup>
import { useCameraManagerStore } from "@ogw_front/stores/camera_manager";
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";

const cameraManagerStore = useCameraManagerStore();
const hybridViewerStore = useHybridViewerStore();

const newPositionName = ref("");

async function saveCurrentPosition() {
if (!newPositionName.value) {
return;
}
await cameraManagerStore.saveCameraPosition(
newPositionName.value,
toRaw(hybridViewerStore.camera_options),
);
newPositionName.value = "";
}
</script>

<template>
<v-container class="pa-5 pb-2 bg-surface-variant-lighten-5">
<v-row dense>
<v-col cols="12">
<v-text-field
v-model="newPositionName"
label="Position Name"
placeholder="e.g. Front View"
density="compact"
variant="outlined"
hide-details
class="mb-3"
></v-text-field>
</v-col>
<v-col cols="12" class="d-flex align-center">
<v-btn
color="primary"
variant="elevated"
block
:disabled="!newPositionName"
@click="saveCurrentPosition"
height="40"
>
Save
</v-btn>
</v-col>
</v-row>
</v-container>
</template>
2 changes: 1 addition & 1 deletion app/components/Recaptcha.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const emailRules = [
return "E-mail is required.";
},
(value) => {
if (/.+@.+\..+/.test(value)) {
if (/.+@.+\..+/u.test(value)) {
return true;
}
return "E-mail must be valid.";
Expand Down
17 changes: 14 additions & 3 deletions app/components/ViewToolbar.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<script setup>
import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";

import ActionButton from "@ogw_front/components/ActionButton.vue";
import CameraBookmarkIcon from "@ogw_front/assets/viewer_svgs/camera-bookmark.svg";
import CameraManager from "@ogw_front/components/CameraManager";
import CameraOrientation from "@ogw_front/components/CameraOrientation.vue";
import Screenshot from "@ogw_front/components/Screenshot";
import ZScaling from "@ogw_front/components/ZScaling";

import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";
import { useViewerStore } from "@ogw_front/stores/viewer";

const hybridViewerStore = useHybridViewerStore();
const viewerStore = useViewerStore();
const take_screenshot = ref(false);
const show_camera_manager = ref(false);
const showCameraOrientation = ref(false);
const showZScaling = ref(false);
const grid_scale = ref(false);
Expand Down Expand Up @@ -51,6 +52,14 @@ const camera_options = [
showCameraOrientation.value = !showCameraOrientation.value;
},
},
{
tooltip: "Manage camera positions",
icon: CameraBookmarkIcon,
iconSize: 34,
action: () => {
show_camera_manager.value = !show_camera_manager.value;
},
},
{
tooltip: "Take a screenshot",
icon: "mdi-camera",
Expand Down Expand Up @@ -91,6 +100,7 @@ const camera_options = [
<ActionButton
:icon="camera_option.icon"
:tooltip="camera_option.tooltip"
:icon-size="camera_option.iconSize"
tooltip-location="left"
@click.stop="camera_option.action"
/>
Expand All @@ -103,6 +113,7 @@ const camera_options = [
@select="hybridViewerStore.setCameraOrientation"
/>
<Screenshot v-model="take_screenshot" />
<CameraManager :show_dialog="show_camera_manager" @close="show_camera_manager = false" />
<ZScaling
v-model:show="showZScaling"
v-model="zScale"
Expand Down
Loading
Loading