feat(frontend): Functionaliteit om leerobjecten te tonen toegevoegd.

Hiervoor ook de state management geherstructureerd.
This commit is contained in:
Gerald Schmittinger 2025-03-24 21:13:46 +01:00
parent 07340de2e3
commit 728b04c9d8
12 changed files with 141 additions and 150 deletions

View file

@ -1,10 +1,9 @@
import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
export class DeleteEndpoint<PP extends Params, QP extends Params, R> extends RestEndpoint<PP, QP, undefined, R> {
readonly method = "GET";
public delete(pathParams: PP, queryParams: QP): RemoteResource<R> {
public delete(pathParams: PP, queryParams: QP): Promise<R> {
return super.request(pathParams, queryParams, undefined);
}
}

View file

@ -1,10 +1,9 @@
import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
export class GetEndpoint<PP extends Params, QP extends Params, R> extends RestEndpoint<PP, QP, undefined, R> {
readonly method = "GET";
public get(pathParams: PP, queryParams: QP): RemoteResource<R> {
public get(pathParams: PP, queryParams: QP): Promise<R> {
return super.request(pathParams, queryParams, undefined);
}
}

View file

@ -1,10 +1,9 @@
import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
export class GetHtmlEndpoint<PP extends Params, QP extends Params> extends RestEndpoint<PP, QP, undefined, Document> {
readonly method: "GET" | "POST" | "PUT" | "DELETE" = "GET";
public get(pathParams: PP, queryParams: QP): RemoteResource<Document> {
public get(pathParams: PP, queryParams: QP): Promise<Document> {
return super.request(pathParams, queryParams, undefined, "document");
}
}

View file

@ -1,10 +1,9 @@
import {type Params, RestEndpoint} from "@/services/api-client/endpoints/rest-endpoint.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
export class PostEndpoint<PP extends Params, QP extends Params, B, R> extends RestEndpoint<PP, QP, B, R> {
readonly method = "POST";
public post(pathParams: PP, queryParams: QP, body: B): RemoteResource<R> {
public post(pathParams: PP, queryParams: QP, body: B): Promise<R> {
return super.request(pathParams, queryParams, body);
}
}

View file

@ -1,4 +1,3 @@
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
import apiClient from "@/services/api-client/api-client.ts";
import {HttpErrorStatusException} from "@/services/api-client/api-exceptions.ts";
import type {ResponseType} from "axios";
@ -8,27 +7,21 @@ export abstract class RestEndpoint<PP extends Params, QP extends Params, B, R> {
constructor(public readonly url: string) {
}
protected request(pathParams: PP, queryParams: QP, body: B, responseType?: ResponseType): RemoteResource<R> {
protected async request(pathParams: PP, queryParams: QP, body: B, responseType?: ResponseType): Promise<R> {
let urlFilledIn = this.url.replace(/:(\w+)(\/|$)/g, (_, key, after) =>
(pathParams[key] ? encodeURIComponent(pathParams[key]) : `:${key}`) + after
);
console.log(this.url);
console.log(/:(\w+)(\W|$)/g.test(this.url))
console.log(pathParams);
console.log("--> filled in: " + urlFilledIn);
return new RemoteResource(async () => {
const response = await apiClient.request<R>({
url: urlFilledIn,
method: this.method,
params: queryParams,
data: body,
responseType: responseType || 'json'
});
if (response.status / 100 !== 2) {
throw new HttpErrorStatusException(response);
}
return response.data;
const response = await apiClient.request<R>({
url: urlFilledIn,
method: this.method,
params: queryParams,
data: body,
responseType: responseType || 'json'
});
if (response.status / 100 !== 2) {
throw new HttpErrorStatusException(response);
}
return response.data;
}
}

View file

@ -1,78 +1,4 @@
export class RemoteResource<T> {
static NOT_LOADED: NotLoadedState = {type: "notLoaded"};
static LOADING: LoadingState = {type: "loading"};
private _state: RemoteResourceState<T> = RemoteResource.NOT_LOADED;
constructor(private readonly requestFn: () => Promise<T>) {
}
public static join<T>(resources: RemoteResource<T>[]): RemoteResource<T[]> {
return new RemoteResource(async () => {
console.log("joined fetch");
const promises = resources.map(it => it.request());
const data = await Promise.all(promises);
const failed = resources
.filter(it => it.state.type === "error")
.map(it => it.state as ErrorState);
if (failed.length > 0) {
console.log("joined error!");
throw failed[0].error;
}
console.log("succ");
console.log(data);
return data.map(it => it!);
});
}
public async request(): Promise<T | undefined> {
this._state = RemoteResource.LOADING;
try {
let resource = await this.requestFn();
this._state = {
type: "success",
data: resource
};
return resource;
} catch (e: any) {
this._state = {
type: "error",
errorCode: e.statusCode,
message: e.message,
error: e
};
}
}
public startRequestInBackground(): RemoteResource<T> {
this.request().then();
return this;
}
public get state(): RemoteResourceState<T> {
return this._state;
}
public get data(): T | undefined {
if (this._state.type === "success") {
return this._state.data;
}
}
public map<U>(mappingFn: (content: T) => U): RemoteResource<U> {
return new RemoteResource<U>(async () => {
await this.request();
if (this._state.type === "success") {
return mappingFn(this._state.data);
} else if (this._state.type === "error") {
throw this._state.error;
} else {
throw new Error("Fetched resource, but afterwards, it was neither in a success nor in an error state. " +
"This should never happen.");
}
});
}
}
import {type ShallowReactive, shallowReactive} from "vue";
export type NotLoadedState = {
type: "notLoaded"
@ -82,8 +8,6 @@ export type LoadingState = {
};
export type ErrorState = {
type: "error",
errorCode?: number,
message?: string,
error: any
};
export type SuccessState<T> = {
@ -91,3 +15,23 @@ export type SuccessState<T> = {
data: T
};
export type RemoteResourceState<T> = NotLoadedState | LoadingState | ErrorState | SuccessState<T>;
export type RemoteResource<T> = ShallowReactive<{
state: RemoteResourceState<T>
}>;
export function remoteResource<T>(): RemoteResource<T> {
return shallowReactive({
state: {
type: "notLoaded"
}
});
}
export function loadResource<T>(resource: RemoteResource<T>, promise: Promise<T>): void {
resource.state = { type: "loading" }
promise.then(
data => resource.state = { type: "success", data },
error => resource.state = { type: "error", error }
);
}

View file

@ -1,13 +1,28 @@
import {GetEndpoint} from "@/services/api-client/endpoints/get-endpoint.ts";
import type {LearningObject} from "@/services/learning-content/learning-object.ts";
import type {Language} from "@/services/learning-content/language.ts";
import type {RemoteResource} from "@/services/api-client/remote-resource.ts";
import {GetHtmlEndpoint} from "@/services/api-client/endpoints/get-html-endpoint.ts";
const getLearningObjectMetadataEndpoint = new GetEndpoint<{hruid: string}, {language: Language, version: number}, LearningObject>(
"/learningObject/:hruid"
);
export function getLearningObjectMetadata(hruid: string, language: Language, version: number): RemoteResource<LearningObject> {
return getLearningObjectMetadataEndpoint
.get({hruid}, {language, version});
const getLearningObjectHtmlEndpoint = new GetHtmlEndpoint<{hruid: string}, {language: Language, version: number}>(
"/learningObject/:hruid/html"
);
export function getLearningObjectMetadata(
hruid: string,
language: Language,
version: number
): Promise<LearningObject> {
return getLearningObjectMetadataEndpoint.get({hruid}, {language, version});
}
export function getLearningObjectHTML(
hruid: string,
language: Language,
version: number
): Promise<Document> {
return getLearningObjectHtmlEndpoint.get({hruid}, {language, version});
}

View file

@ -1,6 +1,5 @@
import {GetEndpoint} from "@/services/api-client/endpoints/get-endpoint.ts";
import {LearningPath, type LearningPathDTO} from "@/services/learning-content/learning-path.ts";
import type {RemoteResource} from "@/services/api-client/remote-resource.ts";
import type {Language} from "@/services/learning-content/language.ts";
import {single} from "@/utils/response-assertions.ts";
@ -8,16 +7,12 @@ const learningPathEndpoint = new GetEndpoint<{}, {search?: string, hruid?: strin
"/learningPath"
);
export function searchLearningPaths(query: string): RemoteResource<LearningPath[]> {
return learningPathEndpoint
.get({}, {search: query})
.map(dtos => dtos.map(dto => LearningPath.fromDTO(dto)));
export async function searchLearningPaths(query: string): Promise<LearningPath[]> {
let dtos = await learningPathEndpoint.get({}, {search: query})
return dtos.map(dto => LearningPath.fromDTO(dto));
}
export function getLearningPath(hruid: string, language: Language): RemoteResource<LearningPath> {
console.log({hruid, language})
return learningPathEndpoint
.get({}, {hruid, language})
.map(it => {console.log(it); return it;})
.map(dtos => LearningPath.fromDTO(single(dtos)));
export async function getLearningPath(hruid: string, language: Language): Promise<LearningPath> {
let dtos = await learningPathEndpoint.get({}, {hruid, language});
return LearningPath.fromDTO(single(dtos));
}

View file

@ -1,5 +1,4 @@
import type {Language} from "@/services/learning-content/language.ts";
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
import type {LearningObject} from "@/services/learning-content/learning-object.ts";
import {getLearningObjectMetadata} from "@/services/learning-content/learning-object-service.ts";
@ -43,7 +42,6 @@ interface LearningPathTransitionDTO {
}
export class LearningPathNode {
public learningObject: RemoteResource<LearningObject>
constructor(
public readonly learningobjectHruid: string,
@ -53,7 +51,10 @@ export class LearningPathNode {
public readonly createdAt: Date,
public readonly updatedAt: Date
) {
this.learningObject = getLearningObjectMetadata(learningobjectHruid, language, version);
}
get learningObject(): Promise<LearningObject> {
return getLearningObjectMetadata(this.learningobjectHruid, this.language, this.version);
}
static fromDTOAndOtherNodes(dto: LearningPathNodeDTO, otherNodes: LearningPathNodeDTO[]): LearningPathNode {
@ -109,8 +110,8 @@ export class LearningPath {
return list;
}
public get learningObjectsAsList(): RemoteResource<LearningObject[]> {
return RemoteResource.join(this.nodesAsList.map(node => node.learningObject));
public get learningObjectsAsList(): Promise<LearningObject[]> {
return Promise.all(this.nodesAsList.map(node => node.learningObject));
}
static fromDTO(dto: LearningPathDTO): LearningPath {