refactor(fe): Icons and Buttons throughout viewer

refactor(fe): Icons and Buttons throughout viewer
This commit is contained in:
andrewwallacespeckle
2025-08-14 11:00:36 +01:00
committed by GitHub
20 changed files with 170 additions and 179 deletions
@@ -1,18 +1,21 @@
<template>
<button
v-tippy="getTooltipProps(isIsolated ? 'Unisolate' : 'Isolate')"
:aria-label="isIsolated ? 'Unisolate' : 'Isolate'"
class="group-hover:opacity-100 rounded-md h-6 w-6 flex items-center justify-center"
:class="buttonClasses"
@click.stop="$emit('click', $event)"
>
<IconViewerUnisolate v-if="isIsolated" class="w-3.5 h-3.5" />
<IconViewerIsolate v-else class="w-3.5 h-3.5" />
</button>
<div v-tippy="getTooltipProps(isIsolated ? 'Unisolate' : 'Isolate')">
<FormButton
color="subtle"
size="sm"
:icon-left="isIsolated ? FunnelX : Funnel"
hide-text
@click.stop="$emit('click', $event)"
>
{{ isIsolated ? 'Unisolate' : 'Isolate' }}
</FormButton>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
import { FunnelX, Funnel } from 'lucide-vue-next'
defineProps<{
isIsolated: boolean
forceVisible?: boolean
}>()
@@ -22,12 +25,4 @@ const { getTooltipProps } = useSmartTooltipDelay()
defineEmits<{
click: [event: Event]
}>()
const buttonClasses = computed(() => {
return {
'opacity-100 hover:bg-highlight-1': props.isIsolated,
'opacity-100 hover:bg-highlight-3': !props.isIsolated && props.forceVisible,
'sm:opacity-0 hover:bg-highlight-3': !props.isIsolated && !props.forceVisible
}
})
</script>
@@ -1,18 +1,22 @@
<template>
<button
v-tippy="getTooltipProps(isHidden ? 'Show' : 'Hide')"
:aria-label="isHidden ? 'Show' : 'Hide'"
class="group-hover:opacity-100 hover:bg-highlight-3 rounded-md h-6 w-6 flex items-center justify-center"
:class="buttonClasses"
@click.stop="$emit('click', $event)"
>
<IconEyeClosed v-if="isHidden" class="w-4 h-4" />
<IconEye v-else class="w-4 h-4" />
</button>
<div v-tippy="getTooltipProps(isHidden ? 'Show' : 'Hide')">
<FormButton
color="subtle"
size="sm"
:icon-left="isHidden ? EyeClosed : Eye"
hide-text
@click.stop="$emit('click', $event)"
>
{{ isHidden ? 'Show' : 'Hide' }}
</FormButton>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
import { FormButton } from '@speckle/ui-components'
import { Eye, EyeClosed } from 'lucide-vue-next'
defineProps<{
isHidden: boolean
forceVisible?: boolean
}>()
@@ -22,11 +26,4 @@ defineEmits<{
}>()
const { getTooltipProps } = useSmartTooltipDelay()
const buttonClasses = computed(() => {
return {
'opacity-100': props.isHidden || props.forceVisible,
'sm:opacity-0': !props.isHidden && !props.forceVisible
}
})
</script>
@@ -14,7 +14,7 @@
class="bg-foundation-2 outline outline-2 outline-primary rounded-tr-full rounded-tl-full rounded-br-full w-8 h-8 -top-10 absolute flex justify-center items-center hover:shadow-md"
@click="onThreadClick"
>
<PlusIcon
<Plus
:class="`w-5 h-5 text-primary ${
modelValue.isExpanded ? 'rotate-45' : ''
} transition`"
@@ -27,7 +27,7 @@
>
Add Comment
<button v-tippy="'Close'" @click="onThreadClick">
<PlusIcon class="w-5 h-5 text-foreground-2 rotate-45" />
<Plus class="w-4 h-4 text-foreground-2 rotate-45" />
</button>
</div>
<FormFileUploadZone
@@ -60,15 +60,14 @@
/>
<div class="w-full flex justify-between items-center p-1">
<FormButton
:icon-left="PaperClipIcon"
:icon-left="Paperclip"
hide-text
:disabled="isPostingNewThread"
color="subtle"
class="!bg-foundation dark:!bg-foundation-2"
@click="trackAttachAndOpenFilePicker()"
/>
<FormButton
:icon-left="PaperAirplaneIcon"
:icon-left="SendHorizonal"
hide-text
:loading="isPostingNewThread"
@click="() => onSubmit()"
@@ -82,7 +81,6 @@
<div v-else></div>
</template>
<script setup lang="ts">
import { PlusIcon, PaperAirplaneIcon, PaperClipIcon } from '@heroicons/vue/24/solid'
import type { Nullable } from '@speckle/shared'
import { onKeyDown } from '@vueuse/core'
import { useIsTypingUpdateEmitter } from '~~/lib/viewer/composables/commentBubbles'
@@ -100,6 +98,7 @@ import { useServerFileUploadLimit } from '~~/lib/common/composables/serverInfo'
import { UniqueFileTypeSpecifier } from '~~/lib/core/helpers/file'
import { acceptedFileExtensions } from '@speckle/shared/blobs'
import type { UploadableFileItem } from '@speckle/ui-components'
import { Paperclip, SendHorizonal, Plus } from 'lucide-vue-next'
const { isEnabled: isEmbedEnabled } = useEmbed()
@@ -51,7 +51,7 @@
<div class="flex-grow flex items-center gap-x-1">
<div class="flex items-center gap-x-0.5">
<FormButton
:icon-left="ChevronLeftIcon"
:icon-left="ChevronLeft"
color="outline"
hide-text
size="sm"
@@ -59,7 +59,7 @@
@click="emit('prev', modelValue)"
/>
<FormButton
:icon-left="ChevronRightIcon"
:icon-left="ChevronRight"
color="outline"
hide-text
size="sm"
@@ -82,14 +82,15 @@
hide-text
size="sm"
color="subtle"
:icon-left="iconThreeDots"
:class="showMenu ? '!bg-highlight-3' : ''"
:icon-left="Ellipsis"
@click="showMenu = !showMenu"
/>
</LayoutMenu>
</div>
<FormButton
v-tippy="modelValue.archived ? 'Unresolve' : 'Resolve'"
:icon-left="IconCircleCheck"
:icon-left="CircleCheck"
hide-text
:disabled="!canArchiveOrUnarchive"
color="subtle"
@@ -97,7 +98,7 @@
@click="toggleCommentResolvedStatus()"
/>
<FormButton
:icon-left="IconXMark"
:icon-left="X"
hide-text
color="subtle"
size="sm"
@@ -176,11 +177,14 @@
</template>
<script setup lang="ts">
import {
ChevronLeftIcon,
ChevronRightIcon,
ArrowLeftIcon,
ArrowUpRightIcon
} from '@heroicons/vue/24/outline'
MoveLeft,
MoveRight,
CircleCheck,
X,
ChevronLeft,
ChevronRight,
Ellipsis
} from 'lucide-vue-next'
import { ensureError } from '@speckle/shared'
import type { Nullable } from '@speckle/shared'
import { onKeyDown, useClipboard, useDraggable, onClickOutside } from '@vueuse/core'
@@ -203,7 +207,6 @@ import { useMixpanel } from '~~/lib/core/composables/mp'
import { useThreadUtilities } from '~~/lib/viewer/composables/ui'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { graphql } from '~/lib/common/generated/gql'
import type { ConcreteComponent } from 'vue'
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
enum ActionTypes {
@@ -259,7 +262,6 @@ const showMenu = ref(false)
const commentsContainer = ref(null as Nullable<HTMLElement>)
const threadContainer = ref(null as Nullable<HTMLElement>)
const threadActivator = ref(null as Nullable<HTMLElement>)
const iconThreeDots = resolveComponent('IconThreeDots') as ConcreteComponent
onClickOutside(threadContainer, (event) => {
const viewerElement = document.getElementById('viewer')
@@ -276,8 +278,6 @@ onClickOutside(threadContainer, (event) => {
const handle = ref(null as Nullable<HTMLElement>)
const justCreatedReply = ref(false)
const IconXMark = resolveComponent('IconXMark') as ConcreteComponent
const IconCircleCheck = resolveComponent('IconCircleCheck') as ConcreteComponent
const comments = computed(() => [
props.modelValue,
@@ -584,13 +584,13 @@ const bannerButton = computed(() => {
if (hasClickedFullContext.value) {
return {
text: 'Back',
icon: ArrowLeftIcon,
icon: MoveLeft,
action: goBack
}
}
return {
text: 'Full context',
icon: ArrowUpRightIcon,
icon: MoveRight,
action: handleContextClick
}
})
@@ -7,7 +7,7 @@
class="text-foreground hover:text-foreground-2 flex items-center gap-x-1"
@click="() => onAttachmentClick(attachment)"
>
<PaperClipIcon class="size-3" />
<Paperclip class="size-3" />
<span class="truncate relative text-body-3xs">
{{ attachment.fileName }}
</span>
@@ -34,7 +34,7 @@
</template>
<template v-else>
<span class="inline-flex space-x-4 items-center">
<ExclamationTriangleIcon class="w-6 h-6" />
<TriangleAlert class="w-6 h-6" />
<span>
Please note: This file is user-uploaded and has not been scanned for
security. Download at your own discretion.
@@ -48,11 +48,6 @@
</div>
</template>
<script setup lang="ts">
import {
ArrowDownTrayIcon,
ExclamationTriangleIcon,
PaperClipIcon
} from '@heroicons/vue/24/outline'
import type { Get } from 'type-fest'
import { ensureError } from '@speckle/shared'
import type { Nullable, Optional } from '@speckle/shared'
@@ -62,6 +57,7 @@ import { prettyFileSize } from '~~/lib/core/helpers/file'
import { useFileDownload } from '~~/lib/core/composables/fileUpload'
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
import type { LayoutDialogButton } from '@speckle/ui-components'
import { Download, Paperclip, TriangleAlert } from 'lucide-vue-next'
type AttachmentFile = NonNullable<
Get<ThreadCommentAttachmentFragment, 'text.attachments[0]'>
@@ -135,7 +131,7 @@ const dialogButtons = computed((): Optional<LayoutDialogButton[]> => {
? prettyFileSize(dialogAttachment.value.fileSize)
: 'Download',
props: {
iconLeft: ArrowDownTrayIcon,
iconLeft: Download,
color: 'outline'
},
onClick: () => {
@@ -25,15 +25,14 @@
/>
<div class="flex justify-between items-center p-1">
<FormButton
:icon-left="PaperClipIcon"
:icon-left="Paperclip"
:disabled="loading"
color="subtle"
hide-text
class="!bg-foundation dark:!bg-foundation-2"
@click="trackAttachAndOpenFilePicker()"
/>
<FormButton
:icon-left="PaperAirplaneIcon"
:icon-left="SendHorizonal"
hide-text
:disabled="loading"
@click="onSubmit"
@@ -44,7 +43,6 @@
</div>
</template>
<script setup lang="ts">
import { PaperAirplaneIcon, PaperClipIcon } from '@heroicons/vue/24/solid'
import type { Nullable } from '@speckle/shared'
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
import { useMixpanel } from '~~/lib/core/composables/mp'
@@ -60,6 +58,7 @@ import { useServerFileUploadLimit } from '~~/lib/common/composables/serverInfo'
import { UniqueFileTypeSpecifier } from '~~/lib/core/helpers/file'
import { acceptedFileExtensions } from '@speckle/shared/blobs'
import type { UploadableFileItem } from '@speckle/ui-components'
import { Paperclip, SendHorizonal } from 'lucide-vue-next'
const props = defineProps<{
modelValue: CommentBubbleModel
@@ -20,6 +20,7 @@
hide-text
color="subtle"
:icon-left="settingsIcon"
size="sm"
:class="
showVisibilityOptions
? '!text-primary-focus !dark:text-foreground-on-primary !bg-info-lighter'
@@ -3,7 +3,7 @@
<template #title>
<div class="flex items-center gap-x-1">
<FormButton
:icon-left="ChevronLeftIcon"
:icon-left="ChevronLeft"
color="subtle"
class="-ml-3"
hide-text
@@ -71,7 +71,7 @@
</ViewerLayoutSidePanel>
</template>
<script setup lang="ts">
import { ChevronLeftIcon } from '@heroicons/vue/24/solid'
import { ChevronLeft } from 'lucide-vue-next'
import { VisualDiffMode } from '@speckle/viewer'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import { uniqBy, debounce } from 'lodash-es'
@@ -67,6 +67,7 @@ import { onKeyStroke, useBreakpoints } from '@vueuse/core'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { Ruler, Scissors, Sun, Layers2, Glasses } from 'lucide-vue-next'
enum ActivePanel {
none = 'none',
@@ -111,7 +112,7 @@ const panels = shallowRef({
[ActivePanel.measurements]: {
id: ActivePanel.measurements,
name: 'Measure',
icon: 'IconViewerMeasurements',
icon: Ruler,
tooltip: getShortcutDisplayText(shortcuts.ToggleMeasurements, {
format: 'separate'
}),
@@ -120,28 +121,28 @@ const panels = shallowRef({
[ActivePanel.sectionBox]: {
id: ActivePanel.sectionBox,
name: 'Section',
icon: 'IconViewerSectionBox',
icon: Scissors,
tooltip: getShortcutDisplayText(shortcuts.ToggleSectionBox, { format: 'separate' }),
extraClasses: ''
},
[ActivePanel.explode]: {
id: ActivePanel.explode,
name: 'Explode',
icon: 'IconViewerExplode',
icon: Layers2,
tooltip: getShortcutDisplayText(shortcuts.ToggleExplode, { format: 'separate' }),
extraClasses: 'hidden md:flex'
},
[ActivePanel.viewModes]: {
id: ActivePanel.viewModes,
name: 'View modes',
icon: 'IconViewerViewModes',
icon: Glasses,
tooltip: getShortcutDisplayText(shortcuts.ToggleViewModes, { format: 'separate' }),
extraClasses: ''
},
[ActivePanel.lightControls]: {
id: ActivePanel.lightControls,
name: 'Light controls',
icon: 'IconViewerLightControls',
icon: Sun,
tooltip: getShortcutDisplayText(shortcuts.ToggleLightControls, {
format: 'separate'
}),
@@ -23,7 +23,7 @@ import type { ConcreteComponent } from 'vue'
defineProps<{
active?: boolean
icon?: string | ConcreteComponent
icon?: ConcreteComponent
secondary?: boolean
dot?: boolean
}>()
@@ -20,7 +20,7 @@
)
"
:active="activePanel === 'models'"
:icon="'IconViewerModels'"
:icon="Box"
@click="toggleActivePanel('models')"
/>
<ViewerControlsButtonToggle
@@ -33,7 +33,7 @@
)
"
:active="activePanel === 'filters'"
:icon="'IconViewerExplorer'"
:icon="ListFilter"
:dot="hasActiveFilters"
@click="toggleActivePanel('filters')"
/>
@@ -47,7 +47,7 @@
)
"
:active="activePanel === 'discussions'"
:icon="'IconViewerDiscussions'"
:icon="MessageSquareText"
@click="toggleActivePanel('discussions')"
/>
@@ -88,7 +88,7 @@
)
"
:active="activePanel === 'devMode'"
:icon="'IconViewerDev'"
:icon="CodeXml"
secondary
@click="toggleActivePanel('devMode')"
/>
@@ -98,14 +98,14 @@
placement: 'right'
})
"
:icon="'IconDocs'"
:icon="BookOpen"
secondary
@click="openDocs"
/>
<ViewerControlsButtonToggle
v-if="isIntercomEnabled"
v-tippy="getTooltipProps('Get help')"
:icon="'IconIntercom'"
:icon="CircleQuestionMark"
secondary
@click="openIntercomChat"
/>
@@ -166,7 +166,15 @@ import { useFunctionRunsStatusSummary } from '~/lib/automate/composables/runStat
import { useIntercomEnabled } from '~~/lib/intercom/composables/enabled'
import { viewerDocsRoute } from '~~/lib/common/helpers/route'
import { useAreSavedViewsEnabled } from '~/lib/viewer/composables/savedViews/general'
import { Camera } from 'lucide-vue-next'
import {
Camera,
CodeXml,
BookOpen,
Box,
ListFilter,
MessageSquareText,
CircleQuestionMark
} from 'lucide-vue-next'
import { ModelsSubView } from '~~/lib/viewer/helpers/sceneExplorer'
type ActivePanel =
@@ -19,12 +19,12 @@
}
)
"
icon="IconViewerZoom"
:icon="Fullscreen"
@click="trackAndzoomExtentsOrSelection()"
/>
<ViewerControlsButtonToggle
v-tippy="getTooltipProps('Camera controls', { placement: 'left' })"
icon="IconViewerCameraControls"
:icon="Video"
:active="activePanel === 'cameraControls'"
@click="toggleActivePanel('cameraControls')"
/>
@@ -47,6 +47,7 @@ import { onClickOutside, useBreakpoints } from '@vueuse/core'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import type { Nullable } from '@speckle/shared'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { Fullscreen, Video } from 'lucide-vue-next'
type ActivePanel = 'none' | 'cameraControls'
@@ -19,6 +19,7 @@
<FormButton
v-tippy="'Toggle coloring'"
color="subtle"
size="sm"
hide-text
:icon-right="colors ? 'IconColouring' : 'IconColouringOutline'"
@click="toggleColors()"
@@ -52,7 +52,7 @@
}`"
@click.stop="hideOrShowObject"
> -->
<EyeSlashIcon v-if="isHidden" class="h-3 w-3" />
<EyeClosed v-if="isHidden" class="h-3 w-3" />
<!-- </button>
<button
:class="`hover:text-primary px-1 py-2 opacity-0 transition group-hover:opacity-100 ${
@@ -60,8 +60,8 @@
}`"
@click.stop="isolateOrUnisolateObject"
> -->
<FunnelIconOutline v-if="!isIsolated" class="h-3 w-3" />
<FunnelIcon v-else class="h-3 w-3" />
<FunnelX v-if="!isIsolated" class="h-3 w-3" />
<Funnel v-else class="h-3 w-3" />
<!-- </button> -->
</div>
</button>
@@ -72,11 +72,10 @@
</div>
</template>
<script setup lang="ts">
import { EyeSlashIcon, FunnelIcon } from '@heroicons/vue/24/solid'
import { FunnelIcon as FunnelIconOutline } from '@heroicons/vue/24/outline'
import { containsAll, hasIntersection } from '~~/lib/common/helpers/utils'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import { useSelectionUtilities } from '~~/lib/viewer/composables/ui'
import { EyeClosed, Funnel, FunnelX } from 'lucide-vue-next'
const props = defineProps<{
item: {
@@ -1,37 +1,40 @@
<template>
<div class="flex">
<div v-if="!hideVersions" v-tippy="'Versions'" class="flex">
<button
class="group-hover:opacity-100 hover:bg-highlight-3 rounded-md h-6 w-6 flex items-center justify-center shrink-0"
<FormButton
:icon-left="History"
hide-text
size="sm"
color="subtle"
@click="$emit('showVersions')"
>
<IconVersions class="w-4 h-4" />
<span class="sr-only">Versions</span>
</button>
Versions
</FormButton>
</div>
<div v-if="!hideAddModel" v-tippy="'Add model'" class="flex">
<button
class="group-hover:opacity-100 hover:bg-highlight-3 rounded-md h-6 w-6 flex items-center justify-center shrink-0"
<div v-tippy="'Add model'" class="flex">
<FormButton
:icon-left="Plus"
hide-text
size="sm"
color="subtle"
@click="$emit('addModel')"
>
<IconPlus class="w-4 h-4" />
<span class="sr-only">Add model</span>
</button>
Add model
</FormButton>
</div>
</div>
</template>
<script setup lang="ts">
import { FormButton } from '@speckle/ui-components'
import { Plus, History } from 'lucide-vue-next'
defineProps<{
hideVersions?: boolean
hideAddModel?: boolean
}>()
defineEmits<{
showVersions: []
addModel: []
}>()
const IconPlus = resolveComponent('IconPlus')
const IconVersions = resolveComponent('IconVersions')
</script>
@@ -56,8 +56,8 @@
</div>
</div>
<div
class="flex items-center ml-auto mr-2 w-0 group-hover:w-auto"
:class="showActionsMenu ? '!w-auto' : ''"
class="flex items-center ml-auto mr-2 w-0 group-hover:w-auto opacity-0 group-hover:opacity-100 transition"
:class="showActionsMenu ? '!w-auto !opacity-100' : ''"
>
<LayoutMenu
v-model:open="showActionsMenu"
@@ -66,16 +66,16 @@
@click.stop.prevent
@chosen="onActionChosen"
>
<button
class="group-hover:opacity-100 hover:bg-highlight-3 rounded-md h-6 w-6 flex items-center justify-center"
<FormButton
hide-text
:class="{
'opacity-100 bg-highlight-3': showActionsMenu,
'sm:opacity-0': !showActionsMenu
'!bg-highlight-3': showActionsMenu
}"
@click.stop="showActionsMenu = !showActionsMenu"
>
<IconThreeDots class="w-4 h-4" />
</button>
color="subtle"
:icon-left="Ellipsis"
size="sm"
@click="showActionsMenu = !showActionsMenu"
/>
</LayoutMenu>
<ViewerVisibilityButton
:is-hidden="isHidden"
@@ -113,6 +113,7 @@ import { getTargetObjectIds } from '~~/lib/object-sidebar/helpers'
import { useLoadLatestVersion } from '~~/lib/viewer/composables/resources'
import { SpeckleViewer } from '@speckle/shared'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { Ellipsis } from 'lucide-vue-next'
type ModelItem = NonNullable<Get<ViewerLoadedResourcesQuery, 'project.models.items[0]'>>
@@ -3,7 +3,7 @@
<template #title>
<div class="flex items-center gap-x-1">
<FormButton
:icon-left="ChevronLeftIcon"
:icon-left="ChevronLeft"
color="subtle"
class="-ml-3"
hide-text
@@ -58,7 +58,7 @@ import { SpeckleViewer } from '@speckle/shared'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { ViewerEvent } from '@speckle/viewer'
import { useViewerEventListener } from '~~/lib/viewer/composables/viewer'
import { ChevronLeftIcon } from '@heroicons/vue/24/solid'
import { ChevronLeft } from 'lucide-vue-next'
import { useDiffUtilities } from '~~/lib/viewer/composables/ui'
const props = defineProps<{
@@ -1,7 +1,7 @@
<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
<template>
<div
class="group relative w-full rounded-md pb-2 text-left pl-5 pt-2"
class="group relative w-full rounded-md text-left pl-5 pt-1 pb-2"
:class="
clickable && !isLimited ? 'hover:bg-highlight-1 cursor-pointer' : 'cursor-default'
"
@@ -11,14 +11,14 @@
<!-- Timeline left border -->
<div
v-if="showTimeline"
class="absolute top-5 left-4 z-10 ml-[2px] w-1 border-l border-outline-3"
class="absolute top-4 left-4 z-10 ml-[2px] mt-[2px] w-1 border-l border-outline-3"
:class="last ? 'h-0' : 'h-[99%]'"
>
<div
v-if="isLoaded"
class="absolute -top-2.5 -left-2 flex items-center justify-center h-4 w-4 bg-foundation-2 rounded-full"
class="absolute -top-1.5 -left-2 flex items-center justify-center h-4 w-4 bg-foundation-2 rounded-full"
>
<IconCheck class="h-4 w-4 text-foreground" />
<Check class="h-3 w-3 text-foreground" />
</div>
<div
v-else
@@ -37,25 +37,29 @@
</span>
</div>
<CommonBadge v-if="isLoaded" rounded>Viewing</CommonBadge>
<LayoutMenu
v-model:open="showActionsMenu"
class="ml-auto mr-2"
:items="actionsItems"
:menu-position="HorizontalDirection.Left"
mount-menu-on-body
@click.stop.prevent
@chosen="onActionChosen"
>
<button
class="opacity-0 group-hover:opacity-100 hover:bg-highlight-3 rounded-md h-5 w-5 flex items-center justify-center shrink-0"
@click.stop="showActionsMenu = !showActionsMenu"
<div class="ml-auto mr-2 mt-0.5">
<LayoutMenu
v-model:open="showActionsMenu"
:items="actionsItems"
:menu-position="HorizontalDirection.Left"
mount-menu-on-body
@click.stop.prevent
@chosen="onActionChosen"
>
<IconThreeDots />
</button>
</LayoutMenu>
<FormButton
hide-text
color="subtle"
:icon-left="Ellipsis"
size="sm"
@click.stop="showActionsMenu = !showActionsMenu"
>
Menu
</FormButton>
</LayoutMenu>
</div>
</div>
<!-- Main stuff -->
<div class="flex items-center pl-5 gap-2 mt-2">
<div class="flex items-center pl-5 gap-2 mt-1">
<div
class="bg-foundation h-12 w-12 flex-shrink-0 rounded-md border border-outline-3"
:class="isLimited ? 'diagonal-stripes' : ''"
@@ -64,7 +68,7 @@
<div
class="flex h-8 w-8 items-center justify-center rounded-md bg-foundation border border-outline-3"
>
<LockClosedIcon class="h-4 w-4 text-foreground-3" />
<Lock class="h-4 w-4 text-foreground-3" />
</div>
</div>
<PreviewImage v-else :preview-url="version.previewUrl" />
@@ -91,7 +95,6 @@
</div>
</template>
<script setup lang="ts">
import { LockClosedIcon } from '@heroicons/vue/24/solid'
import { CommonBadge, keyboardClick } from '@speckle/ui-components'
import dayjs from 'dayjs'
import localizedFormat from 'dayjs/plugin/localizedFormat'
@@ -101,6 +104,7 @@ import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
import { HorizontalDirection } from '~~/lib/common/composables/window'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useCopyModelLink } from '~/lib/projects/composables/modelManagement'
import { Ellipsis, Check, Lock } from 'lucide-vue-next'
dayjs.extend(localizedFormat)
@@ -139,8 +143,6 @@ const {
} = useInjectedViewerState()
const copyModelLink = useCopyModelLink()
const IconThreeDots = resolveComponent('IconThreeDots')
const isLoaded = computed(() => props.isLoadedVersion)
const isLatest = computed(() => props.isLatestVersion)
@@ -158,7 +160,6 @@ const createdAt = computed(() => {
const author = computed(() => props.version.authorUser)
const IconCheck = resolveComponent('IconCheck')
const showActionsMenu = ref(false)
const canDeleteVersion = computed(() => {
@@ -13,7 +13,7 @@
>
<template #title>
<div class="flex items-center gap-x-2">
<p>Selected</p>
<span>Selected</span>
<CommonBadge v-if="objects.length > 1" rounded>
{{ objects.length }}
</CommonBadge>
@@ -21,30 +21,16 @@
</template>
<template #actions>
<div class="flex gap-x-0.5 items-center">
<div
v-tippy="getTooltipProps(isHidden ? 'Show' : 'Hide', { placement: 'top' })"
>
<FormButton
color="subtle"
:icon-left="isHidden ? iconEyeClosed : iconEye"
hide-text
@click.stop="hideOrShowSelection"
/>
</div>
<div
v-tippy="
getTooltipProps(isIsolated ? 'Unisolate' : 'Isolate', {
placement: 'top'
})
"
>
<FormButton
color="subtle"
:icon-left="isIsolated ? iconViewerUnisolate : iconViewerIsolate"
hide-text
@click.stop="isolateOrUnisolateSelection"
/>
</div>
<ViewerVisibilityButton
:is-hidden="isHidden"
:force-visible="showSubMenu"
@click="hideOrShowSelection"
/>
<ViewerIsolateButton
:is-isolated="isIsolated"
:force-visible="showSubMenu"
@click="isolateOrUnisolateSelection"
/>
<LayoutMenu
v-model:open="showSubMenu"
:menu-id="menuId"
@@ -56,7 +42,11 @@
<FormButton
hide-text
color="subtle"
:icon-left="settingsIcon"
size="sm"
:icon-left="Ellipsis"
:class="{
'!bg-highlight-3': showSubMenu
}"
@click="showSubMenu = !showSubMenu"
/>
</LayoutMenu>
@@ -97,8 +87,8 @@ import { useMixpanel } from '~~/lib/core/composables/mp'
import { useIsSmallerOrEqualThanBreakpoint } from '~~/composables/browser'
import { modelRoute } from '~/lib/common/helpers/route'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import type { ConcreteComponent } from 'vue'
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
import { Ellipsis } from 'lucide-vue-next'
enum ActionTypes {
OpenInNewTab = 'open-in-new-tab'
@@ -121,17 +111,11 @@ const breakpoints = useBreakpoints(TailwindBreakpoints)
const isGreaterThanSm = breakpoints.greater('sm')
const menuId = useId()
const mp = useMixpanel()
const { getTooltipProps } = useSmartTooltipDelay()
const itemCount = ref(20)
const sidebarOpen = ref(false)
const sidebarWidth = ref(280)
const showSubMenu = ref(false)
const iconViewerUnisolate = resolveComponent('IconViewerUnisolate') as ConcreteComponent
const iconViewerIsolate = resolveComponent('IconViewerIsolate') as ConcreteComponent
const iconEyeClosed = resolveComponent('IconEyeClosed') as ConcreteComponent
const iconEye = resolveComponent('IconEye') as ConcreteComponent
const settingsIcon = resolveComponent('IconThreeDots') as ConcreteComponent
const objectsUniqueByAppId = computed(() => {
if (!diff.enabled.value) return objects.value
@@ -270,7 +270,12 @@ const iconClasses = computed(() => {
switch (props.size) {
case 'sm':
classParts.push('h-4 w-4 p-0.5')
// Dont add padding to icon if hideText is true on sm size
if (props.hideText) {
classParts.push('h-4 w-4')
} else {
classParts.push('h-4 w-4 p-0.5')
}
break
case 'lg':
classParts.push('h-6 w-6 p-1')