feat(frontend): LearningObjectService en LearningPathService geïmplementeerd.
This commit is contained in:
parent
8b0fc4263f
commit
3c3fddb7d0
24 changed files with 375 additions and 84 deletions
39
frontend/src/components/UsingRemoteResource.vue
Normal file
39
frontend/src/components/UsingRemoteResource.vue
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<script setup lang="ts" generic="T">
|
||||||
|
import {RemoteResource} from "@/services/api-client/remote-resource.ts";
|
||||||
|
import {computed} from "vue";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
resource: RemoteResource<T>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const isLoading = computed(() => props.resource.state.type === 'loading');
|
||||||
|
const isError = computed(() => props.resource.state.type === 'error');
|
||||||
|
|
||||||
|
// `data` will be correctly inferred as `T`
|
||||||
|
const data = computed(() => props.resource.data);
|
||||||
|
|
||||||
|
const error = computed(() => props.resource.state.type === "error" ? props.resource.state : null);
|
||||||
|
const errorMessage = computed(() =>
|
||||||
|
error.value?.message ? error.value.message : JSON.stringify(error.value?.error)
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="isLoading">
|
||||||
|
<v-progress-circular indeterminate></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
<div v-if="isError">
|
||||||
|
<v-empty-state
|
||||||
|
icon="mdi-alert-circle-outline"
|
||||||
|
text="errorMessage"
|
||||||
|
:title=t("error_title")
|
||||||
|
></v-empty-state>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"welcome": "Willkommen"
|
"welcome": "Willkommen",
|
||||||
|
"error_title": "Fehler"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,6 @@
|
||||||
"assignments": "assignments",
|
"assignments": "assignments",
|
||||||
"classes": "classes",
|
"classes": "classes",
|
||||||
"discussions": "discussions",
|
"discussions": "discussions",
|
||||||
"logout": "log out"
|
"logout": "log out",
|
||||||
|
"error_title": "Error"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"welcome": "Bienvenue"
|
"welcome": "Bienvenue",
|
||||||
|
"error_title": "Erreur"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,6 @@
|
||||||
"assignments": "opdrachten",
|
"assignments": "opdrachten",
|
||||||
"classes": "klassen",
|
"classes": "klassen",
|
||||||
"discussions": "discussies",
|
"discussions": "discussies",
|
||||||
"logout": "log uit"
|
"logout": "log uit",
|
||||||
|
"error_title": "Fout"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import i18n from "./i18n/i18n.ts";
|
||||||
// Components
|
// Components
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
import {aliases, mdi} from "vuetify/iconsets/mdi";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
@ -23,6 +24,13 @@ document.head.appendChild(link);
|
||||||
const vuetify = createVuetify({
|
const vuetify = createVuetify({
|
||||||
components,
|
components,
|
||||||
directives,
|
directives,
|
||||||
|
icons: {
|
||||||
|
defaultSet: "mdi",
|
||||||
|
aliases,
|
||||||
|
sets: {
|
||||||
|
mdi
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
app.use(vuetify);
|
app.use(vuetify);
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
|
|
10
frontend/src/services/api-client/api-exceptions.d.ts
vendored
Normal file
10
frontend/src/services/api-client/api-exceptions.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import type {AxiosResponse} from "axios";
|
||||||
|
|
||||||
|
export class HttpErrorStatusException extends Error {
|
||||||
|
public readonly statusCode: number;
|
||||||
|
|
||||||
|
constructor(response: AxiosResponse<any, any>) {
|
||||||
|
super(`${response.statusText} (${response.status})`);
|
||||||
|
this.statusCode = response.status;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 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> {
|
||||||
|
return super.request(pathParams, queryParams, undefined);
|
||||||
|
}
|
||||||
|
}
|
10
frontend/src/services/api-client/endpoints/get-endpoint.ts
Normal file
10
frontend/src/services/api-client/endpoints/get-endpoint.ts
Normal file
|
@ -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 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> {
|
||||||
|
return super.request(pathParams, queryParams, undefined);
|
||||||
|
}
|
||||||
|
}
|
10
frontend/src/services/api-client/endpoints/post-endpoint.ts
Normal file
10
frontend/src/services/api-client/endpoints/post-endpoint.ts
Normal file
|
@ -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 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> {
|
||||||
|
return super.request(pathParams, queryParams, body);
|
||||||
|
}
|
||||||
|
}
|
30
frontend/src/services/api-client/endpoints/rest-endpoint.ts
Normal file
30
frontend/src/services/api-client/endpoints/rest-endpoint.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
return new RemoteResource(async () => {
|
||||||
|
const response = await apiClient.request<R>({
|
||||||
|
url: urlFilledIn,
|
||||||
|
method: this.method,
|
||||||
|
params: queryParams,
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
if (response.status / 100 !== 2) {
|
||||||
|
throw new HttpErrorStatusException(response);
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Params = {[key: string]: string | number | boolean};
|
70
frontend/src/services/api-client/remote-resource.ts
Normal file
70
frontend/src/services/api-client/remote-resource.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
constructor(private readonly requestFn: () => Promise<T>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotLoadedState = {
|
||||||
|
type: "notLoaded"
|
||||||
|
};
|
||||||
|
type LoadingState = {
|
||||||
|
type: "loading"
|
||||||
|
};
|
||||||
|
type ErrorState = {
|
||||||
|
type: "error",
|
||||||
|
errorCode?: number,
|
||||||
|
message?: string,
|
||||||
|
error: any
|
||||||
|
};
|
||||||
|
type SuccessState<T> = {
|
||||||
|
type: "success",
|
||||||
|
data: T
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import apiClient from "@/services/api-client.ts";
|
import apiClient from "@/services/api-client/api-client.ts";
|
||||||
import type { FrontendAuthConfig } from "@/services/auth/auth.d.ts";
|
import type { FrontendAuthConfig } from "@/services/auth/auth.d.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { User, UserManager } from "oidc-client-ts";
|
||||||
import { loadAuthConfig } from "@/services/auth/auth-config-loader.ts";
|
import { loadAuthConfig } from "@/services/auth/auth-config-loader.ts";
|
||||||
import authStorage from "./auth-storage.ts";
|
import authStorage from "./auth-storage.ts";
|
||||||
import { loginRoute } from "@/config.ts";
|
import { loginRoute } from "@/config.ts";
|
||||||
import apiClient from "@/services/api-client.ts";
|
import apiClient from "@/services/api-client/api-client.ts";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
33
frontend/src/services/learning-content/learning-object.ts
Normal file
33
frontend/src/services/learning-content/learning-object.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import type {Language} from "@/services/learning-content/language.ts";
|
||||||
|
|
||||||
|
export interface EducationalGoal {
|
||||||
|
source: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReturnValue {
|
||||||
|
callback_url: string;
|
||||||
|
callback_schema: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LearningObject {
|
||||||
|
key: string;
|
||||||
|
_id: string;
|
||||||
|
uuid: string;
|
||||||
|
version: number;
|
||||||
|
title: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
language: Language;
|
||||||
|
difficulty: number;
|
||||||
|
estimatedTime?: number;
|
||||||
|
available: boolean;
|
||||||
|
teacherExclusive: boolean;
|
||||||
|
educationalGoals: EducationalGoal[];
|
||||||
|
keywords: string[];
|
||||||
|
description: string;
|
||||||
|
targetAges: number[];
|
||||||
|
contentType: string;
|
||||||
|
contentLocation?: string;
|
||||||
|
skosConcepts?: string[];
|
||||||
|
returnValue?: ReturnValue;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
const searchLearningPathsEndpoint = new GetEndpoint<{}, {query: string}, LearningPathDTO[]>(
|
||||||
|
"/learningObjects/:query"
|
||||||
|
);
|
||||||
|
|
||||||
|
export function searchLearningPaths(query: string): RemoteResource<LearningPath[]> {
|
||||||
|
return searchLearningPathsEndpoint
|
||||||
|
.get({}, {query: query})
|
||||||
|
.map(dtos => dtos.map(dto => LearningPath.fromDTO(dto)));
|
||||||
|
}
|
118
frontend/src/services/learning-content/learning-path.ts
Normal file
118
frontend/src/services/learning-content/learning-path.ts
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import type {Language} from "@/services/learning-content/language.ts";
|
||||||
|
import type {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";
|
||||||
|
|
||||||
|
export interface LearningPathDTO {
|
||||||
|
language: string;
|
||||||
|
hruid: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
image?: string; // Image might be missing, so it's optional
|
||||||
|
num_nodes: number;
|
||||||
|
num_nodes_left: number;
|
||||||
|
nodes: LearningPathNodeDTO[];
|
||||||
|
keywords: string;
|
||||||
|
target_ages: number[];
|
||||||
|
min_age: number;
|
||||||
|
max_age: number;
|
||||||
|
__order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LearningPathNodeDTO {
|
||||||
|
_id: string;
|
||||||
|
learningobject_hruid: string;
|
||||||
|
version: number;
|
||||||
|
language: Language;
|
||||||
|
start_node?: boolean;
|
||||||
|
transitions: LearningPathTransitionDTO[];
|
||||||
|
created_at: string;
|
||||||
|
updatedAt: string;
|
||||||
|
done?: boolean; // True if a submission exists for this node by the user for whom the learning path is customized.
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LearningPathTransitionDTO {
|
||||||
|
default: boolean;
|
||||||
|
_id: string;
|
||||||
|
next: {
|
||||||
|
_id: string;
|
||||||
|
hruid: string;
|
||||||
|
version: number;
|
||||||
|
language: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LearningPathNode {
|
||||||
|
public learningObject: RemoteResource<LearningObject>
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly learningobjectHruid: string,
|
||||||
|
public readonly version: number,
|
||||||
|
public readonly language: Language,
|
||||||
|
public readonly transitions: {next: LearningPathNode, default: boolean}[],
|
||||||
|
public readonly createdAt: Date,
|
||||||
|
public readonly updatedAt: Date
|
||||||
|
) {
|
||||||
|
this.learningObject = getLearningObjectMetadata(learningobjectHruid, language, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromDTOAndOtherNodes(dto: LearningPathNodeDTO, otherNodes: LearningPathNodeDTO[]): LearningPathNode {
|
||||||
|
return new LearningPathNode(
|
||||||
|
dto.learningobject_hruid,
|
||||||
|
dto.version,
|
||||||
|
dto.language,
|
||||||
|
dto.transitions.map(transDto => {
|
||||||
|
let nextNodeDto = otherNodes.filter(it =>
|
||||||
|
it.learningobject_hruid === transDto.next.hruid
|
||||||
|
&& it.language === transDto.next.language
|
||||||
|
&& it.version === transDto.next.version
|
||||||
|
);
|
||||||
|
if (nextNodeDto.length !== 1) {
|
||||||
|
throw new Error(`Invalid learning path! There is a transition to node`
|
||||||
|
+ `${transDto.next.hruid}/${transDto.next.language}/${transDto.next.version}, but there are`
|
||||||
|
+ `${nextNodeDto.length} such nodes.`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
next: LearningPathNode.fromDTOAndOtherNodes(nextNodeDto[0], otherNodes),
|
||||||
|
default: transDto.default
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Date(dto.created_at),
|
||||||
|
new Date(dto.updatedAt),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LearningPath {
|
||||||
|
constructor(
|
||||||
|
public readonly language: string,
|
||||||
|
public readonly hruid: string,
|
||||||
|
public readonly title: string,
|
||||||
|
public readonly description: string,
|
||||||
|
public readonly amountOfNodes: number,
|
||||||
|
public readonly amountOfNodesLeft: number,
|
||||||
|
public readonly keywords: string[],
|
||||||
|
public readonly targetAges: {min: number; max: number},
|
||||||
|
public readonly startNode: LearningPathNode,
|
||||||
|
public readonly image?: string // Image might be missing, so it's optional
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromDTO(dto: LearningPathDTO): LearningPath {
|
||||||
|
let startNodeDto = dto.nodes.filter(it => it.start_node);
|
||||||
|
if (startNodeDto.length !== 1) {
|
||||||
|
throw new Error(`Invalid learning path! Expected precisely one start node, but there were ${startNodeDto.length}.`);
|
||||||
|
}
|
||||||
|
return new LearningPath(
|
||||||
|
dto.language,
|
||||||
|
dto.hruid,
|
||||||
|
dto.title,
|
||||||
|
dto.description,
|
||||||
|
dto.num_nodes,
|
||||||
|
dto.num_nodes_left,
|
||||||
|
dto.keywords.split(' '),
|
||||||
|
{min: dto.min_age, max: dto.max_age},
|
||||||
|
LearningPathNode.fromDTOAndOtherNodes(startNodeDto[0], dto.nodes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
import type {Language} from "@/services/learning-paths/language.ts";
|
|
||||||
|
|
||||||
export interface LearningPathIdentifier {
|
|
||||||
hruid: string;
|
|
||||||
language: Language;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EducationalGoal {
|
|
||||||
source: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReturnValue {
|
|
||||||
callback_url: string;
|
|
||||||
callback_schema: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LearningObjectMetadata {
|
|
||||||
_id: string;
|
|
||||||
uuid: string;
|
|
||||||
hruid: string;
|
|
||||||
version: number;
|
|
||||||
language: Language;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
difficulty: number;
|
|
||||||
estimated_time: number;
|
|
||||||
available: boolean;
|
|
||||||
teacher_exclusive: boolean;
|
|
||||||
educational_goals: EducationalGoal[];
|
|
||||||
keywords: string[];
|
|
||||||
target_ages: number[];
|
|
||||||
content_type: string; // Markdown, image, etc.
|
|
||||||
content_location?: string;
|
|
||||||
skos_concepts?: string[];
|
|
||||||
return_value?: ReturnValue;
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import type {Language} from "@/services/learning-paths/language.ts";
|
|
||||||
|
|
||||||
export interface LearningPath {
|
|
||||||
language: string;
|
|
||||||
hruid: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
image?: string; // Image might be missing, so it's optional
|
|
||||||
num_nodes: number;
|
|
||||||
num_nodes_left: number;
|
|
||||||
nodes: LearningObjectNode[];
|
|
||||||
keywords: string;
|
|
||||||
target_ages: number[];
|
|
||||||
min_age: number;
|
|
||||||
max_age: number;
|
|
||||||
__order: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LearningObjectNode {
|
|
||||||
_id: string;
|
|
||||||
learningobject_hruid: string;
|
|
||||||
version: number;
|
|
||||||
language: Language;
|
|
||||||
start_node?: boolean;
|
|
||||||
transitions: Transition[];
|
|
||||||
created_at: string;
|
|
||||||
updatedAt: string;
|
|
||||||
done?: boolean; // True if a submission exists for this node by the user for whom the learning path is customized.
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Transition {
|
|
||||||
default: boolean;
|
|
||||||
_id: string;
|
|
||||||
next: {
|
|
||||||
_id: string;
|
|
||||||
hruid: string;
|
|
||||||
version: number;
|
|
||||||
language: string;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import auth from "@/services/auth/auth-service.ts";
|
import auth from "@/services/auth/auth-service.ts";
|
||||||
import apiClient from "@/services/api-client.ts";
|
import apiClient from "@/services/api-client/api-client.ts";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
const testResponse = ref(null);
|
const testResponse = ref(null);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue