Files
hoppscotch/packages/hoppscotch-common/src/services/spotlight/searchers/workspace.searcher.ts
2023-08-25 01:44:29 +05:30

294 lines
7.8 KiB
TypeScript

import {
Component,
Ref,
computed,
effectScope,
markRaw,
reactive,
ref,
watch,
} from "vue"
import { invokeAction } from "~/helpers/actions"
import { getI18n } from "~/modules/i18n"
import {
SpotlightSearcher,
SpotlightSearcherResult,
SpotlightSearcherSessionState,
SpotlightService,
} from ".."
import {
SearchResult,
StaticSpotlightSearcherService,
} from "./base/static.searcher"
import { Service } from "dioc"
import * as E from "fp-ts/Either"
import MiniSearch from "minisearch"
import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue"
import { useStreamStatic } from "~/composables/stream"
import { runGQLQuery } from "~/helpers/backend/GQLClient"
import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql"
import { workspaceStatus$ } from "~/newstore/workspace"
import { platform } from "~/platform"
import IconEdit from "~icons/lucide/edit"
import IconTrash2 from "~icons/lucide/trash-2"
import IconUser from "~icons/lucide/user"
import IconUserPlus from "~icons/lucide/user-plus"
import IconUsers from "~icons/lucide/users"
type Doc = {
text: string | string[]
alternates: string[]
icon: object | Component
excludeFromSearch?: boolean
}
/**
*
* This searcher is responsible for providing team related actions on the spotlight results.
*
* NOTE: Initializing this service registers it as a searcher with the Spotlight Service.
*/
export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherService<Doc> {
public static readonly ID = "WORKSPACE_SPOTLIGHT_SEARCHER_SERVICE"
private t = getI18n()
public readonly searcherID = "workspace"
public searcherSectionTitle = this.t("spotlight.workspace.title")
private readonly spotlight = this.bind(SpotlightService)
private workspace = useStreamStatic(
workspaceStatus$,
{ type: "personal" },
() => {
/* noop */
}
)[0]
private isTeamSelected = computed(
() =>
this.workspace.value.type === "team" &&
this.workspace.value.teamID !== undefined
)
private documents: Record<string, Doc> = reactive({
new_team: {
text: [this.t("team.title"), this.t("spotlight.workspace.new")],
alternates: ["new", "team", "workspace"],
icon: markRaw(IconUsers),
},
edit_team: {
text: [this.t("team.title"), this.t("spotlight.workspace.edit")],
alternates: ["edit", "team", "workspace"],
icon: markRaw(IconEdit),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
},
invite_members: {
text: [this.t("team.title"), this.t("spotlight.workspace.invite")],
alternates: ["invite", "members", "workspace"],
icon: markRaw(IconUserPlus),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
},
delete_team: {
text: [this.t("team.title"), this.t("spotlight.workspace.delete")],
alternates: ["delete", "team", "workspace"],
icon: markRaw(IconTrash2),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
},
switch_to_personal: {
text: [
this.t("team.title"),
this.t("spotlight.workspace.switch_to_personal"),
],
alternates: ["switch", "team", "workspace", "personal"],
icon: markRaw(IconUser),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
},
})
constructor() {
super({
searchFields: ["text", "alternates"],
fieldWeights: {
text: 2,
alternates: 1,
},
})
this.setDocuments(this.documents)
this.spotlight.registerSearcher(this)
}
protected getSearcherResultForSearchResult(
result: SearchResult<Doc>
): SpotlightSearcherResult {
return {
id: result.id,
icon: result.doc.icon,
text: { type: "text", text: result.doc.text },
score: result.score,
}
}
private deleteTeam(): void {
if (this.workspace.value.type === "team")
invokeAction(`modals.team.delete`, {
teamId: this.workspace.value.teamID,
})
}
public onDocSelected(id: string): void {
if (id === "new_team") {
if (platform.auth.getCurrentUser()) {
invokeAction(`modals.team.new`)
} else {
invokeAction(`modals.login.toggle`)
}
} else if (id === "edit_team") invokeAction(`modals.team.edit`)
else if (id === "invite_members") invokeAction(`modals.team.invite`)
else if (id === "delete_team") this.deleteTeam()
else if (id === "switch_to_personal")
invokeAction(`workspace.switch.personal`)
}
}
/**
* This searcher is responsible for searching through the environment.
* And switching between them.
*/
export class SwitchWorkspaceSpotlightSearcherService
extends Service
implements SpotlightSearcher
{
public static readonly ID = "SWITCH_WORKSPACE_SPOTLIGHT_SEARCHER_SERVICE"
private t = getI18n()
public searcherID = "switch_workspace"
public searcherSectionTitle = this.t("workspace.title")
private readonly spotlight = this.bind(SpotlightService)
constructor() {
super()
this.spotlight.registerSearcher(this)
}
private fetchMyTeams(): Promise<GetMyTeamsQuery["myTeams"]> {
return new Promise(async (resolve) => {
const currentUser = platform.auth.getCurrentUser()
if (!currentUser) return resolve([])
const results: GetMyTeamsQuery["myTeams"] = []
const result = await runGQLQuery({
query: GetMyTeamsDocument,
variables: {
cursor:
results.length > 0 ? results[results.length - 1].id : undefined,
},
})
if (E.isRight(result)) results.push(...result.right.myTeams)
resolve(results)
})
}
private workspace = useStreamStatic(
workspaceStatus$,
{ type: "personal" },
() => {
/* noop */
}
)[0]
createSearchSession(
query: Readonly<Ref<string>>
): [Ref<SpotlightSearcherSessionState>, () => void] {
const loading = ref(false)
const results = ref<SpotlightSearcherResult[]>([])
const minisearch = new MiniSearch({
fields: ["name", "alternates"],
storeFields: ["name"],
})
this.fetchMyTeams().then((teams) => {
minisearch.addAll(
teams.map((entry) => {
let id = `workspace-${entry.id}`
// if id matches add -selected to it
if (
this.workspace.value.type === "team" &&
this.workspace.value.teamID === entry.id
) {
id += "-selected"
}
return {
id,
name: entry.name,
alternates: ["team", "workspace", "change", "switch"],
}
})
)
})
const scopeHandle = effectScope()
scopeHandle.run(() => {
watch(
[query],
([query]) => {
results.value = minisearch
.search(query, {
prefix: true,
fuzzy: true,
boost: {
reltime: 2,
},
weights: {
fuzzy: 0.2,
prefix: 0.8,
},
})
.map((x) => {
return {
id: x.id,
icon: markRaw(
x.id.endsWith("-selected") ? IconCheckCircle : IconUsers
),
score: x.score,
text: {
type: "text",
text: [this.t("workspace.change"), x.name],
},
}
})
},
{ immediate: true }
)
})
const onSessionEnd = () => {
scopeHandle.stop()
minisearch.removeAll()
}
const resultObj = computed<SpotlightSearcherSessionState>(() => ({
loading: loading.value,
results: results.value,
}))
return [resultObj, onSessionEnd]
}
onResultSelect(result: SpotlightSearcherResult): void {
invokeAction("workspace.switch", {
teamId: result.id.split("-")[1],
})
}
}