feat(frontend): Navigatie voor leerpad geïmplementeerd.
This commit is contained in:
parent
3c3fddb7d0
commit
07340de2e3
13 changed files with 216 additions and 54 deletions
|
@ -8,3 +8,15 @@ export class HttpErrorStatusException extends Error {
|
|||
this.statusCode = response.status;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidResponseException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
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> {
|
||||
return super.request(pathParams, queryParams, undefined, "document");
|
||||
}
|
||||
}
|
|
@ -1,23 +1,28 @@
|
|||
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";
|
||||
import {HttpErrorStatusException} from "@/services/api-client/api-exceptions.ts";
|
||||
import type {ResponseType} from "axios";
|
||||
|
||||
export abstract class RestEndpoint<PP extends Params, QP extends Params, B, R> {
|
||||
public abstract readonly method: "GET" | "POST" | "PUT" | "DELETE";
|
||||
constructor(public readonly url: string) {
|
||||
}
|
||||
|
||||
protected request(pathParams: PP, queryParams: QP, body: B): RemoteResource<R> {
|
||||
let urlFilledIn = this.url;
|
||||
urlFilledIn.replace(/:(\w+)([/$])/g, (_, key, after) =>
|
||||
(key in pathParams ? encodeURIComponent(pathParams[key]) : `:${key}`) + after
|
||||
protected request(pathParams: PP, queryParams: QP, body: B, responseType?: ResponseType): RemoteResource<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);
|
||||
|
@ -27,4 +32,4 @@ export abstract class RestEndpoint<PP extends Params, QP extends Params, B, R> {
|
|||
}
|
||||
}
|
||||
|
||||
export type Params = {[key: string]: string | number | boolean};
|
||||
export type Params = {[key: string]: string | number | boolean | undefined};
|
||||
|
|
|
@ -2,22 +2,40 @@ export class RemoteResource<T> {
|
|||
static NOT_LOADED: NotLoadedState = {type: "notLoaded"};
|
||||
static LOADING: LoadingState = {type: "loading"};
|
||||
|
||||
private state: NotLoadedState | LoadingState | ErrorState | SuccessState<T> = RemoteResource.NOT_LOADED;
|
||||
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;
|
||||
this._state = RemoteResource.LOADING;
|
||||
try {
|
||||
let resource = await this.requestFn();
|
||||
this.state = {
|
||||
this._state = {
|
||||
type: "success",
|
||||
data: resource
|
||||
};
|
||||
return resource;
|
||||
} catch (e: any) {
|
||||
this.state = {
|
||||
this._state = {
|
||||
type: "error",
|
||||
errorCode: e.statusCode,
|
||||
message: e.message,
|
||||
|
@ -31,19 +49,23 @@ export class RemoteResource<T> {
|
|||
return this;
|
||||
}
|
||||
|
||||
public get state(): RemoteResourceState<T> {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public get data(): T | undefined {
|
||||
if (this.state.type === "success") {
|
||||
return this.state.data;
|
||||
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;
|
||||
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.");
|
||||
|
@ -52,19 +74,20 @@ export class RemoteResource<T> {
|
|||
}
|
||||
}
|
||||
|
||||
type NotLoadedState = {
|
||||
export type NotLoadedState = {
|
||||
type: "notLoaded"
|
||||
};
|
||||
type LoadingState = {
|
||||
export type LoadingState = {
|
||||
type: "loading"
|
||||
};
|
||||
type ErrorState = {
|
||||
export type ErrorState = {
|
||||
type: "error",
|
||||
errorCode?: number,
|
||||
message?: string,
|
||||
error: any
|
||||
};
|
||||
type SuccessState<T> = {
|
||||
export type SuccessState<T> = {
|
||||
type: "success",
|
||||
data: T
|
||||
}
|
||||
};
|
||||
export type RemoteResourceState<T> = NotLoadedState | LoadingState | ErrorState | SuccessState<T>;
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
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";
|
||||
|
||||
const searchLearningPathsEndpoint = new GetEndpoint<{}, {query: string}, LearningPathDTO[]>(
|
||||
"/learningObjects/:query"
|
||||
const learningPathEndpoint = new GetEndpoint<{}, {search?: string, hruid?: string, language?: Language}, LearningPathDTO[]>(
|
||||
"/learningPath"
|
||||
);
|
||||
|
||||
export function searchLearningPaths(query: string): RemoteResource<LearningPath[]> {
|
||||
return searchLearningPathsEndpoint
|
||||
.get({}, {query: query})
|
||||
return learningPathEndpoint
|
||||
.get({}, {search: query})
|
||||
.map(dtos => 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)));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type {Language} from "@/services/learning-content/language.ts";
|
||||
import type {RemoteResource} from "@/services/api-client/remote-resource.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";
|
||||
|
||||
|
@ -98,6 +98,21 @@ export class LearningPath {
|
|||
) {
|
||||
}
|
||||
|
||||
public get nodesAsList(): LearningPathNode[] {
|
||||
let list: LearningPathNode[] = [];
|
||||
let currentNode = this.startNode;
|
||||
while (currentNode) {
|
||||
list.push(currentNode);
|
||||
currentNode = currentNode.transitions.filter(it => it.default)[0]?.next
|
||||
|| currentNode.transitions[0]?.next;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public get learningObjectsAsList(): RemoteResource<LearningObject[]> {
|
||||
return RemoteResource.join(this.nodesAsList.map(node => node.learningObject));
|
||||
}
|
||||
|
||||
static fromDTO(dto: LearningPathDTO): LearningPath {
|
||||
let startNodeDto = dto.nodes.filter(it => it.start_node);
|
||||
if (startNodeDto.length !== 1) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue