feat: saved views search (#5266)
* feat: saved view search * caching fixes * clean up chromatic
This commit is contained in:
committed by
GitHub
parent
028e4b713e
commit
79ccd28828
@@ -117,28 +117,6 @@ jobs:
|
||||
run: yarn build
|
||||
working-directory: 'packages/viewer-sandbox'
|
||||
|
||||
ui-components-chromatic:
|
||||
env:
|
||||
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
name: UI components chromatic
|
||||
runs-on: blacksmith
|
||||
continue-on-error: ${{ inputs.CONTINUE_ON_ERROR }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: useblacksmith/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: YARN_ENABLE_HARDENED_MODE=0 PUPPETEER_SKIP_DOWNLOAD=true PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn --immutable
|
||||
- name: Build public packages
|
||||
run: yarn build:public
|
||||
- name: Run chromatic
|
||||
run: yarn chromatic
|
||||
working-directory: 'packages/ui-components'
|
||||
|
||||
test-shared:
|
||||
name: Shared
|
||||
runs-on: blacksmith
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
<div
|
||||
class="flex shrink-0 justify-between items-center border-b border-outline-3 h-10 pl-4 pr-2.5"
|
||||
>
|
||||
<div class="text-body-xs text-foreground font-medium leading-none">
|
||||
<span v-if="title" class="truncate">{{ title }}</span>
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<slot name="actions"></slot>
|
||||
<slot name="fullTitle">
|
||||
<div class="text-body-xs text-foreground font-medium leading-none">
|
||||
<span v-if="title" class="truncate">{{ title }}</span>
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<slot name="actions"></slot>
|
||||
</slot>
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
<template #actions>
|
||||
<div v-if="!isLowerPlan" class="flex items-center gap-0.5">
|
||||
<FormButton
|
||||
v-if="false"
|
||||
size="sm"
|
||||
color="subtle"
|
||||
:icon-left="Search"
|
||||
hide-text
|
||||
@click="setSearchMode(true)"
|
||||
/>
|
||||
<div v-tippy="canCreateViewOrGroup?.errorMessage" class="flex items-center">
|
||||
<FormButton
|
||||
@@ -38,6 +38,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="searchMode" #fullTitle>
|
||||
<div class="self-center w-full pr-2 flex gap-2 items-center">
|
||||
<FormTextInput
|
||||
v-bind="bind"
|
||||
name="search"
|
||||
placeholder="Search"
|
||||
color="foundation"
|
||||
auto-focus
|
||||
v-on="on"
|
||||
/>
|
||||
<FormButton
|
||||
v-tippy="'Exit search'"
|
||||
size="sm"
|
||||
color="subtle"
|
||||
:icon-left="X"
|
||||
hide-text
|
||||
name="disableSearch"
|
||||
@click="setSearchMode(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!isLowerPlan">
|
||||
<div class="px-4 pt-2">
|
||||
<ViewerButtonGroup>
|
||||
@@ -58,6 +79,7 @@
|
||||
<ViewerSavedViewsPanelGroups
|
||||
v-model:selected-group-id="selectedGroupId"
|
||||
:views-type="selectedViewsType"
|
||||
:search="searchMode ? search || undefined : undefined"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -78,7 +100,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useMutationLoading } from '@vue/apollo-composable'
|
||||
import { Search, FolderPlus, Plus } from 'lucide-vue-next'
|
||||
import { Search, FolderPlus, Plus, X } from 'lucide-vue-next'
|
||||
import { useSynchronizedCookie } from '~/lib/common/composables/reactiveCookie'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
@@ -91,6 +113,7 @@ import {
|
||||
} from '~/lib/viewer/composables/savedViews/management'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
import { ViewsType, viewsTypeLabels } from '~/lib/viewer/helpers/savedViews'
|
||||
import { useDebouncedTextInput } from '@speckle/ui-components'
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanel_Project on Project {
|
||||
@@ -122,6 +145,7 @@ const {
|
||||
const createGroup = useCreateSavedViewGroup()
|
||||
const createSavedView = useCreateSavedView()
|
||||
const isLoading = useMutationLoading()
|
||||
const { on, bind, value: search } = useDebouncedTextInput()
|
||||
|
||||
const selectedViewsType = ref<ViewsType>(ViewsType.Personal)
|
||||
const selectedGroupId = ref<string | null>(null)
|
||||
@@ -131,6 +155,7 @@ const hideViewerSeatDisclaimer = useSynchronizedCookie<boolean>(
|
||||
default: () => false
|
||||
}
|
||||
)
|
||||
const searchMode = ref(false)
|
||||
|
||||
const canCreateViewOrGroup = computed(
|
||||
() => project.value?.permissions.canCreateSavedView
|
||||
@@ -165,4 +190,14 @@ const onAddGroup = async () => {
|
||||
selectedGroupId.value = group.id
|
||||
}
|
||||
}
|
||||
|
||||
const setSearchMode = (val: boolean) => {
|
||||
if (val) {
|
||||
searchMode.value = true
|
||||
} else {
|
||||
searchMode.value = false
|
||||
}
|
||||
|
||||
search.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
:views-type="viewsType"
|
||||
:group="group"
|
||||
:project="project"
|
||||
:search="search"
|
||||
:is-selected="isGroupSelected(group)"
|
||||
:rename-mode="isGroupInRenameMode(group)"
|
||||
@update:is-selected="(value) => (selectedGroupId = value ? group.id : null)"
|
||||
@@ -92,6 +93,7 @@ const paginableGroupsQuery = graphql(`
|
||||
|
||||
const props = defineProps<{
|
||||
viewsType: ViewsType
|
||||
search?: string
|
||||
}>()
|
||||
|
||||
const selectedGroupId = defineModel<string | null>('selectedGroupId', {
|
||||
@@ -106,7 +108,6 @@ const {
|
||||
} = useInjectedViewerState()
|
||||
const eventBus = useEventBus()
|
||||
|
||||
const search = ref('')
|
||||
const viewBeingEdited = ref<ViewerSavedViewsPanelViewEditDialog_SavedViewFragment>()
|
||||
const viewBeingMoved = ref<ViewerSavedViewsPanelViewMoveDialog_SavedViewFragment>()
|
||||
const viewBeingDeleted = ref<ViewerSavedViewsPanelViewDeleteDialog_SavedViewFragment>()
|
||||
@@ -126,7 +127,7 @@ const {
|
||||
savedViewGroupsInput: {
|
||||
resourceIdString: resourceIdString.value,
|
||||
cursor: null as null | string,
|
||||
search: search.value?.trim() || null,
|
||||
search: props.search?.trim() || null,
|
||||
...viewsTypeToFilters(props.viewsType)
|
||||
}
|
||||
})),
|
||||
@@ -148,7 +149,7 @@ const {
|
||||
const hasGroups = computed(
|
||||
() => (result.value?.project.savedViewGroups.items.length || 0) > 0
|
||||
)
|
||||
const isSearch = computed(() => search.value?.trim().length > 0)
|
||||
const isSearch = computed(() => (props.search || '').trim().length > 0)
|
||||
const emptyStateType = computed(() => (isSearch.value ? 'search' : 'base'))
|
||||
|
||||
const project = computed(() => result.value?.project)
|
||||
@@ -206,8 +207,10 @@ watch(
|
||||
groups,
|
||||
(newGroups) => {
|
||||
if (newGroups.length && !selectedGroupId.value) {
|
||||
// open default group, if any
|
||||
selectedGroupId.value = newGroups.find((g) => g.isUngroupedViewsGroup)?.id || null
|
||||
selectedGroupId.value =
|
||||
(props.search
|
||||
? newGroups[0].id
|
||||
: newGroups.find((g) => !g.isUngroupedViewsGroup)?.id) || null
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
import { isRequired, isStringOfLength } from '~/lib/common/helpers/validation'
|
||||
import { useUpdateSavedView } from '~/lib/viewer/composables/savedViews/management'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
import { isUndefined } from 'lodash-es'
|
||||
|
||||
graphql(`
|
||||
fragment ViewerSavedViewsPanelViewEditDialog_SavedView on SavedView {
|
||||
@@ -128,22 +129,30 @@ const onSubmit = handleSubmit(async (values) => {
|
||||
const name =
|
||||
values.name.trim() && values.name.trim() !== props.view.name
|
||||
? values.name.trim()
|
||||
: null
|
||||
: undefined
|
||||
const description =
|
||||
values.description?.trim() !== (props.view.description || undefined)
|
||||
? values.description?.trim() || null
|
||||
: null
|
||||
: undefined
|
||||
const visibility =
|
||||
values.visibility !== props.view.visibility ? values.visibility : null
|
||||
const groupId = values.group.id !== props.view.group.id ? values.group.id : null
|
||||
values.visibility !== props.view.visibility ? values.visibility : undefined
|
||||
const groupId = values.group.id !== props.view.group.id ? values.group.id : undefined
|
||||
|
||||
const coreInput = {
|
||||
...(isUndefined(name) ? {} : { name }),
|
||||
...(isUndefined(description) ? {} : { description }),
|
||||
...(isUndefined(visibility) ? {} : { visibility }),
|
||||
...(isUndefined(groupId) ? {} : { groupId })
|
||||
}
|
||||
if (!Object.keys(coreInput).length) {
|
||||
open.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const res = await updateView({
|
||||
view: props.view,
|
||||
input: {
|
||||
name,
|
||||
description,
|
||||
visibility,
|
||||
groupId,
|
||||
...coreInput,
|
||||
id: props.view.id,
|
||||
projectId: props.view.projectId
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-8 items-center my-16">
|
||||
<IllustrationEmptystateViewsTab />
|
||||
<div class="text-foreground-2">{{ message }}</div>
|
||||
<div class="text-foreground-2 px-6">{{ message }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -699,8 +699,7 @@ export const modifyObjectField = <
|
||||
'filter',
|
||||
'search',
|
||||
'filter.search',
|
||||
'filter.onlyAuthored',
|
||||
'filter.onlyVisibility',
|
||||
'input.search',
|
||||
...(isArray(autoEvictFiltered) ? autoEvictFiltered : [])
|
||||
]
|
||||
const hasFilter = commonFilters.some(checkFilter)
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
import { useStateSerialization } from '~/lib/viewer/composables/serialization'
|
||||
import { useInjectedViewerState } from '~/lib/viewer/composables/setup'
|
||||
import {
|
||||
filterKeys,
|
||||
onGroupViewRemovalCacheUpdates,
|
||||
onNewGroupViewCacheUpdates
|
||||
} from '~/lib/viewer/helpers/savedViews/cache'
|
||||
@@ -349,7 +350,7 @@ export const useCreateSavedViewGroup = () => {
|
||||
return newItems
|
||||
})
|
||||
}),
|
||||
{ autoEvictFiltered: true }
|
||||
{ autoEvictFiltered: filterKeys }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -425,7 +426,7 @@ export const useDeleteSavedViewGroup = () => {
|
||||
return newItems
|
||||
})
|
||||
}),
|
||||
{ autoEvictFiltered: true }
|
||||
{ autoEvictFiltered: filterKeys }
|
||||
)
|
||||
|
||||
// Possibly a bunch of views got moved back to Ungrouped as well
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import type { ApolloCache } from '@apollo/client/cache'
|
||||
import { isUngroupedGroup } from '@speckle/shared/saved-views'
|
||||
|
||||
export const filterKeys = [
|
||||
'input.search',
|
||||
'input.onlyAuthored',
|
||||
'input.onlyVisibility',
|
||||
'filter.search',
|
||||
'filter.onlyAuthored',
|
||||
'filter.onlyVisibility'
|
||||
]
|
||||
|
||||
/**
|
||||
* Cache mutations for when a group gets a new view:
|
||||
* - If new group, Project.savedViewGroups + 1
|
||||
@@ -36,7 +45,7 @@ export const onNewGroupViewCacheUpdates = (
|
||||
update('items', (items) => [...items, ref('SavedViewGroup', groupId)])
|
||||
})
|
||||
},
|
||||
{ autoEvictFiltered: true }
|
||||
{ autoEvictFiltered: filterKeys }
|
||||
)
|
||||
|
||||
// SavedViewGroup.views + 1
|
||||
@@ -50,7 +59,7 @@ export const onNewGroupViewCacheUpdates = (
|
||||
update('items', (items) => [ref('SavedView', viewId), ...items])
|
||||
})
|
||||
},
|
||||
{ autoEvictFiltered: true }
|
||||
{ autoEvictFiltered: filterKeys }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -115,7 +124,7 @@ export const onGroupViewRemovalCacheUpdates = (
|
||||
)
|
||||
})
|
||||
},
|
||||
{ autoEvictFiltered: true }
|
||||
{ autoEvictFiltered: filterKeys }
|
||||
)
|
||||
|
||||
// Evict entirely
|
||||
@@ -133,7 +142,7 @@ export const onGroupViewRemovalCacheUpdates = (
|
||||
update('items', (items) => items.filter((item) => fromRef(item).id !== id))
|
||||
})
|
||||
},
|
||||
{ autoEvictFiltered: true }
|
||||
{ autoEvictFiltered: filterKeys }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,6 @@
|
||||
"@typescript-eslint/parser": "^7.12.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"chromatic": "^6.11.4",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"storybook:test": "test-storybook",
|
||||
"storybook:test:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"yarn build-storybook --quiet && npx http-server storybook-static --port 6006 --silent\" \"wait-on tcp:6006 && yarn test-storybook --ci\"",
|
||||
"storybook:test:watch": "test-storybook --watch",
|
||||
"chromatic": "chromatic --exit-zero-on-changes --exit-once-uploaded",
|
||||
"lint:js": "eslint .",
|
||||
"lint:tsc": "vue-tsc --noEmit",
|
||||
"lint:prettier": "prettier --config ../../.prettierrc --ignore-path ../../.prettierignore --check .",
|
||||
@@ -80,7 +79,6 @@
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"chromatic": "^6.17.4",
|
||||
"concurrently": "^8.0.1",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
|
||||
Reference in New Issue
Block a user