refactor: initial iterations
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import { Ref } from "vue"
|
||||
|
||||
export type HandleRef<T, InvalidateReason = unknown> = Ref<
|
||||
{ type: "ok"; data: T } | { type: "invalid"; reason: InvalidateReason }
|
||||
>
|
||||
276
packages/hoppscotch-common/src/services/new-workspace/index.ts
Normal file
276
packages/hoppscotch-common/src/services/new-workspace/index.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import { Service } from "dioc"
|
||||
import {
|
||||
Component,
|
||||
Ref,
|
||||
computed,
|
||||
markRaw,
|
||||
shallowReactive,
|
||||
shallowRef,
|
||||
watch,
|
||||
} from "vue"
|
||||
import { WorkspaceProvider } from "./provider"
|
||||
import { HandleRef } from "./handle"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { Workspace, WorkspaceCollection } from "./workspace"
|
||||
import { RESTCollectionChildrenView, RootRESTCollectionView } from "./view"
|
||||
|
||||
export type WorkspaceError<ServiceErr> =
|
||||
| { type: "SERVICE_ERROR"; error: ServiceErr }
|
||||
| { type: "PROVIDER_ERROR"; error: unknown }
|
||||
|
||||
export class NewWorkspaceService extends Service {
|
||||
public static readonly ID = "NEW_WORKSPACE_SERVICE"
|
||||
|
||||
private registeredProviders = shallowReactive(
|
||||
new Map<string, WorkspaceProvider>()
|
||||
)
|
||||
|
||||
public activeWorkspaceHandle: Ref<HandleRef<Workspace> | undefined> =
|
||||
shallowRef()
|
||||
|
||||
public activeWorkspaceDecor = computed(() => {
|
||||
if (this.activeWorkspaceHandle.value?.value.type !== "ok") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return this.registeredProviders.get(
|
||||
this.activeWorkspaceHandle.value.value.data.providerID
|
||||
)!.workspaceDecor
|
||||
})
|
||||
|
||||
public workspaceSelectorComponents = computed(() => {
|
||||
const items: Component[] = []
|
||||
|
||||
const sortedProviders = Array.from(this.registeredProviders.values()).sort(
|
||||
(a, b) =>
|
||||
(b.workspaceDecor?.value.workspaceSelectorPriority ?? 0) -
|
||||
(a.workspaceDecor?.value.workspaceSelectorPriority ?? 0)
|
||||
)
|
||||
|
||||
for (const workspace of sortedProviders) {
|
||||
if (workspace.workspaceDecor?.value?.workspaceSelectorComponent) {
|
||||
items.push(workspace.workspaceDecor.value.workspaceSelectorComponent)
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
// Watch for situations where the handle is invalidated
|
||||
// so the active workspace handle definition can be invalidated
|
||||
watch(
|
||||
() => {
|
||||
return this.activeWorkspaceHandle.value
|
||||
? [
|
||||
this.activeWorkspaceHandle.value,
|
||||
this.activeWorkspaceHandle.value.value,
|
||||
]
|
||||
: [this.activeWorkspaceHandle.value]
|
||||
},
|
||||
() => {
|
||||
if (!this.activeWorkspaceHandle.value) return
|
||||
|
||||
if (this.activeWorkspaceHandle.value.value.type === "invalid") {
|
||||
this.activeWorkspaceHandle.value = undefined
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
}
|
||||
|
||||
public async getWorkspaceHandle(
|
||||
providerID: string,
|
||||
workspaceID: string
|
||||
): Promise<
|
||||
E.Either<WorkspaceError<"INVALID_PROVIDER">, HandleRef<Workspace>>
|
||||
> {
|
||||
const provider = this.registeredProviders.get(providerID)
|
||||
|
||||
if (!provider) {
|
||||
return Promise.resolve(
|
||||
E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" as const })
|
||||
)
|
||||
}
|
||||
|
||||
const handleResult = await provider.getWorkspaceHandle(workspaceID)
|
||||
|
||||
if (E.isLeft(handleResult)) {
|
||||
return E.left({ type: "PROVIDER_ERROR", error: handleResult.left })
|
||||
}
|
||||
|
||||
return E.right(handleResult.right)
|
||||
}
|
||||
|
||||
public async getCollectionHandle(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionID: string
|
||||
): Promise<
|
||||
E.Either<
|
||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||
HandleRef<WorkspaceCollection>
|
||||
>
|
||||
> {
|
||||
if (workspaceHandle.value.type === "invalid") {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||
}
|
||||
|
||||
const provider = this.registeredProviders.get(
|
||||
workspaceHandle.value.data.providerID
|
||||
)
|
||||
|
||||
if (!provider) {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||
}
|
||||
|
||||
const result = await provider.getCollectionHandle(
|
||||
workspaceHandle,
|
||||
collectionID
|
||||
)
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||
}
|
||||
|
||||
return E.right(result.right)
|
||||
}
|
||||
|
||||
public async createRESTRootCollection(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionName: string
|
||||
): Promise<
|
||||
E.Either<
|
||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||
HandleRef<WorkspaceCollection>
|
||||
>
|
||||
> {
|
||||
if (workspaceHandle.value.type === "invalid") {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||
}
|
||||
|
||||
const provider = this.registeredProviders.get(
|
||||
workspaceHandle.value.data.providerID
|
||||
)
|
||||
|
||||
if (!provider) {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||
}
|
||||
|
||||
const result = await provider.createRESTRootCollection(
|
||||
workspaceHandle,
|
||||
collectionName
|
||||
)
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||
}
|
||||
|
||||
return E.right(result.right)
|
||||
}
|
||||
|
||||
public async createRESTChildCollection(
|
||||
parentCollHandle: HandleRef<WorkspaceCollection>,
|
||||
collectionName: string
|
||||
): Promise<
|
||||
E.Either<
|
||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||
HandleRef<WorkspaceCollection>
|
||||
>
|
||||
> {
|
||||
if (parentCollHandle.value.type === "invalid") {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||
}
|
||||
|
||||
const provider = this.registeredProviders.get(
|
||||
parentCollHandle.value.data.providerID
|
||||
)
|
||||
|
||||
if (!provider) {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||
}
|
||||
|
||||
const result = await provider.createRESTChildCollection(
|
||||
parentCollHandle,
|
||||
collectionName
|
||||
)
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||
}
|
||||
|
||||
return E.right(result.right)
|
||||
}
|
||||
|
||||
public async getRESTCollectionChildrenView(
|
||||
collectionHandle: HandleRef<WorkspaceCollection>
|
||||
): Promise<
|
||||
E.Either<
|
||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||
HandleRef<RESTCollectionChildrenView>
|
||||
>
|
||||
> {
|
||||
if (collectionHandle.value.type === "invalid") {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||
}
|
||||
|
||||
const provider = this.registeredProviders.get(
|
||||
collectionHandle.value.data.providerID
|
||||
)
|
||||
|
||||
if (!provider) {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||
}
|
||||
|
||||
const result =
|
||||
await provider.getRESTCollectionChildrenView(collectionHandle)
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||
}
|
||||
|
||||
return E.right(result.right)
|
||||
}
|
||||
|
||||
public async getRESTRootCollectionView(
|
||||
workspaceHandle: HandleRef<Workspace>
|
||||
): Promise<
|
||||
E.Either<
|
||||
WorkspaceError<"INVALID_HANDLE" | "INVALID_PROVIDER">,
|
||||
HandleRef<RootRESTCollectionView>
|
||||
>
|
||||
> {
|
||||
if (workspaceHandle.value.type === "invalid") {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_HANDLE" })
|
||||
}
|
||||
|
||||
const provider = this.registeredProviders.get(
|
||||
workspaceHandle.value.data.providerID
|
||||
)
|
||||
|
||||
if (!provider) {
|
||||
return E.left({ type: "SERVICE_ERROR", error: "INVALID_PROVIDER" })
|
||||
}
|
||||
|
||||
const result = await provider.getRESTRootCollectionView(workspaceHandle)
|
||||
|
||||
if (E.isLeft(result)) {
|
||||
return E.left({ type: "PROVIDER_ERROR", error: result.left })
|
||||
}
|
||||
|
||||
return E.right(result.right)
|
||||
}
|
||||
|
||||
public registerWorkspaceProvider(provider: WorkspaceProvider) {
|
||||
if (this.registeredProviders.has(provider.providerID)) {
|
||||
console.warn(
|
||||
"Ignoring attempt to re-register workspace provider that is already existing:",
|
||||
provider
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
this.registeredProviders.set(provider.providerID, markRaw(provider))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Ref } from "vue"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { HandleRef } from "./handle"
|
||||
import { Workspace, WorkspaceCollection, WorkspaceDecor } from "./workspace"
|
||||
import { RESTCollectionChildrenView, RootRESTCollectionView } from "./view"
|
||||
|
||||
export interface WorkspaceProvider {
|
||||
providerID: string
|
||||
|
||||
workspaceDecor?: Ref<WorkspaceDecor>
|
||||
|
||||
getWorkspaceHandle(
|
||||
workspaceID: string
|
||||
): Promise<E.Either<unknown, HandleRef<Workspace>>>
|
||||
getCollectionHandle(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionID: string
|
||||
): Promise<E.Either<unknown, HandleRef<WorkspaceCollection>>>
|
||||
|
||||
getRESTRootCollectionView(
|
||||
workspaceHandle: HandleRef<Workspace>
|
||||
): Promise<E.Either<unknown, HandleRef<RootRESTCollectionView>>>
|
||||
getRESTCollectionChildrenView(
|
||||
collectionHandle: HandleRef<WorkspaceCollection>
|
||||
): Promise<E.Either<unknown, HandleRef<RESTCollectionChildrenView>>>
|
||||
|
||||
createRESTRootCollection(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionName: string
|
||||
): Promise<E.Either<unknown, HandleRef<WorkspaceCollection>>>
|
||||
createRESTChildCollection(
|
||||
parentCollHandle: HandleRef<WorkspaceCollection>,
|
||||
collectionName: string
|
||||
): Promise<E.Either<unknown, HandleRef<WorkspaceCollection>>>
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppRESTRequest,
|
||||
makeCollection,
|
||||
} from "@hoppscotch/data"
|
||||
import { Service } from "dioc"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { get } from "lodash-es"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { Ref, computed, markRaw, ref, shallowReactive, shallowRef } from "vue"
|
||||
|
||||
import PersonalWorkspaceSelector from "~/components/workspace/PersonalWorkspaceSelector.vue"
|
||||
import { useStreamStatic } from "~/composables/stream"
|
||||
|
||||
import { addRESTCollection, restCollectionStore } from "~/newstore/collections"
|
||||
import { platform } from "~/platform"
|
||||
|
||||
import { HandleRef } from "~/services/new-workspace/handle"
|
||||
import { WorkspaceProvider } from "~/services/new-workspace/provider"
|
||||
import {
|
||||
RESTCollectionChildrenView,
|
||||
RESTCollectionViewItem,
|
||||
RootRESTCollectionView,
|
||||
} from "~/services/new-workspace/view"
|
||||
import {
|
||||
Workspace,
|
||||
WorkspaceCollection,
|
||||
WorkspaceDecor,
|
||||
} from "~/services/new-workspace/workspace"
|
||||
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import { NewWorkspaceService } from ".."
|
||||
|
||||
export class PersonalWorkspaceProviderService
|
||||
extends Service
|
||||
implements WorkspaceProvider
|
||||
{
|
||||
public static readonly ID = "PERSONAL_WORKSPACE_PROVIDER_SERVICE"
|
||||
|
||||
public readonly providerID = "PERSONAL_WORKSPACE_PROVIDER"
|
||||
|
||||
private workspaceService = this.bind(NewWorkspaceService)
|
||||
|
||||
public workspaceDecor: Ref<WorkspaceDecor> = ref({
|
||||
headerCurrentIcon: IconUser,
|
||||
workspaceSelectorComponent: PersonalWorkspaceSelector,
|
||||
workspaceSelectorPriority: 100,
|
||||
})
|
||||
|
||||
private restCollectionState: Ref<{ state: HoppCollection[] }>
|
||||
|
||||
public constructor() {
|
||||
super()
|
||||
|
||||
this.restCollectionState = useStreamStatic(
|
||||
restCollectionStore.subject$,
|
||||
{ state: [] },
|
||||
() => {
|
||||
/* noop */
|
||||
}
|
||||
)[0]
|
||||
|
||||
this.workspaceService.registerWorkspaceProvider(this)
|
||||
}
|
||||
|
||||
private collectionIDMap = shallowReactive(
|
||||
new WeakMap<HoppCollection, string>()
|
||||
)
|
||||
|
||||
private reqIDMap = shallowReactive(new WeakMap<HoppRESTRequest, string>())
|
||||
|
||||
private collectionIDPathMap = shallowReactive(new Map<string, number[]>())
|
||||
|
||||
private generatedUUIDs = new Set<string>()
|
||||
|
||||
private generateUniqueUUID() {
|
||||
let id = uuid()
|
||||
|
||||
while (this.generatedUUIDs.has(id)) {
|
||||
id = uuid()
|
||||
}
|
||||
|
||||
this.generatedUUIDs.add(id)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
private resolvePathFromCollectionID(id: string): number[] | undefined {
|
||||
return this.collectionIDPathMap.get(id)
|
||||
}
|
||||
|
||||
private resolveCollectionFromCollectionID(
|
||||
id: string
|
||||
): HoppCollection | undefined {
|
||||
const path = this.resolvePathFromCollectionID(id)
|
||||
|
||||
if (path === undefined) return
|
||||
|
||||
const collPath = path.flatMap((x, i) =>
|
||||
i === 0 ? [x.toString()] : ["folders", x.toString()]
|
||||
)
|
||||
|
||||
const coll = get(this.restCollectionState.value.state, collPath) as
|
||||
| HoppCollection
|
||||
| undefined
|
||||
|
||||
return coll
|
||||
}
|
||||
|
||||
private getIssuedInstanceIDForCollection(
|
||||
coll: HoppCollection,
|
||||
location: number[]
|
||||
) {
|
||||
const id = this.collectionIDMap.has(coll)
|
||||
? this.collectionIDMap.get(coll)!
|
||||
: this.generateUniqueUUID()
|
||||
|
||||
this.collectionIDPathMap.set(id, location)
|
||||
this.collectionIDMap.set(coll, id)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
private getIssuedInstanceIDForRequest(req: HoppRESTRequest) {
|
||||
const id = this.reqIDMap.get(req) ?? this.generateUniqueUUID()
|
||||
|
||||
this.reqIDMap.set(req, id)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
public createRESTChildCollection(
|
||||
parentCollHandle: HandleRef<WorkspaceCollection>,
|
||||
collectionName: string
|
||||
): Promise<E.Either<unknown, HandleRef<WorkspaceCollection>>> {
|
||||
throw new Error("TODO: Method not implemented.")
|
||||
}
|
||||
|
||||
public createRESTRootCollection(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionName: string
|
||||
): Promise<E.Either<unknown, HandleRef<WorkspaceCollection>>> {
|
||||
if (
|
||||
workspaceHandle.value.type !== "ok" ||
|
||||
workspaceHandle.value.data.providerID !== this.providerID ||
|
||||
workspaceHandle.value.data.workspaceID !== "personal"
|
||||
) {
|
||||
return Promise.resolve(E.left("INVALID_WORKSPACE_HANDLE" as const))
|
||||
}
|
||||
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (
|
||||
workspaceHandle.value.type !== "ok" ||
|
||||
workspaceHandle.value.data.providerID !== this.providerID ||
|
||||
workspaceHandle.value.data.workspaceID !== "personal"
|
||||
) {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "WORKSPACE_INVALIDATED" as const,
|
||||
}
|
||||
}
|
||||
|
||||
addRESTCollection(
|
||||
makeCollection({
|
||||
name: collectionName,
|
||||
folders: [],
|
||||
requests: [],
|
||||
headers: [],
|
||||
auth: {
|
||||
authType: "inherit",
|
||||
authActive: false,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_CREATE_COLLECTION",
|
||||
platform: "rest",
|
||||
workspaceType: "personal",
|
||||
isRootCollection: true,
|
||||
})
|
||||
|
||||
return {
|
||||
type: "ok",
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID: workspaceHandle.value.data.workspaceID,
|
||||
collectionID: "",
|
||||
name: collectionName,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getCollectionHandle(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionID: string
|
||||
): Promise<
|
||||
E.Either<"INVALID_WORKSPACE_HANDLE", HandleRef<WorkspaceCollection>>
|
||||
> {
|
||||
if (
|
||||
workspaceHandle.value.type !== "ok" ||
|
||||
workspaceHandle.value.data.providerID !== this.providerID ||
|
||||
workspaceHandle.value.data.workspaceID !== "personal"
|
||||
) {
|
||||
return Promise.resolve(E.left("INVALID_WORKSPACE_HANDLE" as const))
|
||||
}
|
||||
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (
|
||||
workspaceHandle.value.type !== "ok" ||
|
||||
workspaceHandle.value.data.providerID !== this.providerID ||
|
||||
workspaceHandle.value.data.workspaceID !== "personal"
|
||||
) {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "WORKSPACE_INVALIDATED" as const,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The way the IDs are issued, this will make it so we need a view
|
||||
// before the ID is issued correctly
|
||||
const coll = this.resolveCollectionFromCollectionID(collectionID)
|
||||
|
||||
if (coll === undefined) {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "INVALID_COLL_ID" as const,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: "ok",
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID: workspaceHandle.value.data.workspaceID,
|
||||
collectionID,
|
||||
name: coll.name,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getRESTCollectionChildrenView(
|
||||
collectionHandle: HandleRef<WorkspaceCollection>
|
||||
): Promise<E.Either<never, HandleRef<RESTCollectionChildrenView>>> {
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (
|
||||
collectionHandle.value.type === "invalid" ||
|
||||
collectionHandle.value.data.providerID !== this.providerID ||
|
||||
collectionHandle.value.data.workspaceID !== "personal"
|
||||
) {
|
||||
return {
|
||||
type: "invalid" as const,
|
||||
reason: "INVALID_COLLECTION_HANDLE" as const,
|
||||
}
|
||||
}
|
||||
|
||||
const collectionID = collectionHandle.value.data.collectionID
|
||||
|
||||
return markRaw({
|
||||
type: "ok" as const,
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID: collectionHandle.value.data.workspaceID,
|
||||
collectionID: collectionHandle.value.data.collectionID,
|
||||
|
||||
loading: ref(false),
|
||||
mayHaveMoreContent: ref(false),
|
||||
|
||||
content: computed(() => {
|
||||
const path = this.resolvePathFromCollectionID(collectionID)
|
||||
const coll =
|
||||
this.resolveCollectionFromCollectionID(collectionID)
|
||||
|
||||
if (coll === undefined || path === undefined) {
|
||||
console.warn("Collection against ID was not resolvable")
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
const collections = coll.folders.map((childColl, i) => {
|
||||
const id = this.getIssuedInstanceIDForCollection(childColl, [
|
||||
...path,
|
||||
i,
|
||||
])
|
||||
|
||||
return <RESTCollectionViewItem>{
|
||||
type: "collection",
|
||||
value: {
|
||||
collectionID: id,
|
||||
name: coll.name,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const requests = coll.requests.map((req, i) => {
|
||||
const id = this.getIssuedInstanceIDForRequest(req)
|
||||
|
||||
return <RESTCollectionViewItem>{
|
||||
type: "request",
|
||||
value: {
|
||||
requestID: id,
|
||||
name: req.name,
|
||||
method: req.method,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return [...collections, ...requests]
|
||||
}),
|
||||
loadMore() {
|
||||
return Promise.resolve()
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getRESTRootCollectionView(
|
||||
workspaceHandle: HandleRef<Workspace>
|
||||
): Promise<E.Either<never, HandleRef<RootRESTCollectionView>>> {
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (
|
||||
workspaceHandle.value.type === "invalid" ||
|
||||
workspaceHandle.value.data.providerID !== this.providerID ||
|
||||
workspaceHandle.value.data.workspaceID !== "personal"
|
||||
) {
|
||||
return {
|
||||
type: "invalid" as const,
|
||||
reason: "INVALID_WORKSPACE_HANDLE" as const,
|
||||
}
|
||||
}
|
||||
|
||||
return markRaw({
|
||||
type: "ok" as const,
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID: workspaceHandle.value.data.workspaceID,
|
||||
|
||||
loading: ref(false),
|
||||
mayHaveMoreContent: ref(false),
|
||||
|
||||
collections: computed(() => {
|
||||
return this.restCollectionState.value.state.map((coll, i) => {
|
||||
const id = this.getIssuedInstanceIDForCollection(coll, [i])
|
||||
|
||||
return {
|
||||
collectionID: id,
|
||||
name: coll.name,
|
||||
}
|
||||
})
|
||||
}),
|
||||
loadMore() {
|
||||
return Promise.resolve()
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getWorkspaceHandle(
|
||||
workspaceID: string
|
||||
): Promise<E.Either<unknown, HandleRef<Workspace>>> {
|
||||
if (workspaceID !== "personal") {
|
||||
return Promise.resolve(E.left("INVALID_WORKSPACE_ID" as const))
|
||||
}
|
||||
|
||||
return Promise.resolve(E.right(this.getPersonalWorkspaceHandle()))
|
||||
}
|
||||
|
||||
public getPersonalWorkspaceHandle(): HandleRef<Workspace> {
|
||||
return shallowRef({
|
||||
type: "ok" as const,
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID: "personal",
|
||||
|
||||
name: "Personal Workspace",
|
||||
collectionsAreReadonly: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
import { computed, markRaw, reactive, ref } from "vue"
|
||||
import { useTimestamp } from "@vueuse/core"
|
||||
import { Service } from "dioc"
|
||||
import { WorkspaceProvider } from "../provider"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { HandleRef } from "../handle"
|
||||
import { Workspace, WorkspaceCollection } from "../workspace"
|
||||
import { NewWorkspaceService } from ".."
|
||||
import TestWorkspaceSelector from "~/components/workspace/TestWorkspaceSelector.vue"
|
||||
import { RESTCollectionChildrenView, RootRESTCollectionView } from "../view"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import { get } from "lodash-es"
|
||||
|
||||
type TestReqDef = {
|
||||
name: string
|
||||
}
|
||||
|
||||
type TestCollDef = {
|
||||
name: string
|
||||
collections: TestCollDef[]
|
||||
requests: TestReqDef[]
|
||||
}
|
||||
|
||||
const timestamp = useTimestamp({ interval: 3000 })
|
||||
// const timestamp = ref(Date.now())
|
||||
|
||||
const testData = reactive({
|
||||
workspaceA: {
|
||||
name: computed(() => `Workspace A: ${timestamp.value}`),
|
||||
collections: [
|
||||
<TestCollDef>{
|
||||
name: "Collection A",
|
||||
collections: [
|
||||
{
|
||||
name: "Collection B",
|
||||
collections: [
|
||||
{ name: "Collection C", collections: [], requests: [] },
|
||||
],
|
||||
requests: [],
|
||||
},
|
||||
],
|
||||
requests: [{ name: "Request C" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
workspaceB: {
|
||||
name: "Workspace B",
|
||||
collections: [
|
||||
<TestCollDef>{
|
||||
name: "Collection D",
|
||||
collections: [{ name: "Collection E", collections: [], requests: [] }],
|
||||
requests: [{ name: "Request F" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
;(window as any).testData = testData
|
||||
|
||||
export class TestWorkspaceProviderService
|
||||
extends Service
|
||||
implements WorkspaceProvider
|
||||
{
|
||||
public static readonly ID = "TEST_WORKSPACE_PROVIDER_SERVICE"
|
||||
|
||||
public providerID = "TEST_WORKSPACE_PROVIDER"
|
||||
|
||||
public workspaceDecor = ref({
|
||||
workspaceSelectorComponent: markRaw(TestWorkspaceSelector),
|
||||
headerCurrentIcon: markRaw(IconUser),
|
||||
workspaceSelectorPriority: 10,
|
||||
})
|
||||
|
||||
private readonly workspaceService = this.bind(NewWorkspaceService)
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
this.workspaceService.registerWorkspaceProvider(this)
|
||||
}
|
||||
|
||||
public createRESTRootCollection(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionName: string
|
||||
): Promise<
|
||||
E.Either<"INVALID_WORKSPACE_HANDLE", HandleRef<WorkspaceCollection>>
|
||||
> {
|
||||
if (workspaceHandle.value.type !== "ok") {
|
||||
return Promise.resolve(E.left("INVALID_WORKSPACE_HANDLE" as const))
|
||||
}
|
||||
|
||||
const workspaceID = workspaceHandle.value.data.workspaceID
|
||||
|
||||
const newCollID =
|
||||
testData[workspaceID as keyof typeof testData].collections.length
|
||||
|
||||
testData[workspaceID as keyof typeof testData].collections.push({
|
||||
name: collectionName,
|
||||
collections: [],
|
||||
requests: [],
|
||||
})
|
||||
|
||||
return this.getCollectionHandle(workspaceHandle, newCollID.toString())
|
||||
}
|
||||
|
||||
public createRESTChildCollection(
|
||||
parentCollHandle: HandleRef<WorkspaceCollection>,
|
||||
collectionName: string
|
||||
): Promise<E.Either<unknown, HandleRef<WorkspaceCollection>>> {
|
||||
// TODO: Implement
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
public getWorkspaceHandle(
|
||||
workspaceID: string
|
||||
): Promise<E.Either<never, HandleRef<Workspace>>> {
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (!(workspaceID in testData)) {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "WORKSPACE_WENT_OUT" as const,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: "ok",
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID,
|
||||
name: testData[workspaceID as keyof typeof testData].name,
|
||||
collectionsAreReadonly: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getCollectionHandle(
|
||||
workspaceHandle: HandleRef<Workspace>,
|
||||
collectionID: string
|
||||
): Promise<
|
||||
E.Either<"INVALID_WORKSPACE_HANDLE", HandleRef<WorkspaceCollection>>
|
||||
> {
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (workspaceHandle.value.type !== "ok") {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "WORKSPACE_INVALIDATED" as const,
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceID = workspaceHandle.value.data.workspaceID
|
||||
const collectionPath = collectionID
|
||||
.split("/")
|
||||
.flatMap((x) => ["collections", x])
|
||||
|
||||
const result: TestCollDef | undefined = get(
|
||||
testData[workspaceID as keyof typeof testData],
|
||||
collectionPath
|
||||
)
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "INVALID_COLL_ID",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: "ok",
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID,
|
||||
collectionID,
|
||||
name: result.name,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getRESTCollectionChildrenView(
|
||||
collectionHandle: HandleRef<WorkspaceCollection>
|
||||
): Promise<E.Either<never, HandleRef<RESTCollectionChildrenView>>> {
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (collectionHandle.value.type === "invalid") {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "COLL_HANDLE_IS_INVALID" as const,
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceID = collectionHandle.value.data.workspaceID
|
||||
const collectionID = collectionHandle.value.data.collectionID
|
||||
|
||||
if (!(workspaceID in testData)) {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "WORKSPACE_NOT_PRESENT" as const,
|
||||
}
|
||||
}
|
||||
|
||||
const collectionPath = collectionID
|
||||
.split("/")
|
||||
.flatMap((x) => ["collections", x])
|
||||
|
||||
return markRaw({
|
||||
type: "ok",
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID,
|
||||
collectionID,
|
||||
|
||||
mayHaveMoreContent: ref(false),
|
||||
loading: ref(false),
|
||||
|
||||
content: computed(() => [
|
||||
...(
|
||||
get(testData[workspaceID as keyof typeof testData], [
|
||||
...collectionPath,
|
||||
"collections",
|
||||
]) as TestCollDef[]
|
||||
).map((item, i) => ({
|
||||
type: "collection" as const,
|
||||
value: {
|
||||
collectionID: `${collectionID}/${i}`,
|
||||
name: item.name,
|
||||
},
|
||||
})),
|
||||
...(
|
||||
get(testData[workspaceID as keyof typeof testData], [
|
||||
...collectionPath,
|
||||
"requests",
|
||||
]) as TestReqDef[]
|
||||
).map((item, i) => ({
|
||||
type: "request" as const,
|
||||
value: {
|
||||
requestID: `${collectionID}/${i}`,
|
||||
name: item.name,
|
||||
method: "get",
|
||||
},
|
||||
})),
|
||||
]),
|
||||
|
||||
loadMore(_count: number) {
|
||||
return Promise.resolve()
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getRESTRootCollectionView(
|
||||
workspaceHandle: HandleRef<Workspace>
|
||||
): Promise<E.Either<never, HandleRef<RootRESTCollectionView>>> {
|
||||
return Promise.resolve(
|
||||
E.right(
|
||||
computed(() => {
|
||||
if (workspaceHandle.value.type === "invalid") {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "WORKSPACE_IS_INVALID" as const,
|
||||
}
|
||||
}
|
||||
|
||||
const workspaceID = workspaceHandle.value.data.workspaceID
|
||||
|
||||
if (!(workspaceID in testData)) {
|
||||
return {
|
||||
type: "invalid",
|
||||
reason: "WORKSPACE_NOT_PRESENT" as const,
|
||||
}
|
||||
}
|
||||
|
||||
return markRaw({
|
||||
type: "ok",
|
||||
data: {
|
||||
providerID: this.providerID,
|
||||
workspaceID,
|
||||
|
||||
mayHaveMoreContent: ref(false),
|
||||
loading: ref(false),
|
||||
|
||||
collections: computed(() => {
|
||||
return testData[
|
||||
workspaceID as keyof typeof testData
|
||||
].collections.map((x, i) => ({
|
||||
collectionID: i.toString(),
|
||||
name: x.name,
|
||||
}))
|
||||
}),
|
||||
|
||||
loadMore() {
|
||||
return Promise.resolve()
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public getWorkspaceCandidates() {
|
||||
return computed(() =>
|
||||
Object.keys(testData).map((workspaceID) => ({
|
||||
id: workspaceID,
|
||||
name: testData[workspaceID as keyof typeof testData].name,
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Ref } from "vue"
|
||||
|
||||
export type RESTCollectionViewCollection = {
|
||||
collectionID: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type RESTCollectionViewRequest = {
|
||||
requestID: string
|
||||
|
||||
name: string
|
||||
method: string
|
||||
}
|
||||
|
||||
export type RESTCollectionViewItem =
|
||||
| { type: "collection"; value: RESTCollectionViewCollection }
|
||||
| { type: "request"; value: RESTCollectionViewRequest }
|
||||
|
||||
export interface RootRESTCollectionView {
|
||||
providerID: string
|
||||
workspaceID: string
|
||||
|
||||
mayHaveMoreContent: Ref<boolean>
|
||||
loading: Ref<boolean>
|
||||
|
||||
collections: Ref<RESTCollectionViewCollection[]>
|
||||
|
||||
loadMore(count: number): Promise<void>
|
||||
}
|
||||
|
||||
export interface RESTCollectionChildrenView {
|
||||
providerID: string
|
||||
workspaceID: string
|
||||
collectionID: string
|
||||
|
||||
mayHaveMoreContent: Ref<boolean>
|
||||
loading: Ref<boolean>
|
||||
|
||||
content: Ref<RESTCollectionViewItem[]>
|
||||
|
||||
loadMore(count: number): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component } from "vue"
|
||||
|
||||
export type Workspace = {
|
||||
providerID: string
|
||||
workspaceID: string
|
||||
|
||||
name: string
|
||||
|
||||
collectionsAreReadonly: boolean
|
||||
}
|
||||
|
||||
export type WorkspaceCollection = {
|
||||
providerID: string
|
||||
workspaceID: string
|
||||
collectionID: string
|
||||
|
||||
name: string
|
||||
}
|
||||
|
||||
export type WorkspaceDecor = {
|
||||
headerComponent?: Component
|
||||
|
||||
headerCurrentIcon?: Component | object
|
||||
|
||||
workspaceSelectorComponent?: Component
|
||||
workspaceSelectorPriority?: number
|
||||
}
|
||||
Reference in New Issue
Block a user