feat: first time user spotlight animation (#3977)
This commit is contained in:
@@ -175,6 +175,7 @@
|
|||||||
"different_parent": "Cannot reorder collection with different parent",
|
"different_parent": "Cannot reorder collection with different parent",
|
||||||
"edit": "Edit Collection",
|
"edit": "Edit Collection",
|
||||||
"import_or_create": "Import or create a collection",
|
"import_or_create": "Import or create a collection",
|
||||||
|
"import_collection":"Import Collection",
|
||||||
"invalid_name": "Please provide a name for the collection",
|
"invalid_name": "Please provide a name for the collection",
|
||||||
"invalid_root_move": "Collection already in the root",
|
"invalid_root_move": "Collection already in the root",
|
||||||
"moved": "Moved Successfully",
|
"moved": "Moved Successfully",
|
||||||
@@ -849,6 +850,13 @@
|
|||||||
"new": "Create new workspace",
|
"new": "Create new workspace",
|
||||||
"switch_to_personal": "Switch to your personal workspace",
|
"switch_to_personal": "Switch to your personal workspace",
|
||||||
"title": "Workspaces"
|
"title": "Workspaces"
|
||||||
|
},
|
||||||
|
"phrases":{
|
||||||
|
"try": "Try",
|
||||||
|
"import_collections": "Import collections",
|
||||||
|
"create_environment": "Create environment",
|
||||||
|
"create_workspace": "Create workspace",
|
||||||
|
"share_request": "Share request"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sse": {
|
"sse": {
|
||||||
|
|||||||
@@ -21,19 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-1 flex items-center justify-between space-x-2">
|
<div class="col-span-1 flex items-center justify-between space-x-2">
|
||||||
<button
|
<AppSpotlightSearch />
|
||||||
class="flex h-full flex-1 cursor-text items-center justify-between self-stretch rounded border border-dividerDark bg-primaryDark px-2 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
|
|
||||||
@click="invokeAction('modals.search.toggle', undefined, 'mouseclick')"
|
|
||||||
>
|
|
||||||
<span class="inline-flex flex-1 items-center">
|
|
||||||
<icon-lucide-search class="svg-icons mr-2" />
|
|
||||||
{{ t("app.search") }}
|
|
||||||
</span>
|
|
||||||
<span class="flex space-x-1">
|
|
||||||
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
|
|
||||||
<kbd class="shortcut-key">K</kbd>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2 flex items-center justify-between space-x-2">
|
<div class="col-span-2 flex items-center justify-between space-x-2">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -251,7 +239,6 @@ import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core"
|
|||||||
import { computed, reactive, ref, watch } from "vue"
|
import { computed, reactive, ref, watch } from "vue"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery, TeamMemberRole } from "~/helpers/backend/graphql"
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import IconDownload from "~icons/lucide/download"
|
import IconDownload from "~icons/lucide/download"
|
||||||
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
import IconLifeBuoy from "~icons/lucide/life-buoy"
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="border-animation relative p-[1px] rounded flex-1 self-stretch overflow-hidden flex items-center justify-center"
|
||||||
|
:class="{
|
||||||
|
'before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:aspect-square before:w-full before:absolute before:bg-':
|
||||||
|
!HAS_OPENED_SPOTLIGHT,
|
||||||
|
}"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="relative flex flex-1 cursor-text items-center justify-between self-stretch rounded bg-primaryDark px-2 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary overflow-hidden"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
invokeAction('modals.search.toggle', undefined, 'mouseclick')
|
||||||
|
!HAS_OPENED_SPOTLIGHT && toggleSetting('HAS_OPENED_SPOTLIGHT')
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span class="inline-flex flex-1 items-center">
|
||||||
|
<icon-lucide-search class="svg-icons mr-2" />
|
||||||
|
<span v-if="!HAS_OPENED_SPOTLIGHT" class="flex flex-1">
|
||||||
|
{{ t("spotlight.phrases.try") }}
|
||||||
|
<TransitionGroup tag="div" name="list" class="ml-1 relative">
|
||||||
|
<span
|
||||||
|
v-for="(phrase, index) in phraseToShow"
|
||||||
|
:key="phrase.text"
|
||||||
|
:data-index="index"
|
||||||
|
class="truncate"
|
||||||
|
>
|
||||||
|
"{{ t(phrase.text) }}"
|
||||||
|
</span>
|
||||||
|
</TransitionGroup>
|
||||||
|
</span>
|
||||||
|
<template v-else>
|
||||||
|
{{ t("app.search") }}
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
<span class="flex space-x-1">
|
||||||
|
<kbd class="shortcut-key">{{ getPlatformSpecialKey() }}</kbd>
|
||||||
|
<kbd class="shortcut-key">K</kbd>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { watch, computed, ref } from "vue"
|
||||||
|
import { useI18n } from "~/composables/i18n"
|
||||||
|
import { useSetting } from "~/composables/settings"
|
||||||
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import { toggleSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
|
||||||
|
const HAS_OPENED_SPOTLIGHT = useSetting("HAS_OPENED_SPOTLIGHT")
|
||||||
|
|
||||||
|
const phrases = ref([
|
||||||
|
{ text: "spotlight.phrases.import_collections", show: true },
|
||||||
|
{ text: "spotlight.phrases.create_environment", show: false },
|
||||||
|
{ text: "spotlight.phrases.create_workspace", show: false },
|
||||||
|
{ text: "spotlight.phrases.share_request", show: false },
|
||||||
|
])
|
||||||
|
|
||||||
|
let intervalId: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
//cycle through the phrases
|
||||||
|
const showNextPhrase = () => {
|
||||||
|
let i = 0
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
phrases.value[i].show = false
|
||||||
|
i++
|
||||||
|
if (i >= phrases.value.length) {
|
||||||
|
i = 0
|
||||||
|
}
|
||||||
|
phrases.value[i].show = true
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopPhraseInterval = () => {
|
||||||
|
if (intervalId) clearInterval(intervalId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const phraseToShow = computed(() => {
|
||||||
|
return phrases.value.filter((phrase) => phrase.show)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
HAS_OPENED_SPOTLIGHT,
|
||||||
|
() => {
|
||||||
|
!HAS_OPENED_SPOTLIGHT.value ? showNextPhrase() : stopPhraseInterval()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Transition Classes */
|
||||||
|
.list-enter-active {
|
||||||
|
transition: all 1s ease;
|
||||||
|
}
|
||||||
|
.list-leave-active {
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
}
|
||||||
|
.list-enter-from,
|
||||||
|
.list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conic gradient */
|
||||||
|
.border-animation::before {
|
||||||
|
background: conic-gradient(
|
||||||
|
transparent 270deg,
|
||||||
|
var(--accent-color),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
animation: rotate 4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: translate(-50%, -50%) scale(1.4) rotate(0turn);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-50%, -50%) scale(1.4) rotate(1turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -192,6 +192,7 @@ import { PersistenceService } from "~/services/persistence"
|
|||||||
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
import { PersistedOAuthConfig } from "~/services/oauth/oauth.service"
|
||||||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||||
import { EditingProperties } from "../Properties.vue"
|
import { EditingProperties } from "../Properties.vue"
|
||||||
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -676,4 +677,11 @@ const resetSelectedData = () => {
|
|||||||
editingRequest.value = null
|
editingRequest.value = null
|
||||||
editingRequestIndex.value = null
|
editingRequestIndex.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineActionHandler("collection.new", () => {
|
||||||
|
displayModalAdd(true)
|
||||||
|
})
|
||||||
|
defineActionHandler("modals.collection.import", () => {
|
||||||
|
displayModalImportExport(true)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2351,4 +2351,7 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
defineActionHandler("collection.new", () => {
|
defineActionHandler("collection.new", () => {
|
||||||
displayModalAdd(true)
|
displayModalAdd(true)
|
||||||
})
|
})
|
||||||
|
defineActionHandler("modals.collection.import", () => {
|
||||||
|
displayModalImportExport(true)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type HoppAction =
|
|||||||
| "collection.new" // Create root collection
|
| "collection.new" // Create root collection
|
||||||
| "flyouts.chat.open" // Shows the keybinds flyout
|
| "flyouts.chat.open" // Shows the keybinds flyout
|
||||||
| "flyouts.keybinds.toggle" // Shows the keybinds flyout
|
| "flyouts.keybinds.toggle" // Shows the keybinds flyout
|
||||||
|
| "modals.collection.import" // Shows the collection import modal
|
||||||
| "modals.search.toggle" // Shows the search modal
|
| "modals.search.toggle" // Shows the search modal
|
||||||
| "modals.support.toggle" // Shows the support modal
|
| "modals.support.toggle" // Shows the support modal
|
||||||
| "modals.share.toggle" // Shows the share modal
|
| "modals.share.toggle" // Shows the share modal
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ import { useI18n } from "~/composables/i18n"
|
|||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { InvocationTriggers, defineActionHandler } from "~/helpers/actions"
|
import { InvocationTriggers, defineActionHandler } from "~/helpers/actions"
|
||||||
import { hookKeybindingsListener } from "~/helpers/keybindings"
|
import { hookKeybindingsListener } from "~/helpers/keybindings"
|
||||||
import { applySetting } from "~/newstore/settings"
|
import { applySetting, toggleSetting } from "~/newstore/settings"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { HoppSpotlightSessionEventData } from "~/platform/analytics"
|
import { HoppSpotlightSessionEventData } from "~/platform/analytics"
|
||||||
import { PersistenceService } from "~/services/persistence"
|
import { PersistenceService } from "~/services/persistence"
|
||||||
@@ -97,6 +97,8 @@ const t = useI18n()
|
|||||||
const persistenceService = useService(PersistenceService)
|
const persistenceService = useService(PersistenceService)
|
||||||
const spotlightService = useService(SpotlightService)
|
const spotlightService = useService(SpotlightService)
|
||||||
|
|
||||||
|
const HAS_OPENED_SPOTLIGHT = useSetting("HAS_OPENED_SPOTLIGHT")
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
if (!mdAndLarger.value) {
|
if (!mdAndLarger.value) {
|
||||||
rightSidebar.value = false
|
rightSidebar.value = false
|
||||||
@@ -160,6 +162,7 @@ defineActionHandler("modals.search.toggle", (_, trigger) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
showSearch.value = !showSearch.value
|
showSearch.value = !showSearch.value
|
||||||
|
!HAS_OPENED_SPOTLIGHT.value && toggleSetting("HAS_OPENED_SPOTLIGHT")
|
||||||
})
|
})
|
||||||
|
|
||||||
defineActionHandler("modals.support.toggle", () => {
|
defineActionHandler("modals.support.toggle", () => {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ export type SettingsDef = {
|
|||||||
SIDEBAR: boolean
|
SIDEBAR: boolean
|
||||||
SIDEBAR_ON_LEFT: boolean
|
SIDEBAR_ON_LEFT: boolean
|
||||||
COLUMN_LAYOUT: boolean
|
COLUMN_LAYOUT: boolean
|
||||||
|
|
||||||
|
HAS_OPENED_SPOTLIGHT: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDefaultSettings = (): SettingsDef => ({
|
export const getDefaultSettings = (): SettingsDef => ({
|
||||||
@@ -109,6 +111,8 @@ export const getDefaultSettings = (): SettingsDef => ({
|
|||||||
SIDEBAR: true,
|
SIDEBAR: true,
|
||||||
SIDEBAR_ON_LEFT: false,
|
SIDEBAR_ON_LEFT: false,
|
||||||
COLUMN_LAYOUT: true,
|
COLUMN_LAYOUT: true,
|
||||||
|
|
||||||
|
HAS_OPENED_SPOTLIGHT: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
type ApplySettingPayload = {
|
type ApplySettingPayload = {
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ const SettingsDefSchema = z.object({
|
|||||||
cookie: z.boolean().catch(true),
|
cookie: z.boolean().catch(true),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
|
HAS_OPENED_SPOTLIGHT: z.optional(z.boolean()),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Common properties shared across REST & GQL collections
|
// Common properties shared across REST & GQL collections
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
restCollectionStore,
|
restCollectionStore,
|
||||||
} from "~/newstore/collections"
|
} from "~/newstore/collections"
|
||||||
import IconFolder from "~icons/lucide/folder"
|
import IconFolder from "~icons/lucide/folder"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import RESTRequestSpotlightEntry from "~/components/app/spotlight/entry/RESTRequest.vue"
|
import RESTRequestSpotlightEntry from "~/components/app/spotlight/entry/RESTRequest.vue"
|
||||||
import GQLRequestSpotlightEntry from "~/components/app/spotlight/entry/GQLRequest.vue"
|
import GQLRequestSpotlightEntry from "~/components/app/spotlight/entry/GQLRequest.vue"
|
||||||
import {
|
import {
|
||||||
@@ -151,6 +152,10 @@ export class CollectionsSpotlightSearcherService
|
|||||||
id: `create-collection`,
|
id: `create-collection`,
|
||||||
name: this.t("collection.new"),
|
name: this.t("collection.new"),
|
||||||
})
|
})
|
||||||
|
minisearch.add({
|
||||||
|
id: "import-collection",
|
||||||
|
name: this.t("collection.import"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageCategory === "rest") {
|
if (pageCategory === "rest") {
|
||||||
@@ -168,6 +173,11 @@ export class CollectionsSpotlightSearcherService
|
|||||||
text: this.t("collection.new"),
|
text: this.t("collection.new"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const importCollectionText: SpotlightResultTextType<any> = {
|
||||||
|
type: "text",
|
||||||
|
text: this.t("collection.import_collection"),
|
||||||
|
}
|
||||||
|
|
||||||
scopeHandle.run(() => {
|
scopeHandle.run(() => {
|
||||||
const isPersonalWorkspace = computed(
|
const isPersonalWorkspace = computed(
|
||||||
() => this.workspaceService.currentWorkspace.value.type === "personal"
|
() => this.workspaceService.currentWorkspace.value.type === "personal"
|
||||||
@@ -183,44 +193,37 @@ export class CollectionsSpotlightSearcherService
|
|||||||
results.value = []
|
results.value = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const getResultText = (id: string): SpotlightResultTextType<any> => {
|
||||||
if (pageCategory === "rest") {
|
if (id === "create-collection") return newCollectionText
|
||||||
const searchResults = minisearch.search(query).slice(0, 10)
|
else if (id === "import-collection") return importCollectionText
|
||||||
|
return {
|
||||||
results.value = searchResults.map((result) => ({
|
type: "custom",
|
||||||
id: result.id,
|
component: markRaw(
|
||||||
text:
|
pageCategory === "rest"
|
||||||
result.id === "create-collection"
|
? RESTRequestSpotlightEntry
|
||||||
? newCollectionText
|
: GQLRequestSpotlightEntry
|
||||||
: {
|
),
|
||||||
type: "custom",
|
componentProps: {
|
||||||
component: markRaw(RESTRequestSpotlightEntry),
|
folderPath: id.split(
|
||||||
componentProps: {
|
pageCategory === "rest" ? "rest-" : "gql-"
|
||||||
folderPath: result.id.split("rest-")[1],
|
)[1],
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
icon: markRaw(IconFolder),
|
|
||||||
score: result.score,
|
|
||||||
}))
|
|
||||||
} else if (pageCategory === "graphql") {
|
|
||||||
const searchResults = minisearch.search(query).slice(0, 10)
|
|
||||||
|
|
||||||
results.value = searchResults.map((result) => ({
|
|
||||||
id: result.id,
|
|
||||||
text:
|
|
||||||
result.id === "create-collection"
|
|
||||||
? newCollectionText
|
|
||||||
: {
|
|
||||||
type: "custom",
|
|
||||||
component: markRaw(GQLRequestSpotlightEntry),
|
|
||||||
componentProps: {
|
|
||||||
folderPath: result.id.split("gql-")[1],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
icon: markRaw(IconFolder),
|
|
||||||
score: result.score,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getResultIcon = (id: string) => {
|
||||||
|
if (id === "import-collection") return markRaw(IconImport)
|
||||||
|
return markRaw(IconFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResults = minisearch.search(query).slice(0, 10)
|
||||||
|
|
||||||
|
results.value = searchResults.map((result) => ({
|
||||||
|
id: result.id,
|
||||||
|
text: getResultText(result.id),
|
||||||
|
icon: getResultIcon(result.id),
|
||||||
|
score: result.score,
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -288,6 +291,9 @@ export class CollectionsSpotlightSearcherService
|
|||||||
public onResultSelect(result: SpotlightSearcherResult): void {
|
public onResultSelect(result: SpotlightSearcherResult): void {
|
||||||
if (result.id === "create-collection") return invokeAction("collection.new")
|
if (result.id === "create-collection") return invokeAction("collection.new")
|
||||||
|
|
||||||
|
if (result.id === "import-collection")
|
||||||
|
return invokeAction(`modals.collection.import`)
|
||||||
|
|
||||||
const [type, path] = result.id.split("-")
|
const [type, path] = result.id.split("-")
|
||||||
|
|
||||||
if (type === "rest") {
|
if (type === "rest") {
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import { Service } from "dioc"
|
import { Service } from "dioc"
|
||||||
import {
|
import {
|
||||||
|
SpotlightResultTextType,
|
||||||
SpotlightSearcher,
|
SpotlightSearcher,
|
||||||
SpotlightSearcherResult,
|
SpotlightSearcherResult,
|
||||||
SpotlightSearcherSessionState,
|
SpotlightSearcherSessionState,
|
||||||
SpotlightService,
|
SpotlightService,
|
||||||
} from ".."
|
} from ".."
|
||||||
import { getI18n } from "~/modules/i18n"
|
import { getI18n } from "~/modules/i18n"
|
||||||
import { Ref, computed, effectScope, markRaw, watch } from "vue"
|
import { Ref, computed, effectScope, markRaw, ref, watch } from "vue"
|
||||||
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
import { TeamSearchService } from "~/helpers/teams/TeamsSearch.service"
|
||||||
import { cloneDeep, debounce } from "lodash-es"
|
import { cloneDeep, debounce } from "lodash-es"
|
||||||
import IconFolder from "~icons/lucide/folder"
|
import IconFolder from "~icons/lucide/folder"
|
||||||
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
import RESTTeamRequestEntry from "~/components/app/spotlight/entry/RESTTeamRequestEntry.vue"
|
import RESTTeamRequestEntry from "~/components/app/spotlight/entry/RESTTeamRequestEntry.vue"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
|
import MiniSearch from "minisearch"
|
||||||
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
|
||||||
export class TeamsSpotlightSearcherService
|
export class TeamsSpotlightSearcherService
|
||||||
extends Service
|
extends Service
|
||||||
@@ -41,9 +45,79 @@ export class TeamsSpotlightSearcherService
|
|||||||
this.spotlight.registerSearcher(this)
|
this.spotlight.registerSearcher(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCurrentPageCategory() {
|
||||||
|
// TODO: Better logic for this ?
|
||||||
|
try {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
|
||||||
|
if (url.pathname.startsWith("/graphql")) {
|
||||||
|
return "graphql"
|
||||||
|
} else if (url.pathname === "/") {
|
||||||
|
return "rest"
|
||||||
|
}
|
||||||
|
return "other"
|
||||||
|
} catch (e) {
|
||||||
|
return "other"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createSearchSession(
|
createSearchSession(
|
||||||
query: Readonly<Ref<string>>
|
query: Readonly<Ref<string>>
|
||||||
): [Ref<SpotlightSearcherSessionState>, () => void] {
|
): [Ref<SpotlightSearcherSessionState>, () => void] {
|
||||||
|
const pageCategory = this.getCurrentPageCategory()
|
||||||
|
|
||||||
|
// Only show the searcher on the REST page
|
||||||
|
if (pageCategory !== "rest") {
|
||||||
|
return [computed(() => ({ loading: false, results: [] })), () => {}]
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = ref<SpotlightSearcherResult[]>([])
|
||||||
|
|
||||||
|
const minisearch = new MiniSearch({
|
||||||
|
fields: ["name"],
|
||||||
|
storeFields: ["name"],
|
||||||
|
searchOptions: {
|
||||||
|
prefix: true,
|
||||||
|
fuzzy: true,
|
||||||
|
boost: {
|
||||||
|
name: 2,
|
||||||
|
},
|
||||||
|
weights: {
|
||||||
|
fuzzy: 0.2,
|
||||||
|
prefix: 0.8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
minisearch.add({
|
||||||
|
id: `create-collection`,
|
||||||
|
name: this.t("collection.new"),
|
||||||
|
})
|
||||||
|
minisearch.add({
|
||||||
|
id: "import-collection",
|
||||||
|
name: this.t("collection.import"),
|
||||||
|
})
|
||||||
|
|
||||||
|
const newCollectionText: SpotlightResultTextType<any> = {
|
||||||
|
type: "text",
|
||||||
|
text: this.t("collection.new"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const importCollectionText: SpotlightResultTextType<any> = {
|
||||||
|
type: "text",
|
||||||
|
text: this.t("collection.import_collection"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const getResultText = (id: string): SpotlightResultTextType<any> => {
|
||||||
|
if (id === "create-collection") return newCollectionText
|
||||||
|
return importCollectionText
|
||||||
|
}
|
||||||
|
|
||||||
|
const getResultIcon = (id: string) => {
|
||||||
|
if (id === "import-collection") return markRaw(IconImport)
|
||||||
|
return markRaw(IconFolder)
|
||||||
|
}
|
||||||
|
|
||||||
const isTeamWorkspace = computed(
|
const isTeamWorkspace = computed(
|
||||||
() => this.workspaceService.currentWorkspace.value.type === "team"
|
() => this.workspaceService.currentWorkspace.value.type === "team"
|
||||||
)
|
)
|
||||||
@@ -59,6 +133,13 @@ export class TeamsSpotlightSearcherService
|
|||||||
if (this.workspaceService.currentWorkspace.value.type === "team") {
|
if (this.workspaceService.currentWorkspace.value.type === "team") {
|
||||||
const teamID = this.workspaceService.currentWorkspace.value.teamID
|
const teamID = this.workspaceService.currentWorkspace.value.teamID
|
||||||
debouncedSearch(query, teamID)?.catch(() => {})
|
debouncedSearch(query, teamID)?.catch(() => {})
|
||||||
|
const searchResults = minisearch.search(query).slice(0, 10)
|
||||||
|
results.value = searchResults.map((result) => ({
|
||||||
|
id: result.id,
|
||||||
|
text: getResultText(result.id),
|
||||||
|
icon: getResultIcon(result.id),
|
||||||
|
score: result.score,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -91,36 +172,47 @@ export class TeamsSpotlightSearcherService
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resultObj = computed<SpotlightSearcherSessionState>(() => {
|
const resultObj = computed<SpotlightSearcherSessionState>(() => {
|
||||||
return isTeamWorkspace.value
|
if (isTeamWorkspace.value) {
|
||||||
? {
|
const teamsSearchResults =
|
||||||
loading: this.teamsSearch.teamsSearchResultsLoading.value,
|
this.teamsSearch.teamsSearchResultsFormattedForSpotlight.value
|
||||||
results:
|
const minisearchResults = results.value
|
||||||
this.teamsSearch.teamsSearchResultsFormattedForSpotlight.value.map(
|
|
||||||
(result) => ({
|
|
||||||
id: result.request.id,
|
|
||||||
icon: markRaw(IconFolder),
|
|
||||||
score: 1, // make a better scoring system for this
|
|
||||||
text: {
|
|
||||||
type: "custom",
|
|
||||||
component: markRaw(RESTTeamRequestEntry),
|
|
||||||
componentProps: {
|
|
||||||
collectionTitles: result.collectionTitles,
|
|
||||||
request: result.request,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
loading: false,
|
|
||||||
results: [],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const mergedResults = [
|
||||||
|
...teamsSearchResults.map((result) => ({
|
||||||
|
id: result.request.id,
|
||||||
|
icon: markRaw(IconFolder),
|
||||||
|
score: 1, // make a better scoring system for this
|
||||||
|
text: {
|
||||||
|
type: "custom",
|
||||||
|
component: markRaw(RESTTeamRequestEntry),
|
||||||
|
componentProps: {
|
||||||
|
collectionTitles: result.collectionTitles,
|
||||||
|
request: result.request,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
...minisearchResults,
|
||||||
|
] as SpotlightSearcherResult[]
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading: this.teamsSearch.teamsSearchResultsLoading.value,
|
||||||
|
results: mergedResults,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
results: [],
|
||||||
|
}
|
||||||
|
})
|
||||||
return [resultObj, onSessionEnd]
|
return [resultObj, onSessionEnd]
|
||||||
}
|
}
|
||||||
|
|
||||||
onResultSelect(result: SpotlightSearcherResult): void {
|
onResultSelect(result: SpotlightSearcherResult): void {
|
||||||
|
if (result.id === "create-collection") return invokeAction("collection.new")
|
||||||
|
|
||||||
|
if (result.id === "import-collection")
|
||||||
|
return invokeAction(`modals.collection.import`)
|
||||||
|
|
||||||
let inheritedProperties: HoppInheritedProperty | undefined = undefined
|
let inheritedProperties: HoppInheritedProperty | undefined = undefined
|
||||||
|
|
||||||
const selectedRequest = this.teamsSearch.searchResultsRequests[result.id]
|
const selectedRequest = this.teamsSearch.searchResultsRequests[result.id]
|
||||||
|
|||||||
Reference in New Issue
Block a user