Compare commits

...

3 Commits

Author SHA1 Message Date
Dogukan Karatas 34115d9a5d feat (visual): align visual with FE2 (#222)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
* view mode menu updated

* clean up
2026-01-19 23:19:51 +03:00
Mucahit Bilal GOKER 74ac3e3990 add object and application ids (#221) 2026-01-15 20:40:46 +03:00
Mucahit Bilal GOKER f9b5e250d8 feat: Issues helper function (#194)
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled
* helper func

* register

* query: remove code duplicate

* discussions gone, issues in.

* add issue urls

* add optional replies input
2026-01-06 16:02:53 +03:00
11 changed files with 629 additions and 83 deletions
+6
View File
@@ -189,6 +189,12 @@ shared Speckle.Models.MaterialQuantities = Value.ReplaceType(
type function (inputTable as table, optional addPrefix as logical) as table
);
[DataSource.Kind = "Speckle"]
shared Speckle.Project.Issues = Value.ReplaceType(
Speckle.LoadFunction("Project.Issues.pqm"),
type function (url as Uri.Type, optional getReplies as logical) as table
);
[DataSource.Kind = "Speckle", Publish="GetByUrl.Publish"]
shared Speckle.GetByUrl = Value.ReplaceType(
Speckle.LoadFunction("GetByUrl.pqm"),
@@ -0,0 +1,173 @@
// Function for getting issues from Speckle projects, models, or versions
(url as text, optional getReplies as logical) as table =>
let
// Import required functions
Parser = Extension.LoadFunction("Parser.pqm"),
ApiFetch = Extension.LoadFunction("Api.Fetch.pqm"),
// Set default value for getReplies parameter
getRepliesValue = if getReplies = null then false else getReplies,
// Extension.LoadFunction logic for importing functions from other files
Extension.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName),
asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared)
catch (e) =>
error
[
Reason = "Extension.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
],
// Parse the URL to get necessary components with fallback for project-only URLs
parsedUrl = try Parser(url) otherwise
// Custom parsing for project-only URLs
let
urlParts = Uri.Parts(url),
baseUrl = Text.Combine({urlParts[Scheme], "://", urlParts[Host]}),
pathSegments = List.Select(Text.Split(urlParts[Path], "/"), each _ <> ""),
projectId = if List.Count(pathSegments) >= 2 and pathSegments{0} = "projects"
then pathSegments{1} else null
in
if projectId = null then
error [
Reason = "Invalid URL",
Message = "The URL must be a valid Speckle project URL in the format 'https://server/projects/PROJECT_ID' or include models/versions"
]
else
[
baseUrl = baseUrl,
projectId = projectId,
modelId = null,
versionId = null
],
server = parsedUrl[baseUrl],
projectId = parsedUrl[projectId],
modelId = parsedUrl[modelId],
versionId = parsedUrl[versionId],
// Define the GraphQL query (single query for all scopes)
issuesQuery = "query Project($projectId: String!, $input: ProjectIssuesInput" &
(if getRepliesValue then ", $repliesInput2: IssueRepliesInput" else "") & ") {
project(id: $projectId) {
issues(input: $input) {
items {
identifier
title
rawDescription
status
priority
assignee {
user {
name
}
}
dueDate
labels {
name
}
createdAt
updatedAt
resourceIdString
viewerState
id" &
(if getRepliesValue then "
replies(input: $repliesInput2) {
items {
issueId
id
rawDescription
createdAt
author {
user {
name
}
}
}
}" else "") & "
}
}
}
}",
// Build input variable dynamically based on URL scope
inputVariable =
if versionId <> null then
// Version URL: resourceIdString = "MODEL_ID@VERSION_ID"
[
limit = 10000,
resourceIdString = modelId & "@" & versionId
]
else if modelId <> null then
// Model URL: resourceIdString = MODEL_ID
[
limit = 10000,
resourceIdString = modelId
]
else
// Project URL: no resourceIdString
[
limit = 10000
],
// Build query variables
queryVariables = if getRepliesValue then
[
projectId = projectId,
input = inputVariable,
repliesInput2 = [limit = 10000]
]
else
[
projectId = projectId,
input = inputVariable
],
// Make the API request using ApiFetch
result = ApiFetch(server, issuesQuery, queryVariables),
// Extract issues from the response
issues = result[project][issues][items],
// Transform to table structure with specified columns
issuesTable = Table.FromRecords(
List.Transform(issues, (issue) =>
let
// Extract selectedObjectApplicationIds from viewerState (already a record object)
viewerState = try issue[viewerState] otherwise null,
selectedObjectIds = try viewerState[ui][filters][selectedObjectApplicationIds] otherwise null,
objectIds = try Record.FieldNames(selectedObjectIds) otherwise null,
applicationIds = try Record.FieldValues(selectedObjectIds) otherwise null,
baseRecord = [
ID = issue[identifier],
Title = issue[title],
Description = try issue[rawDescription] otherwise null,
Status = try issue[status] otherwise null,
Priority = try issue[priority] otherwise null,
Assignee = try issue[assignee][user][name] otherwise null,
#"Due Date" = try DateTime.From(issue[dueDate]) otherwise null,
Labels = try List.Transform(issue[labels], each _[name]) otherwise {},
#"Created at" = try DateTime.From(issue[createdAt]) otherwise null,
#"Updated at" = try DateTime.From(issue[updatedAt]) otherwise null,
URL = server & "/projects/" & projectId & "/models/" & issue[resourceIdString] & "#threadId=" & issue[id],
#"Object IDs" = objectIds,
#"Application IDs" = applicationIds
],
recordWithReplies = if getRepliesValue then
baseRecord & [Replies = try issue[replies][items] otherwise null]
else
baseRecord
in
recordWithReplies
)
)
in
issuesTable
+9
View File
@@ -92,6 +92,15 @@
},
"navbarHidden": {
"type": { "bool": true }
},
"edgesEnabled": {
"type": { "bool": true }
},
"edgesWeight": {
"type": { "numeric": true }
},
"edgesColor": {
"type": { "numeric": true }
}
}
},
@@ -33,13 +33,22 @@
</ViewerControlsButtonToggle>
</ViewerControlsButtonGroup>
<ViewerControlsButtonGroup>
<!-- View Modes -->
<ViewerViewModesMenu
:open="viewModesOpen"
@force-close-others="activeControl = 'none'"
@update:open="(value) => toggleActiveControl(value ? 'viewModes' : 'none')"
@view-mode-clicked="(value) => $emit('view-mode-clicked', value)"
/>
<!-- View Modes Toggle -->
<div class="relative">
<ViewerControlsButtonToggle
flat
tooltip="View modes"
:active="viewModesOpen"
@click="toggleActiveControl('viewModes')"
>
<ViewModesIcon class="h-5 w-5" />
</ViewerControlsButtonToggle>
<!-- View Modes Panel (shown when glasses icon is clicked) -->
<ViewerViewModesMenu
v-if="viewModesOpen"
@view-mode-clicked="(viewMode, options) => $emit('view-mode-clicked', viewMode, options)"
/>
</div>
<!-- Views -->
<ViewerViewsMenu
:open="viewsOpen"
@@ -65,7 +74,7 @@
<script setup lang="ts">
import { ArrowsPointingOutIcon } from '@heroicons/vue/24/solid'
import { SpeckleView } from '@speckle/viewer'
import { SpeckleView, ViewMode } from '@speckle/viewer'
import { computed, ref } from 'vue'
import { useVisualStore } from '@src/store/visualStore'
import ViewerControlsButtonGroup from './viewer/controls/ViewerControlsButtonGroup.vue'
@@ -76,19 +85,21 @@ import ViewerViewsMenu from './viewer/views/ViewerViewsMenu.vue'
import Perspective from '../components/global/icon/Perspective.vue'
import PerspectiveMore from '../components/global/icon/PerspectiveMore.vue'
import ViewModesIcon from '../components/global/icon/ViewModes.vue'
import Ghost from '../components/global/icon/Ghost.vue'
import ZoomToFit from '../components/global/icon/ZoomToFit.vue'
import type { ViewModeOptions } from '@src/plugins/viewer'
const visualStore = useVisualStore()
const emits = defineEmits([
'update:sectionBox',
'view-clicked',
'toggle-projection',
'clear-palette',
'view-mode-clicked'
])
const emits = defineEmits<{
(e: 'update:sectionBox', value: boolean): void
(e: 'view-clicked', view: SpeckleView): void
(e: 'toggle-projection'): void
(e: 'clear-palette'): void
(e: 'view-mode-clicked', viewMode: ViewMode, options: ViewModeOptions): void
}>()
withDefaults(defineProps<{ sectionBox: boolean; views: SpeckleView[] }>(), {
sectionBox: false
})
@@ -130,12 +141,7 @@ const toggleZoomOnFilter = () => {
visualStore.writeZoomOnFilterToFile()
}
const viewModesOpen = computed({
get: () => activeControl.value === 'viewModes',
set: (value) => {
activeControl.value = value ? 'viewModes' : 'none'
}
})
const viewModesOpen = computed(() => activeControl.value === 'viewModes')
const viewsOpen = computed({
get: () => activeControl.value === 'views',
@@ -81,7 +81,7 @@
:views="views"
class="fixed top-11 left-2 z-30"
@view-clicked="(view) => viewerHandler.setView(view)"
@view-mode-clicked="(viewMode) => viewerHandler.setViewMode(viewMode)"
@view-mode-clicked="(viewMode, options) => viewerHandler.setViewMode(viewMode, options)"
/>
</transition>
@@ -0,0 +1,104 @@
<template>
<div class="w-full flex flex-col gap-2">
<div class="flex items-center justify-between">
<label
:for="name"
class="block text-body-2xs text-foreground-2"
>
{{ label || name }}
</label>
<span class="text-body-2xs text-foreground-2">{{ displayValue }}</span>
</div>
<input
:id="name"
:name="name"
type="range"
:min="min"
:max="max"
:step="step"
:value="currentValue"
:disabled="disabled"
class="w-full h-1.5 outline-none slider"
:class="{
'disabled:opacity-50 disabled:cursor-not-allowed': disabled
}"
:aria-label="label"
:aria-valuemin="min"
:aria-valuemax="max"
:aria-valuenow="currentValue"
@input="handleInput"
/>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
const props = defineProps<{
min: number
max: number
step: number
name: string
label: string
disabled?: boolean
modelValue?: number
}>()
const emit = defineEmits(['update:modelValue'])
const currentValue = ref(props.modelValue ?? props.min)
// Watch for external changes to modelValue
watch(() => props.modelValue, (newVal) => {
if (newVal !== undefined && newVal !== currentValue.value) {
currentValue.value = newVal
}
})
const displayValue = computed(() => {
// Round to avoid floating point issues
return Math.round(currentValue.value * 10) / 10
})
const clampValue = (value: number): number => {
return Math.max(props.min, Math.min(props.max, value))
}
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement
const value = Number(target.value)
const clampedValue = clampValue(value)
currentValue.value = clampedValue
emit('update:modelValue', clampedValue)
}
</script>
<style scoped>
.slider {
-webkit-appearance: none;
appearance: none;
background: transparent;
}
.slider::-webkit-slider-runnable-track {
@apply h-1.5 rounded-full bg-outline-3;
}
.slider::-moz-range-track {
@apply h-1.5 rounded-full bg-outline-3;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
@apply h-2.5 w-2.5 rounded-full cursor-pointer bg-foreground-2;
margin-top: -2px;
}
.slider::-moz-range-thumb {
-webkit-appearance: none;
appearance: none;
@apply h-2.5 w-2.5 rounded-full cursor-pointer border-0 bg-foreground-2;
}
</style>
@@ -0,0 +1,48 @@
<template>
<div class="flex items-center space-x-2">
<button
:id="name"
type="button"
role="switch"
:aria-checked="modelValue"
:disabled="disabled"
class="relative inline-flex flex-shrink-0 h-[18px] w-[30px] rounded-full transition-colors ease-in-out duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary cursor-pointer disabled:cursor-not-allowed disabled:opacity-40"
:class="modelValue ? 'bg-primary' : 'bg-foreground-3'"
@click="toggle"
>
<span
class="pointer-events-none inline-block h-3 w-3 rounded-full mt-[3px] ml-[3px] ring-0 transition ease-in-out duration-200 bg-foreground-on-primary"
:class="modelValue ? 'translate-x-[12px]' : 'translate-x-0'"
/>
</button>
<label v-if="showLabel" :for="name" class="block label-light">
<span>{{ label || name }}</span>
</label>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
modelValue?: boolean
showLabel?: boolean
name: string
label?: string
disabled?: boolean
}>(),
{
showLabel: true,
modelValue: false
}
)
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const toggle = () => {
if (!props.disabled) {
emit('update:modelValue', !props.modelValue)
}
}
</script>
@@ -1,83 +1,204 @@
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
<template>
<ViewerMenu v-model:open="open" title="View modes">
<template #trigger-icon>
<ViewModes class="h-5 w-5" />
</template>
<template #title>View modes</template>
<div
class="p-1.5"
@mouseenter="cancelCloseTimer"
@mouseleave="isManuallyOpened ? undefined : startCloseTimer"
@focusin="cancelCloseTimer"
@focusout="isManuallyOpened ? undefined : startCloseTimer"
>
<div v-for="(label, mode) in viewModes" :key="mode">
<ViewerMenuItem
:label="label"
:active="mode.toString() === visualStore.defaultViewModeInFile"
@click="handleViewModeChange(Number(mode))"
<div class="absolute left-10 sm:left-[46px] -top-0 bg-foundation rounded-md border border-outline-2 shadow min-w-[180px] z-30">
<!-- Header -->
<div class="px-2 py-1.5 border-b border-outline-2">
<span class="text-body-2xs font-medium text-foreground">View modes</span>
</div>
<!-- View Mode List -->
<div class="py-0.5">
<button
v-for="item in viewModes"
:key="item.mode"
class="w-full px-2 py-1 flex items-center hover:bg-highlight-1 text-left"
@click="handleViewModeChange(item.mode)"
>
<div class="flex items-center gap-1.5">
<CheckIcon
v-if="isActiveMode(item.mode)"
class="w-3.5 h-3.5 text-foreground"
/>
<span v-else class="w-3.5 h-3.5" />
<span class="text-body-2xs" :class="isActiveMode(item.mode) ? 'text-foreground font-medium' : 'text-foreground-2'">
{{ item.label }}
</span>
</div>
</button>
</div>
<!-- Edges Section -->
<div class="border-t border-outline-2 px-2 py-1.5 space-y-2">
<!-- Edges Toggle -->
<div class="flex items-center justify-between">
<span class="text-body-2xs text-foreground">Edges</span>
<FormSwitch
v-model="edgesEnabledLocal"
:show-label="false"
name="toggle-edges"
:disabled="currentViewMode === ViewMode.PEN"
/>
</div>
<!-- Weight Slider (only show when edges enabled) -->
<div v-if="edgesEnabledLocal" class="py-1">
<FormRange
v-model="edgesWeightLocal"
name="edge-weight"
label="Weight"
:min="0.5"
:max="3"
:step="0.1"
/>
</div>
<!-- Color Selector (only show when edges enabled) -->
<div v-if="edgesEnabledLocal" class="flex items-center justify-between">
<span class="text-body-2xs text-foreground-2">Color</span>
<div class="flex items-center gap-1">
<button
v-for="(color, index) in edgesColorOptions"
:key="color === 'auto' ? 'auto' : color"
class="flex items-center justify-center size-4 rounded-full"
:class="edgesColorLocal === color && 'ring-2 ring-primary ring-offset-1'"
@click="handleEdgesColorChange(color)"
>
<span
class="size-3 rounded-full cursor-pointer"
:style="{
background:
index === 0
? 'linear-gradient(135deg, #1a1a1a 50%, #ffffff 50%)'
: `#${(color as number).toString(16).padStart(6, '0')}`
}"
/>
</button>
</div>
</div>
</div>
</ViewerMenu>
</div>
</template>
<script setup lang="ts">
import { useTimeoutFn } from '@vueuse/core'
import { ViewMode } from '@speckle/viewer'
import ViewerMenu from '../menu/ViewerMenu.vue'
import ViewerMenuItem from '../menu/ViewerMenuItem.vue'
import { onUnmounted, ref, computed, onMounted } from 'vue'
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { useVisualStore } from '@src/store/visualStore'
import ViewModes from '../../global/icon/ViewModes.vue'
import FormSwitch from '../../form/FormSwitch.vue'
import FormRange from '../../form/FormRange.vue'
import { CheckIcon } from '@heroicons/vue/24/solid'
import type { ViewModeOptions } from '@src/plugins/viewer'
const viewModes = {
[ViewMode.DEFAULT]: 'Default',
[ViewMode.SHADED]: 'Shaded',
[ViewMode.PEN]: 'Pen',
[ViewMode.ARCTIC]: 'Arctic'
}
// Array to maintain proper display order (matching Speckle frontend)
const viewModes = [
{ mode: ViewMode.DEFAULT, label: 'Rendered' },
{ mode: ViewMode.SHADED, label: 'Shaded' },
{ mode: ViewMode.ARCTIC, label: 'Arctic' },
{ mode: ViewMode.SOLID, label: 'Solid' },
{ mode: ViewMode.PEN, label: 'Pen' }
]
const edgesColorOptions = [
'auto' as const,
0x3b82f6, // blue-500
0x8b5cf6, // violet-500
0x65a30d, // lime-600
0xf97316, // orange-500
0xf43f5e // rose-500
]
const visualStore = useVisualStore()
// Props
const props = defineProps<{
open: boolean
}>()
// Emits
const emit = defineEmits<{
(e: 'update:open', value: boolean): void
(e: 'force-close-others'): void
(e: 'view-mode-clicked', value: ViewMode): void
(e: 'view-mode-clicked', value: ViewMode, options: ViewModeOptions): void
}>()
// Computed v-model
const open = computed({
get: () => props.open,
set: (val) => emit('update:open', val)
// Initialization flag
const isInitialized = ref(false)
// Local state synced with store (with safe defaults)
const edgesEnabledLocal = ref(visualStore.edgesEnabled ?? true)
const edgesWeightLocal = ref(visualStore.edgesWeight ?? 1)
const edgesColorLocal = ref<number | 'auto'>(visualStore.edgesColor ?? 'auto')
// Mark as initialized after next tick to prevent watchers firing on mount
onMounted(() => {
nextTick(() => {
isInitialized.value = true
})
})
// State
const isManuallyOpened = ref(false)
// Current view mode from store
const currentViewMode = computed(() => {
return visualStore.defaultViewModeInFile
? Number(visualStore.defaultViewModeInFile) as ViewMode
: ViewMode.DEFAULT
})
const { start: startCloseTimer, stop: cancelCloseTimer } = useTimeoutFn(
() => {
open.value = false
},
3000,
{ immediate: false }
)
const isActiveMode = (mode: ViewMode) => mode === currentViewMode.value
const handleViewModeChange = (mode: ViewMode) => {
open.value = false
visualStore.setDefaultViewModeInFile(mode.toString())
visualStore.writeViewModeToFile(mode)
emit('view-mode-clicked', mode)
// Compute the actual edge color to use (auto resolves to dark)
const finalEdgesColor = computed(() => {
if (edgesColorLocal.value === 'auto') {
return 0x1a1a1a // dark edges by default
}
return edgesColorLocal.value
})
// Build view mode options
const buildViewModeOptions = (mode: ViewMode): ViewModeOptions => {
// PEN mode always has edges enabled and opacity 1
const isPenMode = mode === ViewMode.PEN
return {
edges: isPenMode ? true : edgesEnabledLocal.value,
outlineThickness: edgesWeightLocal.value,
outlineOpacity: isPenMode ? 1 : 0.75,
outlineColor: finalEdgesColor.value
}
}
onUnmounted(() => {
cancelCloseTimer()
const handleViewModeChange = (mode: ViewMode) => {
const options = buildViewModeOptions(mode)
visualStore.setDefaultViewModeInFile(mode.toString())
visualStore.writeViewModeToFile(mode)
emit('view-mode-clicked', mode, options)
}
const handleEdgesColorChange = (color: number | 'auto') => {
edgesColorLocal.value = color
}
// Apply edges changes to viewer when settings change
const applyEdgesSettings = () => {
// Don't apply during initialization
if (!isInitialized.value) return
// Update store
visualStore.setEdgesEnabled(edgesEnabledLocal.value)
visualStore.setEdgesWeight(edgesWeightLocal.value)
visualStore.setEdgesColor(edgesColorLocal.value)
visualStore.writeEdgesSettingsToFile()
// Re-apply current view mode with new options
const options = buildViewModeOptions(currentViewMode.value)
emit('view-mode-clicked', currentViewMode.value, options)
}
// Watch for edges settings changes and apply them
watch([edgesEnabledLocal, edgesWeightLocal, edgesColorLocal], () => {
applyEdgesSettings()
})
// Sync local state with store when store changes (e.g., from file load)
watch(() => visualStore.edgesEnabled, (val) => {
edgesEnabledLocal.value = val
})
watch(() => visualStore.edgesWeight, (val) => {
edgesWeightLocal.value = val
})
watch(() => visualStore.edgesColor, (val) => {
edgesColorLocal.value = val
})
</script>
+22 -4
View File
@@ -35,12 +35,19 @@ export interface Hit {
point: { x: number; y: number; z: number }
}
export interface ViewModeOptions {
edges?: boolean
outlineThickness?: number
outlineOpacity?: number
outlineColor?: number
}
export interface IViewerEvents {
ping: (message: string) => void
setSelection: (objectIds: string[]) => void
resetFilter: (objectIds: string[], ghost: boolean, zoom: boolean) => void
filterSelection: (objectIds: string[], ghost: boolean, zoom: boolean) => void
setViewMode: (viewMode: ViewMode) => void
setViewMode: (viewMode: ViewMode, options?: ViewModeOptions) => void
colorObjectsByGroup: (
colorById: {
objectIds: string[]
@@ -138,9 +145,9 @@ export class ViewerHandler {
return
}
public setViewMode(viewMode: ViewMode) {
public setViewMode(viewMode: ViewMode, options?: ViewModeOptions) {
const viewModes = this.viewer.getExtension(ViewModes)
viewModes.setViewMode(viewMode)
viewModes.setViewMode(viewMode, options)
}
public snapshotCameraPositionAndStore = () => {
@@ -240,7 +247,18 @@ export class ViewerHandler {
store.setSpeckleViews(speckleViews)
if (store.defaultViewModeInFile) {
this.setViewMode(Number(store.defaultViewModeInFile))
const viewMode = Number(store.defaultViewModeInFile) as ViewMode
// Apply view mode with edges options from store (with safe defaults)
const edgesEnabled = store.edgesEnabled ?? true
const edgesWeight = store.edgesWeight ?? 1
const edgesColor = store.edgesColor ?? 'auto'
const options: ViewModeOptions = {
edges: edgesEnabled,
outlineThickness: edgesWeight,
outlineOpacity: viewMode === ViewMode.PEN ? 1 : 0.75,
outlineColor: edgesColor === 'auto' ? undefined : edgesColor
}
this.setViewMode(viewMode, options)
}
Tracker.dataLoaded({
@@ -62,6 +62,11 @@ export const useVisualStore = defineStore('visualStore', () => {
const cameraPosition = ref<number[]>(undefined)
const defaultViewModeInFile = ref<string>(undefined)
// Edges settings for view modes
const edgesEnabled = ref<boolean>(true)
const edgesWeight = ref<number>(1)
const edgesColor = ref<number | 'auto'>('auto')
const speckleViews = ref<SpeckleView[]>([])
// callback mechanism to viewer to be able to manage input data accordingly.
@@ -471,6 +476,37 @@ export const useVisualStore = defineStore('visualStore', () => {
const setCameraPositionInFile = (newValue: number[]) => (cameraPosition.value = newValue)
const setDefaultViewModeInFile = (newValue: string) => (defaultViewModeInFile.value = newValue)
// Edges settings setters
const setEdgesEnabled = (val: boolean) => {
edgesEnabled.value = val
}
const setEdgesWeight = (val: number) => {
edgesWeight.value = val
}
const setEdgesColor = (val: number | 'auto') => {
edgesColor.value = val
}
const writeEdgesSettingsToFile = () => {
// NOTE: need skipping the update function, it resets the viewer state unnecessarily.
postFileSaveSkipNeeded.value = true
host.value.persistProperties({
merge: [
{
objectName: 'viewMode',
properties: {
edgesEnabled: edgesEnabled.value,
edgesWeight: edgesWeight.value,
edgesColor: edgesColor.value === 'auto' ? -1 : edgesColor.value
},
selector: null
}
]
})
}
const setSpeckleViews = (newSpeckleViews: SpeckleView[]) => (speckleViews.value = newSpeckleViews)
const setFormattingSettings = (newFormattingSettings: SpeckleVisualSettingsModel) =>
(formattingSettings.value = newFormattingSettings)
@@ -555,6 +591,9 @@ export const useVisualStore = defineStore('visualStore', () => {
isLoadingFromFile,
cameraPosition,
defaultViewModeInFile,
edgesEnabled,
edgesWeight,
edgesColor,
speckleViews,
postFileSaveSkipNeeded,
postClickSkipNeeded,
@@ -583,6 +622,10 @@ export const useVisualStore = defineStore('visualStore', () => {
setPostFileSaveSkipNeeded,
setCameraPositionInFile,
setDefaultViewModeInFile,
setEdgesEnabled,
setEdgesWeight,
setEdgesColor,
writeEdgesSettingsToFile,
setSpeckleViews,
loadObjectsFromFile,
setHost,
+18
View File
@@ -185,6 +185,24 @@ export class Visual implements IVisual {
)
}
// Load edges settings
const viewModeSettings = options.dataViews[0].metadata.objects.viewMode
if (viewModeSettings) {
if ('edgesEnabled' in viewModeSettings) {
console.log(`Edges Enabled: ${viewModeSettings.edgesEnabled as boolean}`)
visualStore.setEdgesEnabled(viewModeSettings.edgesEnabled as boolean)
}
if ('edgesWeight' in viewModeSettings) {
console.log(`Edges Weight: ${viewModeSettings.edgesWeight as number}`)
visualStore.setEdgesWeight(viewModeSettings.edgesWeight as number)
}
if ('edgesColor' in viewModeSettings) {
const colorVal = viewModeSettings.edgesColor as number
console.log(`Edges Color: ${colorVal}`)
visualStore.setEdgesColor(colorVal === -1 ? 'auto' : colorVal)
}
}
if (options.dataViews[0].metadata.objects.cameraPosition?.positionX as string) {
console.log(`Stored camera position is found`)
visualStore.setCameraPositionInFile([