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
85 changes: 46 additions & 39 deletions src/api/OfflineApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,56 @@ export default class OfflineApi {

// Method to handle POST requests
async post<T = any>(url: string, data: T, ...config: any): AxiosPromise<T> {
const { model, id, query } = urlParts(url);
const { model, id, query, parts } = urlParts(url);
log('post', url, data);
switch (model) {
case 'admin':
return resolveResponse(data as unknown as T);
case 'cms':
return resolveResponse({} as unknown as T);
case 'users':
if (parts.length === 1 && parts[0] === 'documentRoots') {
const { documentRootIds, type } = data as {
documentRootIds: string[];
type?: DocumentType;
};
if (documentRootIds.length === 0) {
resolveResponse([] as unknown as T);
}
const documentRootDocs = await Promise.all(
documentRootIds.map((id) => this.documentsBy(id))
);
const filteredDocs = type
? documentRootDocs.map((docs) => docs.filter((doc) => doc.type === type))
: documentRootDocs;

const documenRoots = documentRootIds.map((rid) => {
return {
id: rid,
access: Access.RW_DocumentRoot,
sharedAccess: Access.RW_DocumentRoot,
userPermissions: [],
groupPermissions: [],
documents:
filteredDocs.find(
(docs) => docs.length > 0 && docs[0].documentRootId === rid
) || []
};
}) as unknown as T;
log('-> post', url, documenRoots);
return resolveResponse(documenRoots);
}
return resolveResponse([] as unknown as T);
case 'documents':
if (url === 'documents/multiple') {
const documentRootIds = new Set((data as { documentRootIds: string[] }).documentRootIds);
const allDocuments = await this.dbAdapter.getAll<Document<any>>(DOCUMENTS_STORE);

const filteredDocuments = allDocuments.filter((doc) =>
documentRootIds.has(doc.documentRootId)
);
return resolveResponse(filteredDocuments as unknown as T);
}
const document = await this.upsertDocumentRecord<any>(
data as Partial<Document<any>>,
query.has('uniqueMain')
Expand Down Expand Up @@ -214,35 +256,6 @@ export default class OfflineApi {
switch (model) {
case 'user':
return resolveResponse(OfflineUser as unknown as T);
case 'users':
if (parts.length === 1 && parts[0] === 'documentRoots') {
const ids = query.getAll('ids');
const docType = query.get('type') as DocumentType | null;
if (ids.length === 0) {
resolveResponse([] as unknown as T);
}
const documentRootDocs = await Promise.all(ids.map((id) => this.documentsBy(id)));
const filteredDocs = docType
? documentRootDocs.map((docs) => docs.filter((doc) => doc.type === docType))
: documentRootDocs;

const documenRoots = ids.map((rid) => {
return {
id: rid,
access: Access.RW_DocumentRoot,
sharedAccess: Access.RW_DocumentRoot,
userPermissions: [],
groupPermissions: [],
documents:
filteredDocs.find(
(docs) => docs.length > 0 && docs[0].documentRootId === rid
) || []
};
}) as unknown as T;
log('-> get', url, documenRoots);
return resolveResponse(documenRoots);
}
return resolveResponse([OfflineUser] as unknown as T);
case 'admin':
return resolveResponse([] as unknown as T);
case 'allowedActions':
Expand All @@ -255,6 +268,7 @@ export default class OfflineApi {
}
return resolveResponse(null);
}
// TODO: is this needed/used at all?
if (query.has('ids')) {
const ids = query.getAll('ids');
const filteredDocuments: Document<any>[] = [];
Expand All @@ -266,19 +280,12 @@ export default class OfflineApi {
}
return resolveResponse(filteredDocuments as unknown as T);
}
if (query.has('rids')) {
const rids = query.getAll('rids');

const allDocuments = await this.dbAdapter.getAll<Document<any>>(DOCUMENTS_STORE);

const filteredDocuments = allDocuments.filter((doc) => rids.includes(doc.documentRootId));

return resolveResponse(filteredDocuments as unknown as T);
}

return resolveResponse(
(await this.dbAdapter.getAll<Document<any>>(DOCUMENTS_STORE)) as unknown as T
);
case 'users':
return resolveResponse([OfflineUser] as unknown as T);
case 'documentRoots':
if (parts[0] === 'permissions') {
return resolveResponse({
Expand Down
4 changes: 1 addition & 3 deletions src/api/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,7 @@ export function update<Type extends DocumentType>(
* TODO: would it be better to only grab documents from a specific student group?
*/
export function allDocuments(documentRootIds: string[], signal: AbortSignal): AxiosPromise<Document<any>[]> {
return api.get(`/documents?${documentRootIds.map((id) => `rids=${id}`).join('&')}`, {
signal
});
return api.post('/documents/multiple', { documentRootIds }, { signal });
}

export function linkTo<Type extends DocumentType>(
Expand Down
15 changes: 14 additions & 1 deletion src/api/documentRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,20 @@ export function findManyFor(
if (documentType) {
params.append('type', documentType);
}
return api.get(`/users/${userId}/documentRoots?${params.toString()}`, { signal });
const data: {
documentRootIds: string[];
ignoreMissingRoots?: boolean;
type?: string;
} = {
documentRootIds: ids
};
if (ignoreMissingRoots) {
data.ignoreMissingRoots = true;
}
if (documentType) {
data.type = documentType;
}
return api.post(`/users/${userId}/documentRoots`, data, { signal });
}

export function create(
Expand Down
9 changes: 9 additions & 0 deletions src/models/Page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,20 @@ export default class Page {
.sort((a, b) => a!.root!.meta!.pagePosition - b!.root!.meta.pagePosition);
}

@computed
get primaryStudentGroupName() {
return this._primaryStudentGroupName ?? this.store.currentStudentGroupName;
}

@action
setPrimaryStudentGroupName(name?: string) {
this._primaryStudentGroupName = name;
}

get hasCustomPrimaryStudentGroup() {
return !!this._primaryStudentGroupName;
}

@computed
get primaryStudentGroup() {
return this._primaryStudentGroupName
Expand Down
12 changes: 0 additions & 12 deletions src/stores/DocumentRootStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,12 @@ export class DocumentRootStore extends iStore {
access: accessConfig || {}
});
this.loadQueued();
if (this.queued.size > 42) {
// max 2048 characters in URL - flush if too many
this.loadQueued.flush();
}
}

/**
* load the documentRoots only
* - after 20 ms of "silence" (=no further load-requests during this period)
* - or after 25ms have elapsed
* - or when more then 42 records are queued (@see loadInNextBatch)
* (otherwise the URL maxlength would be reached)
*/
loadQueued = _.debounce(action(this._loadQueued), 25, {
leading: false,
Expand All @@ -192,12 +186,6 @@ export class DocumentRootStore extends iStore {
}
const batch = [...this.queued];
this.queued.clear();
if (batch.length > 42) {
const postponed = batch.splice(42);
postponed.forEach((item) => this.queued.set(item[0], item[1]));
this.loadQueued();
console.log('Postponing', postponed.length, 'document roots for next batch');
}
const currentBatch = new Map(batch);
/**
* if the user is not logged in, we can't load the documents
Expand Down
85 changes: 75 additions & 10 deletions src/stores/PageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import globalData from '@generated/globalData';
const ensureTrailingSlash = (str: string) => {
return str.endsWith('/') ? str : `${str}/`;
};
const ensureLeadingSlash = (str: string) => {
return str.startsWith('/') ? str : `/${str}`;
};
const BasePathRegex = new RegExp(`^${siteConfig.baseUrl}`, 'i');
export const AUTO_GENERATED_PAGE_PREFIX = '__auto_generated__';
export const SidebarVersions = (
globalData['docusaurus-plugin-content-docs'].default as GlobalPluginData
Expand Down Expand Up @@ -43,12 +47,14 @@ interface PagesIndex {
export class PageStore extends iStore {
readonly root: RootStore;

pages = observable<Page>([]);
pages = observable<Page>([], { deep: false });

@observable accessor currentPageId: string | undefined = undefined;
@observable accessor runningTurtleScriptId: string | undefined = undefined;

@observable.ref accessor _pageIndex: PageIndex[] = [];
@observable accessor currentPath: string | undefined = undefined;
loadedPageIndices = new Set<string>();

constructor(store: RootStore) {
super();
Expand All @@ -59,14 +65,24 @@ export class PageStore extends iStore {
return SidebarVersions;
}

@computed
get sidebarVersionPaths() {
return SidebarVersions.map((version) => version.versionPath);
}

@computed
get landingPages() {
return this.pages.filter((page) => page.isLandingpage);
}

@computed
get isPageIndexLoaded() {
return this._pageIndex.length > 0;
}

@action
loadPageIndex(force: boolean = false) {
if (!force && this._pageIndex.length > 0) {
if (!force && this.isPageIndexLoaded) {
return Promise.resolve();
}
return fetch(`${siteConfig.baseUrl}tdev-artifacts/page-progress-state/pageIndex.json`)
Expand Down Expand Up @@ -96,23 +112,72 @@ export class PageStore extends iStore {
})
)
.then(() => {
this.loadTaskableDocuments();
if (this.currentPath) {
this.loadTaskableDocuments(this.currentStudentGroupName);
}
})
.catch((err) => {
console.error('Failed to load page index', err);
});
}

@action
loadTaskableDocuments() {
this.pages.forEach((page) => {
page.taskableDocumentRootIds.forEach((id) => {
this.root.documentRootStore.loadInNextBatch(id, undefined, {
skipCreate: true,
documentRoot: 'addIfMissing'
setCurrentPath(path: string | undefined) {
if (path === this.currentPath) {
return;
}
if (!path) {
this.currentPath = undefined;
return;
}
this.currentPath = path.replace(BasePathRegex, '/');
if (this.isPageIndexLoaded) {
this.loadTaskableDocuments(this.currentStudentGroupName);
}
this.resetPagesStudentGroups();
}

@action
resetPagesStudentGroups() {
this.pages
.filter((p) => p.hasCustomPrimaryStudentGroup)
.forEach((p) => p.setPrimaryStudentGroupName(undefined));
}

@computed
get currentPathParts() {
if (!this.currentPath) {
return [];
}
return this.currentPath.split('/').filter((p) => p.length > 0);
}

@computed
get currentStudentGroupName() {
return this.currentPathParts[0];
}

@action
loadTaskableDocuments(pathPrefix: string | undefined, force?: boolean) {
const prefix = ensureLeadingSlash(ensureTrailingSlash(pathPrefix ?? ''));
const isEntryPoint = this.sidebarVersionPaths.includes(prefix);
if (!isEntryPoint) {
return;
}
if (!force && this.loadedPageIndices.has(prefix)) {
return;
}
this.loadedPageIndices.add(prefix);
this.pages
.filter((p) => p.path.startsWith(prefix) && !p.isAutoGenerated)
.forEach((page) => {
page.taskableDocumentRootIds.forEach((id) => {
this.root.documentRootStore.loadInNextBatch(id, undefined, {
skipCreate: true,
documentRoot: 'addIfMissing'
});
});
});
});
}

find = computedFn(
Expand Down
2 changes: 1 addition & 1 deletion src/stores/UserStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class UserStore extends iStore<`update-${string}`> {
return this.withAbortController(`load-all`, async (ct) => {
return apiAll(ct.signal).then(
action((res) => {
const models = res.data.map((d) => this.createModel(d));
const models = res.data?.map((d) => this.createModel(d));
this.users.replace(models);
})
);
Expand Down
27 changes: 0 additions & 27 deletions src/theme/DocItem/Content/index.tsx

This file was deleted.

Loading