feat: revamped spotlight (#3171)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com>
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
import { Service } from "dioc"
|
||||
import {
|
||||
SpotlightSearcher,
|
||||
SpotlightSearcherResult,
|
||||
SpotlightSearcherSessionState,
|
||||
SpotlightService,
|
||||
} from "../"
|
||||
import { Ref, computed, effectScope, markRaw, ref, watch } from "vue"
|
||||
import { getI18n } from "~/modules/i18n"
|
||||
import MiniSearch from "minisearch"
|
||||
import { graphqlHistoryStore, restHistoryStore } from "~/newstore/history"
|
||||
import { useTimeAgo } from "@vueuse/core"
|
||||
import IconHistory from "~icons/lucide/history"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import SpotlightRESTHistoryEntry from "~/components/app/spotlight/entry/RESTHistory.vue"
|
||||
import SpotlightGQLHistoryEntry from "~/components/app/spotlight/entry/GQLHistory.vue"
|
||||
import { capitalize } from "lodash-es"
|
||||
import { shortDateTime } from "~/helpers/utils/date"
|
||||
import { useStreamStatic } from "~/composables/stream"
|
||||
import { activeActions$, invokeAction } from "~/helpers/actions"
|
||||
import { map } from "rxjs/operators"
|
||||
import { HoppRESTDocument } from "~/helpers/rest/document"
|
||||
|
||||
/**
|
||||
* This searcher is responsible for searching through the history.
|
||||
* It also provides actions to clear the history.
|
||||
*
|
||||
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
|
||||
*/
|
||||
export class HistorySpotlightSearcherService
|
||||
extends Service
|
||||
implements SpotlightSearcher
|
||||
{
|
||||
public static readonly ID = "HISTORY_SPOTLIGHT_SEARCHER_SERVICE"
|
||||
|
||||
private t = getI18n()
|
||||
|
||||
public searcherID = "history"
|
||||
public searcherSectionTitle = this.t("tab.history")
|
||||
|
||||
private readonly spotlight = this.bind(SpotlightService)
|
||||
|
||||
private clearHistoryActionEnabled = useStreamStatic(
|
||||
activeActions$.pipe(map((actions) => actions.includes("history.clear"))),
|
||||
activeActions$.value.includes("history.clear"),
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
private restHistoryEntryOpenable = useStreamStatic(
|
||||
activeActions$.pipe(
|
||||
map((actions) => actions.includes("rest.request.open"))
|
||||
),
|
||||
activeActions$.value.includes("rest.request.open"),
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
private gqlHistoryEntryOpenable = useStreamStatic(
|
||||
activeActions$.pipe(map((actions) => actions.includes("gql.request.open"))),
|
||||
activeActions$.value.includes("gql.request.open"),
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.spotlight.registerSearcher(this)
|
||||
}
|
||||
|
||||
createSearchSession(
|
||||
query: Readonly<Ref<string>>
|
||||
): [Ref<SpotlightSearcherSessionState>, () => void] {
|
||||
const loading = ref(false)
|
||||
const results = ref<SpotlightSearcherResult[]>([])
|
||||
|
||||
const minisearch = new MiniSearch({
|
||||
fields: ["url", "title", "reltime", "date"],
|
||||
storeFields: ["url"],
|
||||
})
|
||||
|
||||
const stopWatchHandle = watch(
|
||||
this.clearHistoryActionEnabled,
|
||||
(enabled) => {
|
||||
if (enabled) {
|
||||
if (minisearch.has("clear-history")) return
|
||||
|
||||
minisearch.add({
|
||||
id: "clear-history",
|
||||
title: this.t("action.clear_history"),
|
||||
})
|
||||
} else {
|
||||
if (!minisearch.has("clear-history")) return
|
||||
|
||||
minisearch.discard("clear-history")
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
if (this.restHistoryEntryOpenable.value) {
|
||||
minisearch.addAll(
|
||||
restHistoryStore.value.state
|
||||
.filter((x) => !!x.updatedOn)
|
||||
.map((entry, index) => {
|
||||
const relTimeString = capitalize(
|
||||
useTimeAgo(entry.updatedOn!, {
|
||||
updateInterval: 0,
|
||||
}).value
|
||||
)
|
||||
|
||||
return {
|
||||
id: `rest-${index}`,
|
||||
url: entry.request.endpoint,
|
||||
reltime: relTimeString,
|
||||
date: shortDateTime(entry.updatedOn!),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (this.gqlHistoryEntryOpenable.value) {
|
||||
minisearch.addAll(
|
||||
graphqlHistoryStore.value.state
|
||||
.filter((x) => !!x.updatedOn)
|
||||
.map((entry, index) => {
|
||||
const relTimeString = capitalize(
|
||||
useTimeAgo(entry.updatedOn!, {
|
||||
updateInterval: 0,
|
||||
}).value
|
||||
)
|
||||
|
||||
return {
|
||||
id: `gql-${index}`,
|
||||
url: entry.request.url,
|
||||
reltime: relTimeString,
|
||||
date: shortDateTime(entry.updatedOn!),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const scopeHandle = effectScope()
|
||||
|
||||
scopeHandle.run(() => {
|
||||
watch(
|
||||
[query, this.clearHistoryActionEnabled],
|
||||
([query]) => {
|
||||
results.value = minisearch
|
||||
.search(query, {
|
||||
prefix: true,
|
||||
fuzzy: true,
|
||||
boost: {
|
||||
reltime: 2,
|
||||
},
|
||||
weights: {
|
||||
fuzzy: 0.2,
|
||||
prefix: 0.8,
|
||||
},
|
||||
})
|
||||
.map((x) => {
|
||||
if (x.id === "clear-history") {
|
||||
return {
|
||||
id: "clear-history",
|
||||
icon: markRaw(IconTrash2),
|
||||
score: x.score,
|
||||
text: {
|
||||
type: "text",
|
||||
text: this.t("action.clear_history"),
|
||||
},
|
||||
}
|
||||
}
|
||||
if (x.id.startsWith("rest-")) {
|
||||
const entry =
|
||||
restHistoryStore.value.state[parseInt(x.id.split("-")[1])]
|
||||
|
||||
return {
|
||||
id: x.id,
|
||||
icon: markRaw(IconHistory),
|
||||
score: x.score,
|
||||
text: {
|
||||
type: "custom",
|
||||
component: markRaw(SpotlightRESTHistoryEntry),
|
||||
componentProps: {
|
||||
historyEntry: entry,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Assume gql
|
||||
const entry =
|
||||
graphqlHistoryStore.value.state[parseInt(x.id.split("-")[1])]
|
||||
|
||||
return {
|
||||
id: x.id,
|
||||
icon: markRaw(IconHistory),
|
||||
score: x.score,
|
||||
text: {
|
||||
type: "custom",
|
||||
component: markRaw(SpotlightGQLHistoryEntry),
|
||||
componentProps: {
|
||||
historyEntry: entry,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
})
|
||||
|
||||
const onSessionEnd = () => {
|
||||
scopeHandle.stop()
|
||||
stopWatchHandle()
|
||||
minisearch.removeAll()
|
||||
}
|
||||
|
||||
const resultObj = computed<SpotlightSearcherSessionState>(() => ({
|
||||
loading: loading.value,
|
||||
results: results.value,
|
||||
}))
|
||||
|
||||
return [resultObj, onSessionEnd]
|
||||
}
|
||||
|
||||
onResultSelect(result: SpotlightSearcherResult): void {
|
||||
if (result.id === "clear-history") {
|
||||
invokeAction("history.clear")
|
||||
} else if (result.id.startsWith("rest")) {
|
||||
const req =
|
||||
restHistoryStore.value.state[parseInt(result.id.split("-")[1])].request
|
||||
|
||||
invokeAction("rest.request.open", {
|
||||
doc: <HoppRESTDocument>{
|
||||
request: req,
|
||||
isDirty: false,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// Assume gql
|
||||
const req =
|
||||
graphqlHistoryStore.value.state[parseInt(result.id.split("-")[1])]
|
||||
.request
|
||||
|
||||
invokeAction("gql.request.open", {
|
||||
request: req,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user