Files
speckle-server/packages/frontend-2/components/viewer/selection/Object.vue
T
andrewwallacespeckle fcb924d3a5 DO NOT MERGE - refactor: new design system implementation (#2537)
* refactor WIP

* Button design changes

* FE2 FormButton Updates

* ts composition api

* CommonTextLink Changes

* CommonTextLink prop updates

* Add disabled styles

* WIP

* Design system updates

* Colour Updates

* New Text Styles. Initial FE2 changes

* More fe2 styling classes

* Minor update

* Minor update

* Fix build

* More updates for discussion

* More styling updates

* Minor updates to inputs

* Revert change to size options

* More text updates

* More font class swapping

* Revert dui3 changes

* Confirmed Lineheights

* Add story files for new text styles

* Minor copy changes

* Minor typo

* Revert variant>color

* New Colours WIP

* andrew/web-1371-misalignment-in-account-dropdown

* andrew/web-1374-settings-text-styles-are-not-right

* andrew/web-1375-nav-texts-should-be-14px

* andrew/web-1376-decrease-size-of-versions-header

* andrew/web-1377-version-card-title

* Updates

* semibold>medium

* Colour updates

* Sizing updates

* Colour updates

* Colour updates

* Measure mode

* Updates

* Fix build

* Fix build

* WIP Updates

* Changes from PR

* Updated login, registration and reset password styling

* Make share dropdown bg white

* Updated viewer titles

* Fix: Resize panel highlight color in the viewer should be blue

* Fix: Blue + Add link in Models. And other blue links in Viewer

* Add labelPosition Prop. Fix Button stories

* Updated CommonLink to remove default underline

* Add Highlight Color

* Card updates from Michal

* Updated discussion icon on version card

* Small tweaks to version card

* Small tweaks to version card

* Fix: Ghost button doesn't have padding

* Fix: Write Delete...

* Fix: Version hover border color

* Updates to Project Card. Updates to PageTabs

* Fix: Adjust title in announcement modal

* Updates from Comments

* Select Background Colour

* Fix: Select dropdown color

* Improve list view. Improve discussions

* Fix: Minor tweaks to onboarding checklist

* Fix: Clean up nav

* Hide third item when not >md

* Change project heading size

* Add border to version card

* Adjust spacing in dropdowns

* Slight change

* Update button style in Version card

* Tweaked nav menu

* Tweaked nav menu

* Various styling tweaks

* Fix settings modal subheader

* Various styling tweaks and fixes

* Tweak settings dialog styling

* Tweak simple scrollbar

* Minor tweaks to model page

* Minor tweaks to model page

* Minor tweak to login

* Tweak discussion card

* Tweak settings page

* Tweak vertical tabs

* Tweak Dialog alignment

* Fix some paddings

* Change IconVersions to ClockIcon

* Tweak spacing between icons

* Updates to Card Icons

* Bold "connectors" in empty project message

* Remove padding in Profile field

* Update inline model create

* Remove icons from share menu

* Updated Delete dialog

* Wrong text positioning in alert

* Updated copy in dropdown

* Change bg to bg-foundation in select dropdown component

* Fix merge conflicy

* Selection Info title colour

* Wrong text class

* Update card colours based on call

* Update card colours

* Update empty state

* Input label font weight

* Updates to Embed

* Various styling fixes

* Fix; Viewer panel header styling

* Fix; Adjust BG in dev mode list items

* Fix; Fix button placement in video modal

* Fix: Share menu is not using LayoutMenu

* Fix: Buttons clash under filters

* Fix: Adjust spacing in selection info

* Fix: Adjust gray BG behind model preview images

* Fix: No hover cursor on model card

* Fix: Align text styling in dev mode and selection info panel

* Fix for menu width

* Fix mobile problems

* Fix Add spacing on new login screens

* Revert prose change. Add prose-sm

* Text - Use contain for bg image

* Fix onboarding screens

* Responsive fixes

* Fix hydration errors

* Added padding to Add Model Dialog

* Fix versions buttons

* Fix build problem

* Changes PRE PR

* Final Pre PR Changes

* Remove DUI3 change

* Fix small issue with dialog after merge conflict

* Remove label classes from Visibility Select

* Revert changes made in Controls.vue

* Remove old-webhooks

* Add highlight colours to Storybook

* Add v-keyboard-clickable

---------

Co-authored-by: Mike Tasset <mike.tasset@gmail.com>
2024-07-30 15:34:41 +01:00

341 lines
10 KiB
Vue

<template>
<div
:class="`${
isModifiedQuery.modified && root
? 'outline outline-2 rounded py-1 px-1 outline-amber-500'
: ''
}`"
>
<div class="mb-1 flex items-center">
<button
class="flex h-full w-full pl-1 pr-2 py-0.5 items-center gap-1 rounded bg-foundation-2 hover:sm:bg-primary-muted hover:text-primary"
:class="unfold && 'text-primary'"
@click="unfold = !unfold"
@mouseenter="highlightObject"
@focusin="highlightObject"
@mouseleave="unhighlightObject"
@focusout="unhighlightObject"
>
<ChevronDownIcon
:class="`h-3 w-3 transition ${headerClasses} ${
!unfold ? '-rotate-90' : 'rotate-0'
}`"
/>
<div :class="`truncate text-body-2xs font-medium ${headerClasses}`">
{{ title || headerAndSubheader.header }}
<span
v-if="(props.root || props.modifiedSibling) && isModifiedQuery.modified"
>
{{ isModifiedQuery.isNew ? '(new)' : '(old)' }}
</span>
</div>
</button>
</div>
<div v-if="unfold" class="ml-1 space-y-1 px-2 py-1">
<div
v-for="(kvp, index) in [
...categorisedValuePairs.primitives,
...categorisedValuePairs.nulls
]"
:key="index"
class="flex w-full"
>
<div
:class="`grid grid-cols-3 w-full pl-2 py-0.5 ${
kvp.value === null || kvp.value === undefined ? 'text-foreground-2' : ''
}`"
>
<div
class="col-span-1 truncate text-body-3xs mr-2 font-medium"
:title="(kvp.key as string)"
>
{{ kvp.key }}
</div>
<div
class="group col-span-2 pl-1 truncate text-body-3xs flex gap-1 items-center"
:title="(kvp.value as string)"
>
<div class="flex gap-1 items-center w-full">
<!-- NOTE: can't do kvp.value || 'null' because 0 || 'null' = 'null' -->
<span
class="truncate"
:class="kvp.value === null ? '' : 'group-hover:max-w-[calc(100%-1rem)]'"
>
{{ kvp.value === null ? 'null' : kvp.value }}
</span>
<button
v-if="isCopyable(kvp)"
:class="isCopyable(kvp) ? 'cursor-pointer' : 'cursor-default'"
class="opacity-0 group-hover:opacity-100 w-4"
@click="handleCopy(kvp)"
>
<ClipboardDocumentIcon class="h-3 w-3" />
</button>
</div>
</div>
</div>
</div>
<div
v-for="(kvp, index) in categorisedValuePairs.objects"
:key="index"
class="pl-2"
>
<ViewerSelectionObject
:object="(kvp.value as SpeckleObject) || {}"
:title="(kvp.key as string)"
:unfold="false"
/>
</div>
<div
v-for="(kvp, index) in categorisedValuePairs.nonPrimitiveArrays"
:key="index"
class="text-xs"
>
<div class="text-foreground-2 grid grid-cols-3 pl-2">
<div
class="col-span-1 truncate text-xs font-medium"
:title="(kvp.key as string)"
>
{{ kvp.key }}
</div>
<div class="col-span-2 flex w-full min-w-0 truncate text-xs pl-1">
<div class="flex-grow truncate">{{ kvp.innerType }} array</div>
<div class="text-foreground-2">({{ kvp.arrayLength }})</div>
</div>
</div>
</div>
<div v-for="(kvp, index) in categorisedValuePairs.primitiveArrays" :key="index">
<div class="grid grid-cols-3">
<div
class="col-span-1 truncate text-xs font-medium pl-2"
:title="(kvp.key as string)"
>
{{ kvp.key }}
</div>
<div
class="col-span-2 flex w-full min-w-0 truncate text-xs"
:title="(kvp.value as string)"
>
<div class="flex-grow truncate">{{ kvp.arrayPreview }}</div>
<div class="text-foreground-2">({{ kvp.arrayLength }})</div>
</div>
</div>
</div>
</div>
<div v-if="isModifiedQuery.modified && isModifiedQuery.pair && root" class="mt-2">
<ViewerSelectionObject :object="isModifiedQuery.pair" :modified-sibling="true" />
</div>
</div>
</template>
<script setup lang="ts">
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { ChevronDownIcon } from '@heroicons/vue/24/solid'
import { ClipboardDocumentIcon } from '@heroicons/vue/24/outline'
import type { SpeckleObject } from '~~/lib/viewer/helpers/sceneExplorer'
import { getHeaderAndSubheaderForSpeckleObject } from '~~/lib/object-sidebar/helpers'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import { useHighlightedObjectsUtilities } from '~/lib/viewer/composables/ui'
const {
ui: {
diff: { result, enabled: diffEnabled }
}
} = useInjectedViewerState()
const props = withDefaults(
defineProps<{
object: SpeckleObject
root?: boolean
title?: string
unfold?: boolean
debug?: boolean
modifiedSibling?: boolean
}>(),
{ debug: false, unfold: false, root: false, modifiedSibling: false }
)
const { highlightObjects, unhighlightObjects } = useHighlightedObjectsUtilities()
const unfold = ref(props.unfold)
const isAdded = computed(() => {
if (!diffEnabled.value) return false
return (
result.value?.added.findIndex(
(o) => (o.model.raw as SpeckleObject).applicationId === props.object.applicationId
) !== -1
)
})
const isRemoved = computed(() => {
if (!diffEnabled.value) return false
return (
result.value?.removed.findIndex(
(o) => (o.model.raw as SpeckleObject).applicationId === props.object.applicationId
) !== -1
)
})
const isUnchanged = computed(() => {
if (!diffEnabled.value) return false
return (
result.value?.unchanged.findIndex(
(o) => (o.model.raw as SpeckleObject).applicationId === props.object.applicationId
) !== -1
)
})
const isModifiedQuery = computed(() => {
// if (props.modifiedSibling) return { modified: false } // prevent recursion?
if (!diffEnabled.value) return { modified: false }
const modifiedObjectPairs = result.value?.modified.map((pair) => {
return [pair[0].model.raw as SpeckleObject, pair[1].model.raw as SpeckleObject]
})
if (!modifiedObjectPairs) return { modified: false }
const obj = props.object
const pairedItems = modifiedObjectPairs.find(
(item) => item[0].id === obj.id || item[1].id === obj.id
)
if (!pairedItems) return { modified: false }
const pair = pairedItems[0].id === obj.id ? pairedItems[1] : pairedItems[0]
if (!pair) return { modified: false }
return {
modified: true,
pair,
isNew: pairedItems[0].id !== obj.id
}
})
const headerClasses = computed(() => {
if (props.modifiedSibling) return 'text-amber-500'
if (!props.root) return ''
if (!diffEnabled.value) return ''
if (!Object.keys(props.object).includes('applicationId')) return ''
if (isAdded.value) return 'text-green-500'
if (isRemoved.value) return 'text-red-500'
if (isUnchanged.value) return 'text-foreground'
return 'text-amber-500'
})
const headerAndSubheader = computed(() => {
return getHeaderAndSubheaderForSpeckleObject(props.object)
})
const isCopyable = (kvp: Record<string, unknown>) => {
return kvp.value !== null && kvp.value !== undefined && typeof kvp.value !== 'object'
}
const handleCopy = async (kvp: Record<string, unknown>) => {
const { copy } = useClipboard()
if (isCopyable(kvp)) {
const keyName = kvp.key as string
await copy(kvp.value as string, {
successMessage: `${keyName} copied to clipboard`,
failureMessage: `Failed to copy ${keyName} to clipboard`
})
}
}
const ignoredProps = [
'__closure',
'displayMesh',
'displayValue',
'totalChildrenCount',
'__importedUrl',
'__parents',
'bbox'
]
const keyValuePairs = computed(() => {
const kvps = [] as Record<string, unknown>[]
// handle revit paramters
if (props.title === 'parameters') {
const paramKeys = Object.keys(props.object)
for (const prop of paramKeys) {
const param = props.object[prop] as Record<string, unknown>
if (!param) continue
kvps.push({
key: param.name as string,
type: typeof param.value,
innerType: null,
arrayLength: null,
arrayPreview: null,
value: param.value
})
}
return kvps
}
const objectKeys = Object.keys(props.object)
for (const key of objectKeys) {
if (ignoredProps.includes(key)) continue
const type = Array.isArray(props.object[key]) ? 'array' : typeof props.object[key]
let innerType = null
let arrayLength = null
let arrayPreview = null
if (type === 'array') {
const arr = props.object[key] as unknown[]
arrayLength = arr.length
if (arr.length > 0) {
innerType = Array.isArray(arr[0]) ? 'array' : typeof arr[0]
arrayPreview = arr.slice(0, 3).join(', ')
if (arr.length > 10) arrayPreview += ' ...' // in case truncate doesn't hit
}
}
kvps.push({
key,
type,
innerType,
arrayLength,
arrayPreview,
value: props.object[key]
})
}
return kvps
})
const categorisedValuePairs = computed(() => {
return {
primitives: keyValuePairs.value.filter(
(item) => item.type !== 'object' && item.type !== 'array' && item.value !== null
),
objects: keyValuePairs.value.filter(
(item) => item.type === 'object' && item.value !== null
),
nonPrimitiveArrays: keyValuePairs.value.filter(
(item) =>
item.type === 'array' &&
item.value !== null &&
(item.innerType === 'object' || item.innerType === 'array')
),
primitiveArrays: keyValuePairs.value.filter(
(item) =>
item.type === 'array' &&
item.value !== null &&
!(item.innerType === 'object' || item.innerType === 'array')
),
nulls: keyValuePairs.value.filter((item) => item.value === null)
}
})
const highlightObject = () => {
highlightObjects([props.object.id])
}
const unhighlightObject = () => {
unhighlightObjects([props.object.id])
}
watch(
() => props.unfold,
(newVal) => {
unfold.value = newVal
}
)
</script>