Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/documents/Assessable/ChoiceAnswer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import UnknownDocumentType from '@tdev-components/shared/Alert/UnknownDocumentTy
import Loader from '@tdev-components/Loader';
import useIsBrowser from '@docusaurus/useIsBrowser';
import { useDocumentRootId } from '@tdev-hooks/useContextDocumentRootId';
import { useFirstDocumentBy } from '@tdev-hooks/useFirstDocumentBy';
import { useNestedAssessableDocumentBy } from '@tdev-hooks/useNestedAssessableDocumentBy';
import { DocContext } from '@tdev-components/documents/DocumentContext';
import { AssessableComponentProps } from '@tdev-models/documents/Assessable/AssessableMeta';
import { type default as ChoiceAnswerModel, ModelMeta } from '@tdev-models/documents/Assessable/ChoiceAnswer';
Expand Down Expand Up @@ -45,7 +45,7 @@ const ChoiceAnswer = observer((props: ChoiceAnswerProps) => {
const [meta] = React.useState(new ModelMeta(props));
const docRootId = useDocumentRootId(props.id);

const doc = useFirstDocumentBy(docRootId, meta, props.qid);
const doc = useNestedAssessableDocumentBy(docRootId, meta, props.qid);
const isBrowser = useIsBrowser();

if (!doc) {
Expand Down
3 changes: 1 addition & 2 deletions src/components/documents/Assessable/Inputs/Option/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { observer } from 'mobx-react-lite';
import { useDocument } from '@tdev-hooks/useContextDocument';
import Button from '@tdev-components/shared/Button';
import { mdiTrashCanOutline } from '@mdi/js';
import { AssessableType, AssessableTypeModelMapping } from '@tdev-api/document';
import ChoiceAnswer from '@tdev-models/documents/Assessable/ChoiceAnswer';
import type { AssessableType, AssessableTypeModelMapping } from '@tdev-api/document';

export interface Props<T extends AssessableType> {
type?: T;
Expand Down
3 changes: 0 additions & 3 deletions src/components/documents/Assessable/Quiz/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { useFirstMainDocument } from '@tdev-hooks/useFirstMainDocument';
import { observer } from 'mobx-react-lite';
import React from 'react';
import UnknownDocumentType from '@tdev-components/shared/Alert/UnknownDocumentType';
import Loader from '@tdev-components/Loader';
import styles from './styles.module.scss';
import useIsBrowser from '@docusaurus/useIsBrowser';
import { DocumentRootIdContext } from '@tdev-hooks/useContextDocumentRootId';
import { AssessableComponentProps } from '@tdev-models/documents/Assessable/AssessableMeta';
import { ModelMeta } from '@tdev-models/documents/Assessable/Quiz';
Expand Down
4 changes: 2 additions & 2 deletions src/components/documents/Assessable/TrueFalseAnswer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ModelMeta
} from '@tdev-models/documents/Assessable/TrueFalseAnswer';
import { useDocumentRootId } from '@tdev-hooks/useContextDocumentRootId';
import { useFirstDocumentBy } from '@tdev-hooks/useFirstDocumentBy';
import { useNestedAssessableDocumentBy } from '@tdev-hooks/useNestedAssessableDocumentBy';
import UnknownDocumentType from '@tdev-components/shared/Alert/UnknownDocumentType';
import QuestionCard from '../QuestionCard';
import { DocContext } from '@tdev-components/documents/DocumentContext';
Expand Down Expand Up @@ -37,7 +37,7 @@ const onUpdateSelection = action((doc: TrueFalseAnswerModel, optionIndex: number
const TrueFalseAnswer = observer((props: Props) => {
const [meta] = React.useState(new ModelMeta({ ...props }));
const docRootId = useDocumentRootId(props.id);
const doc = useFirstDocumentBy(docRootId, meta, props.qid);
const doc = useNestedAssessableDocumentBy(docRootId, meta, props.qid);

if (!doc) {
return <UnknownDocumentType type={meta.type} />;
Expand Down
2 changes: 1 addition & 1 deletion src/components/documents/CmsText/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ export function useFirstCmsTextDocumentIfExists(id?: string): CmsText | undefine

// Not using useFirstMainDocument() here because that would always supply a (dummy) document.
const docRoot = useDocumentRoot(id, meta, false);
return docRoot?.firstMainDocument;
return docRoot?.documentsByType?.get(meta.type)?.[0] as CmsText | undefined;
}
6 changes: 5 additions & 1 deletion src/hooks/useDocumentRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ export const useDocumentRoot = <Type extends DocumentType>(
return reaction(
() => documentRootStore.find(dummyDocumentRoot.id)?._triggerDocumentReload,
() => {
const firstMainDoc = documentRootStore.find(dummyDocumentRoot.id)?.firstMainDocument;
const docRoot = documentRootStore.find(dummyDocumentRoot.id);
if (!docRoot) {
return;
}
const firstMainDoc = docRoot.documentsByType.get(meta.type)?.[0];
if (firstMainDoc) {
return;
}
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/useFirstMainDocument.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { DocumentType } from '@tdev-api/document';
import { DocumentType, TypeModelMapping } from '@tdev-api/document';
import { TypeMeta } from '@tdev-models/DocumentRoot';
import { useDocumentRoot } from '@tdev-hooks/useDocumentRoot';
import { useStore } from '@tdev-hooks/useStore';
Expand Down Expand Up @@ -65,6 +65,5 @@ export const useFirstMainDocument = <Type extends DocumentType>(
{ fireImmediately: true }
);
}, [userStore, createDocument, documentRoot]);

return documentRoot?.firstMainDocument || dummyDocument;
return (documentRoot?.documentsByType?.get(meta.type)?.[0] as TypeModelMapping[Type]) || dummyDocument;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import { reaction } from 'mobx';
import { DUMMY_DOCUMENT_ID } from './useFirstMainDocument';
import { AssessableMeta } from '@tdev-models/documents/Assessable/AssessableMeta';
import useLinkedMetaModel from './useLinkedMetaModel';
import _ from 'es-toolkit/compat';

const access = {} as Config;

const requested = new Set<string>();

/**
* This hook provides access to the first main document of the rootDocument.
* This is especially useful, when the DocumentType is expected to have only
Expand All @@ -19,7 +22,7 @@ const access = {} as Config;
* For bridging the time until the first main document is loaded,
* a dummy document is provided in the meantime.
*/
export const useFirstDocumentBy = <Type extends AssessableType>(
export const useNestedAssessableDocumentBy = <Type extends AssessableType>(
documentRootId: string | undefined,
/** ensure to put meta in a React.useState */
meta: AssessableMeta<Type>,
Expand Down Expand Up @@ -54,26 +57,48 @@ export const useFirstDocumentBy = <Type extends AssessableType>(
updatedAt: new Date().toISOString()
}) as AssessableTypeModelMapping[Type]
);
const [canRequest, setCanRequest] = React.useState(false);
React.useEffect(() => {
if (!documentRoot) {
return;
}
const timeoutId = setTimeout(() => {
setCanRequest(true);
}, 5);
return () => {
clearTimeout(timeoutId);
};
}, [documentRoot]);

React.useEffect(() => {
if (!documentRoot || !canRequest) {
return;
}
return reaction(
() => documentRoot?._canInitializeDocuments && !documentRoot.documents.some(selector),
(needsCreation) => {
if (!needsCreation) {
return;
}
documentStore.create({
documentRootId: documentRoot.id,
authorId: userStore.current!.id,
type: meta.type,
data: meta.defaultData
});
const key = `${documentRoot.id}::${userStore.current!.id}::${meta.type}::${qid}`;
if (requested.has(key)) {
return;
}
requested.add(key);
documentStore
.create({
documentRootId: documentRoot.id,
authorId: userStore.current!.id,
type: meta.type,
data: meta.defaultData
})
.then(() => {
requested.delete(key);
});
},
{ fireImmediately: true }
);
}, [userStore, documentRoot]);
}, [userStore, documentRoot, canRequest]);

const firstDoc = documentRoot?.documents.find(selector) as AssessableTypeModelMapping[Type] | undefined;
const doc = firstDoc || dummyDocument;
Expand Down
48 changes: 12 additions & 36 deletions src/models/DocumentRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,41 +227,16 @@ class DocumentRoot<T extends DocumentType> {
return this.store.root.userStore.viewedUserId;
}

/**
* All documents which
* - **don't have a parent**
* - having the **same type** as this document root
*
* @returns All main documents, **ordered by creation date**, oldest first.
*/
@computed
get mainDocuments(): TypeModelMapping[T][] {
const docs = orderBy(
this.documents.filter((d) => d.isMain),
['createdAt', 'id'],
['asc', 'asc']
) as TypeModelMapping[T][];
if (this.isDummy) {
return docs;
}
const byUser = docs.filter((d) => d.authorId === this.viewedUserId);

if (
this.store.root.userStore.current?.hasElevatedAccess &&
this.store.root.userStore.isUserSwitched
) {
return byUser;
}

if (NoneAccess.has(this.sharedAccess)) {
return byUser;
}
return [...byUser, ...docs.filter((d) => d.authorId !== this.viewedUserId)];
}

@computed
get firstMainDocument(): TypeModelMapping[T] | undefined {
return this.mainDocuments[0];
get documentsByType(): Map<DocumentType, TypeModelMapping[DocumentType][]> {
return orderBy(this.documents, ['createdAt', 'id'], ['asc', 'asc']).reduce((map, doc) => {
const docs = map.get(doc.type) || [];
if (docs.length === 0) {
map.set(doc.type, docs);
}
docs.push(doc);
return map;
}, new Map<DocumentType, TypeModelMapping[DocumentType][]>());
}

@action
Expand Down Expand Up @@ -300,12 +275,13 @@ class DocumentRoot<T extends DocumentType> {

@computed
get _needsInitialDocumentCreation() {
return this._canInitializeDocuments && !this.firstMainDocument;
return this._canInitializeDocuments && !this.documentsByType.has(this.meta.type);
}

@computed
get _triggerDocumentReload() {
return `${this.firstMainDocument?.id}-${this.store.root.userStore.viewedUserId}`;
const firstMainDoc = this.documentsByType.get(this.meta.type)?.[0];
return `${firstMainDoc?.id}-${this.store.root.userStore.viewedUserId}`;
}
}

Expand Down
8 changes: 0 additions & 8 deletions src/models/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,6 @@ export default class Page {
});
}

@computed
get documents() {
return this.documentRoots
.flatMap((doc) => doc.firstMainDocument)
.filter((d) => d?.root?.meta.pagePosition)
.sort((a, b) => a!.root!.meta!.pagePosition - b!.root!.meta.pagePosition);
}

@computed
get studentGroupName() {
const pathParts = this.path.split('/').filter((p) => p.length > 0);
Expand Down
9 changes: 5 additions & 4 deletions src/models/documents/Assessable/iAssessable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,14 @@ abstract class iAssessable<T extends AssessableType> extends iDocument<T> implem

@computed
get quiz(): Quiz | undefined {
if (!this.inQuiz || this.root?.firstMainDocument?.type !== 'quiz') {
if (!this.inQuiz) {
return undefined;
}
if (this.root.firstMainDocument.id === this.id) {
const firstQuiz = this.root?.documents.find((d) => d.type === 'quiz') as Quiz | undefined;
if (!firstQuiz || firstQuiz.id === this.id) {
return undefined;
}
return this.root.firstMainDocument;
return firstQuiz;
}

@computed
Expand All @@ -147,7 +148,7 @@ abstract class iAssessable<T extends AssessableType> extends iDocument<T> implem
if (!this.inQuiz || this.type === 'quiz') {
return null;
}
const quiz = this.root?.firstMainDocument;
const quiz = this.root?.documentsByType?.get('quiz')?.[0] as Quiz | undefined;
if (quiz?.type !== 'quiz') {
return null;
}
Expand Down
8 changes: 6 additions & 2 deletions src/models/documents/DynamicDocumentRoots/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,11 @@ class DynamicDocumentRoots<Type extends ContainerType> extends iDocument<'dynami
@computed
get linkedDocumentContainers(): ContainerTypeModelMapping[Type][] {
return this.linkedDynamicDocumentRoots
.flatMap((dr) => dr.firstMainDocument as ContainerTypeModelMapping[Type] | undefined)
.flatMap(
(dr) =>
dr.documentsByType.get(this.containerType)?.[0] as
ContainerTypeModelMapping[Type] | undefined
)
.filter((d) => !!d);
}

Expand All @@ -224,7 +228,7 @@ class DynamicDocumentRoots<Type extends ContainerType> extends iDocument<'dynami
return new Map<string, ContainerTypeModelMapping[Type]>(
this.linkedDynamicDocumentRoots.map((dr) => [
dr.id,
dr.firstMainDocument as ContainerTypeModelMapping[Type]
dr.documentsByType.get(this.containerType)?.[0] as ContainerTypeModelMapping[Type]
])
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/models/documents/ScriptVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ScriptVersion extends iDocument<'script_version'> {

@computed
get version() {
const script = this.root?.firstMainDocument as Script;
const script = this.root?.documentsByType.get('script')?.[0] as Script;
if (!script) {
return 0;
}
Expand Down
66 changes: 37 additions & 29 deletions src/stores/DocumentRootStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { action, computed, observable, runInAction } from 'mobx';
import { action, computed, observable, runInAction, transaction } from 'mobx';
import { RootStore } from '@tdev-stores/rootStore';
import { computedFn } from 'mobx-utils';
import DocumentRoot, { TypeMeta } from '@tdev-models/DocumentRoot';
Expand Down Expand Up @@ -325,36 +325,44 @@ export class DocumentRootStore extends iStore {
if (!documentRoot) {
return;
}
if (config.load.documentRoot) {
if (config.load.documentRoot === 'addIfMissing') {
const current = this.find(data.id);
if (!current || current.isUnknown) {
this.addDocumentRoot(documentRoot);
return transaction(() => {
if (config.load.documentRoot) {
if (config.load.documentRoot === 'addIfMissing') {
const current = this.find(data.id);
if (!current || current.isUnknown) {
this.addDocumentRoot(documentRoot);
}
} else {
this.addDocumentRoot(documentRoot, { cleanup: true, deep: false });
}
} else {
this.addDocumentRoot(documentRoot, { cleanup: true, deep: false });
}
}
if (config.load.groupPermissions) {
data.groupPermissions.forEach((gp) => {
this.root.permissionStore.addGroupPermission(
new GroupPermission({ ...gp, documentRootId: documentRoot.id }, this.root.permissionStore)
);
});
}
if (config.load.userPermissions) {
data.userPermissions.forEach((up) => {
this.root.permissionStore.addUserPermission(
new UserPermission({ ...up, documentRootId: documentRoot.id }, this.root.permissionStore)
);
});
}
if (config.load.documents) {
data.documents.forEach((doc) => {
this.root.documentStore.addToStore(doc);
});
}
return documentRoot;
if (config.load.groupPermissions) {
data.groupPermissions.forEach((gp) => {
this.root.permissionStore.addGroupPermission(
new GroupPermission(
{ ...gp, documentRootId: documentRoot.id },
this.root.permissionStore
)
);
});
}
if (config.load.userPermissions) {
data.userPermissions.forEach((up) => {
this.root.permissionStore.addUserPermission(
new UserPermission(
{ ...up, documentRootId: documentRoot.id },
this.root.permissionStore
)
);
});
}
if (config.load.documents) {
data.documents.forEach((doc) => {
this.root.documentStore.addToStore(doc);
});
}
return documentRoot;
});
}

@action
Expand Down
Loading