Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a29e746
feat(modelStyles): add logic to color all model_component by type
MaxNumerique Apr 13, 2026
d3a45ee
Apply prepare changes
MaxNumerique Apr 13, 2026
08de78d
oxlint
MaxNumerique Apr 13, 2026
ecc9f47
Merge branch 'feat/model-component-type-color' of https://github.com/…
MaxNumerique Apr 13, 2026
f3791f5
fix reactive style for model_components
MaxNumerique Apr 13, 2026
03a9b44
rm this ?
MaxNumerique Apr 13, 2026
6c8bdc0
harmonize transaction via Dexie
MaxNumerique Apr 14, 2026
bb01773
feat(modelStyleOptions): add visibility toggle for model_component_ty…
MaxNumerique Apr 14, 2026
2d0749b
feat(modelStyleoptions): add a random options for colors taht can be …
MaxNumerique Apr 14, 2026
9ba6b63
update
MaxNumerique Apr 15, 2026
f931877
refacto and add the loading bar with start/stop request
MaxNumerique Apr 15, 2026
3549c4b
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
MaxNumerique Apr 15, 2026
1da7229
adapt from next
MaxNumerique Apr 15, 2026
401dc84
oxlint
MaxNumerique Apr 15, 2026
81fa66b
Apply prepare changes
MaxNumerique Apr 15, 2026
0efdfea
refacto
MaxNumerique Apr 16, 2026
2bc5bdd
Merge branch 'feat/model-component-type-color' of https://github.com/…
MaxNumerique Apr 16, 2026
6fd072a
refacto
MaxNumerique Apr 16, 2026
4855444
Apply prepare changes
MaxNumerique Apr 16, 2026
7bb38a8
trigger
MaxNumerique Apr 16, 2026
7fee05b
Merge branch 'feat/model-component-type-color' of https://github.com/…
MaxNumerique Apr 16, 2026
a25f761
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
MaxNumerique Apr 16, 2026
5261593
test
MaxNumerique Apr 16, 2026
edd52fd
Apply prepare changes
MaxNumerique Apr 16, 2026
e555796
common
MaxNumerique Apr 16, 2026
bd0de34
Merge branch 'feat/model-component-type-color' of https://github.com/…
MaxNumerique Apr 16, 2026
46e8bb4
color
MaxNumerique Apr 16, 2026
518fb4e
mutateStyle
MaxNumerique Apr 16, 2026
e6eda86
visibility fixed
MaxNumerique Apr 16, 2026
22e5515
refacto
MaxNumerique Apr 17, 2026
8730d04
revert
MaxNumerique Apr 17, 2026
169bf73
modelComponentTypeStyle
MaxNumerique Apr 17, 2026
e356fed
Apply prepare changes
MaxNumerique Apr 17, 2026
fa392f3
fix
MaxNumerique Apr 17, 2026
519191b
Merge branch 'feat/model-component-type-color' of https://github.com/…
MaxNumerique Apr 17, 2026
1f7f3ae
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
MaxNumerique Apr 17, 2026
d2de9a1
refacto and fixes
MaxNumerique Apr 17, 2026
3b0de45
Apply prepare changes
MaxNumerique Apr 17, 2026
2a53b56
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
MaxNumerique Apr 17, 2026
8b6a476
Merge branch 'feat/model-component-type-color' of https://github.com/…
MaxNumerique Apr 17, 2026
be059f4
no longer return consstant color
MaxNumerique Apr 17, 2026
fd6d4d6
renames and refacto
MaxNumerique Apr 17, 2026
417da1e
Apply prepare changes
MaxNumerique Apr 17, 2026
3c2334e
revert
MaxNumerique Apr 17, 2026
77287d2
Merge branch 'feat/model-component-type-color' of https://github.com/…
MaxNumerique Apr 17, 2026
d86e80f
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
MaxNumerique Apr 17, 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
3 changes: 2 additions & 1 deletion app/components/Viewer/ContextMenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ function toggleOptions() {
variant="panel"
padding="pa-0"
class="elevation-24"
style="overflow: hidden; display: flex; flex-direction: column"
>
<v-card-text class="pa-5">
<v-card-text class="pa-5" style="overflow-y: auto; flex: 1; min-height: 0">
<slot name="options" />
</v-card-text>
</GlassCard>
Expand Down
181 changes: 129 additions & 52 deletions app/components/Viewer/Generic/Model/ModelStyleCard.vue
Original file line number Diff line number Diff line change
@@ -1,96 +1,173 @@
<script setup>
import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
import ViewerOptionsColorPicker from "@ogw_front/components/Viewer/Options/ColorPicker.vue";
import VisibilitySwitch from "@ogw_front/components/Viewer/Options/VisibilitySwitch.vue";
import { useDataStore } from "@ogw_front/stores/data";
import { useDataStyleStore } from "@ogw_front/stores/data_style";
import { useHybridViewerStore } from "@ogw_front/stores/hybrid_viewer";

const dataStyleStore = useDataStyleStore();
const hybridViewerStore = useHybridViewerStore();
const dataStore = useDataStore();

const { itemProps } = defineProps({
itemProps: { type: Object, required: true },
});

const modelId = computed(() => itemProps.meta_data.modelId || itemProps.id);
const componentId = computed(() => itemProps.meta_data.pickedComponentId);
const selection = dataStyleStore.visibleMeshComponents(modelId);
const componentType = ref(undefined);

watchEffect(async () => {
if (itemProps.meta_data.viewer_type === "model_component_type") {
componentType.value = itemProps.meta_data.modelComponentType;
} else if (componentId.value && modelId.value) {
componentType.value = await dataStore.meshComponentType(modelId.value, componentId.value);
} else {
componentType.value = undefined;
}
});

const modelVisibility = computed({
get: () => dataStyleStore.modelVisibility(modelId.value),
set: (value) => {
dataStyleStore.setModelVisibility(modelId.value, value);
set: async (newValue) => {
await dataStyleStore.setModelVisibility(modelId.value, newValue);
hybridViewerStore.remoteRender();
},
});

const modelComponentTypeVisibility = computed({
get: () => selection.value.includes(componentType.value),
set: async (newValue) => {
await dataStyleStore.setModelComponentTypeVisibility(
modelId.value,
componentType.value,
newValue,
);
hybridViewerStore.remoteRender();
},
});

const componentVisibility = computed({
get: () => selection.value.includes(componentId.value),
set: async (newValue) => {
await dataStyleStore.setModelComponentsVisibility(modelId.value, [componentId.value], newValue);
hybridViewerStore.remoteRender();
},
});

const componentColor = computed({
get: () =>
componentId.value
? dataStyleStore.getModelComponentColor(modelId.value, componentId.value)
? dataStyleStore.getModelComponentEffectiveColor(
modelId.value,
componentId.value,
componentType.value,
)
: undefined,
set: async (newValue) => {
set: async (color) => {
if (componentId.value) {
await dataStyleStore.setModelComponentsColor(modelId.value, [componentId.value], newValue);
await dataStyleStore.setModelComponentsColor(modelId.value, [componentId.value], color);
hybridViewerStore.remoteRender();
}
},
});

const modelComponentTypeColor = computed({
get: () =>
componentType.value
? dataStyleStore.getModelComponentTypeColor(modelId.value, componentType.value)
: undefined,
set: async (color) => {
if (componentType.value) {
await dataStyleStore.setModelComponentTypeColor(modelId.value, componentType.value, color);
hybridViewerStore.remoteRender();
}
},
});

const modelComponentTypeColorMode = computed({
get: () => dataStyleStore.getModelComponentTypeColorMode(modelId.value, componentType.value),
set: async (colorMode) => {
if (componentType.value) {
await dataStyleStore.setModelComponentTypeColorMode(
modelId.value,
componentType.value,
colorMode,
);
hybridViewerStore.remoteRender();
}
},
});

const componentColorMode = computed({
get: () => dataStyleStore.getModelComponentColorMode(modelId.value, componentId.value),
set: async (colorMode) => {
if (componentId.value) {
await dataStyleStore.setModelComponentColorMode(modelId.value, componentId.value, colorMode);
hybridViewerStore.remoteRender();
}
},
});

const colorModes = [
{ title: "Constant", value: "constant" },
{ title: "Random", value: "random" },
];

const modelComponentTypeLabel = computed(() =>
componentType.value ? `${componentType.value}s Options` : "",
);
</script>

<template>
<div class="model-style-card">
<div class="options-section">
<div class="section-badge">Model Options</div>
<v-container class="pa-2">
<VisibilitySwitch v-model="modelVisibility" />
</v-container>
</div>

<div v-if="componentId" class="options-section mt-6">
<div class="section-badge">Component Options</div>
<v-container class="pa-2">
<OptionsSection title="Model Options">
<VisibilitySwitch v-model="modelVisibility" />
</OptionsSection>

<OptionsSection v-if="componentType" :title="modelComponentTypeLabel" class="mt-6">
<VisibilitySwitch v-model="modelComponentTypeVisibility" />
<div class="text-caption mb-1 mt-2">Color Mode</div>
<v-select
v-model="modelComponentTypeColorMode"
:items="colorModes"
density="compact"
hide-details
class="mb-3"
variant="outlined"
/>

<template v-if="modelComponentTypeColorMode === 'constant'">
<div class="text-caption mb-1">Color</div>
<ViewerOptionsColorPicker v-model="modelComponentTypeColor" />
</template>
</OptionsSection>

<OptionsSection v-if="componentId" title="Component Options" class="mt-6">
<VisibilitySwitch v-model="componentVisibility" />
<div class="text-caption mb-1 mt-2">Color Mode</div>
<v-select
v-model="componentColorMode"
:items="colorModes"
density="compact"
hide-details
class="mb-3"
variant="outlined"
/>

<template v-if="componentColorMode === 'constant'">
<div class="text-caption mb-1">Color</div>
<ViewerOptionsColorPicker v-model="componentColor" />
</v-container>
</div>
</template>
</OptionsSection>
</div>
</template>

<style scoped>
.model-style-card {
min-width: 280px;
}

.options-section {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 12px 8px 8px 8px;
}

.section-badge {
position: absolute;
top: -12px;
left: 16px;
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
padding: 2px 12px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.v-theme--light .options-section {
border-color: rgba(0, 0, 0, 0.12);
}

.v-theme--light .section-badge {
background-color: white;
color: #444;
border-color: rgba(0, 0, 0, 0.12);
padding-top: 20px;
overflow-x: hidden;
}
</style>
20 changes: 12 additions & 8 deletions app/components/Viewer/ObjectTree/Views/ModelComponents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ async function onSelectionChange(current) {
}
hybridViewerStore.remoteRender();
}
function showContextMenu(event, item) {
const actualItem = item.raw || item;
emit("show-menu", {
event,
itemId: actualItem.category ? actualItem.id : viewId,
context_type: actualItem.category ? "model_component" : "model_component_type",
modelId: viewId,
modelComponentType: actualItem.category ? undefined : actualItem.id,
});
}
</script>

<template>
Expand Down Expand Up @@ -83,14 +94,7 @@ async function onSelectionChange(current) {
<ObjectTreeItemLabel
:item="item"
show-tooltip
@contextmenu="
emit('show-menu', {
event: $event,
itemId: item.id,
context_type: 'model_component',
modelId: viewId,
})
"
@contextmenu="showContextMenu($event, item)"
/>
</template>
</v-treeview>
Expand Down
74 changes: 74 additions & 0 deletions app/components/Viewer/Options/OptionsSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script setup>
const { title } = defineProps({
title: { type: String, required: true },
});

const isCollapsed = ref(false);
</script>

<template>
<div class="options-section">
<div class="section-badge" @click="isCollapsed = !isCollapsed">
{{ title }}
<v-icon class="collapse-icon" :class="{ rotated: isCollapsed }" size="12">
mdi-chevron-down
</v-icon>
</div>
<v-container v-show="!isCollapsed" class="pa-2">
<slot />
</v-container>
</div>
</template>

<style scoped>
.options-section {
position: relative;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 12px 8px 8px 8px;
}

.section-badge {
position: absolute;
top: -12px;
left: 16px;
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
padding: 2px 12px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
gap: 4px;
}

.section-badge:hover {
background-color: rgba(255, 255, 255, 0.2);
}

.collapse-icon {
transition: transform 0.2s ease;
}

.collapse-icon.rotated {
transform: rotate(-90deg);
}

.v-theme--light .options-section {
border-color: rgba(0, 0, 0, 0.12);
}

.v-theme--light .section-badge {
background-color: white;
color: #444;
border-color: rgba(0, 0, 0, 0.12);
}
</style>
4 changes: 2 additions & 2 deletions app/stores/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,10 @@ export const useDataStore = defineStore("data", () => {
await database.model_components_relation.where("id").equals(modelId).delete();
}

async function getMeshComponentGeodeIds(modelId, component_type) {
async function getMeshComponentGeodeIds(modelId, type) {
const components = await database.model_components
.where("[id+type]")
.equals([modelId, component_type])
.equals([modelId, type])
.toArray();
return components.map((component) => component.geode_id);
}
Expand Down
13 changes: 11 additions & 2 deletions app/stores/data_style.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
return {
styles: dataStyleState.styles,
componentStyles: dataStyleState.componentStyles,
modelComponentTypeStyles: dataStyleState.modelComponentTypeStyles,
};
}

async function importStores(snapshot) {
const stylesSnapshot = snapshot.styles;
const componentStylesSnapshot = snapshot.componentStyles;
const modelComponentTypeStylesSnapshot = snapshot.modelComponentTypeStyles;

await dataStyleState.clear();

Expand All @@ -60,12 +62,19 @@ export const useDataStyleStore = defineStore("dataStyle", () => {
const component_style_promises = Object.values(componentStylesSnapshot).map((style) =>
database.model_component_datastyle.put(structuredClone(style)),
);
const model_component_type_style_promises = Object.values(modelComponentTypeStylesSnapshot).map(
(style) => database.model_component_type_datastyle.put(structuredClone(style)),
);

await Promise.all([...style_promises, ...component_style_promises]);
await Promise.all([
...style_promises,
...component_style_promises,
...model_component_type_style_promises,
]);
}

function applyAllStylesFromState() {
const ids = Object.keys(dataStyleState.styles);
const ids = Object.keys(dataStyleState.styles.value);
const promises = ids.map(async (id) => {
const meta = await dataStore.item(id);
const viewerType = meta.viewer_type;
Expand Down
Loading
Loading