Skip to content

Commit 81632f1

Browse files
authored
Merge pull request #2456 from concord-consortium/CLUE-12-teacher-documents-as-curriculum
CLUE-12 Teacher documents as curriculum
2 parents 387ee1d + 9cd4cfb commit 81632f1

34 files changed

+1553
-452
lines changed

cypress/e2e/functional/document_tests/bookmark_test_spec.js

+13
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@ context('Bookmarks', function () {
4747
resourcesPanel.getCanvasItemTitle('my-work', 'bookmarks').contains(copyDocumentTitle).should('exist');
4848
resourcesPanel.getCanvasItemTitle('my-work', 'bookmarks').contains(title).should('exist');
4949

50+
cy.log('verify second document can be opened in bookmarks panel');
51+
resourcesPanel.getFocusDocumentTitle().contains(title);
52+
resourcesPanel.getCanvasItemTitle('my-work', 'bookmarks').contains(copyDocumentTitle).click();
53+
// There is confusing behavior here where the first click on another document doesn't open this as the
54+
// the second document, instead it toggles the primary document to be this one. This happens
55+
// because there is no explicit primary document initially. So this first click tries to set this
56+
// secondary document but because there is no primary the persistent ui stores it as the primary doc.
57+
resourcesPanel.getFocusDocumentTitle().contains(copyDocumentTitle);
58+
// Now clicking back on the first document in the list should open it as the "secondary" document.
59+
resourcesPanel.getCanvasItemTitle('my-work', 'bookmarks').contains(title).click();
60+
resourcesPanel.getPrimaryFocusDocumentTitle().contains(copyDocumentTitle);
61+
resourcesPanel.getSecondaryFocusDocumentTitle().contains(title);
62+
5063
cy.log('verify publish personal document');
5164
canvas.publishCanvas("personal");
5265
resourcesPanel.openTopTab('class-work');

cypress/support/elements/common/ResourcesPanel.js

+22
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,27 @@ class ResourcesPanel{
9999
// but the top level (navTab) of this sub tab is not active/selected.
100100
return cy.get('.nav-tab-panel .react-tabs__tab-panel--selected .react-tabs__tab-panel--selected .edit-button');
101101
}
102+
103+
getFocusDocument() {
104+
return cy.get('.focus-document');
105+
}
106+
107+
getFocusDocumentTitle() {
108+
return this.getFocusDocument().find('.document-title');
109+
}
110+
111+
// The Primary and Secondary documents are used in the bookmarks tab
112+
getPrimaryFocusDocument() {
113+
return cy.get('.focus-document.primary');
114+
}
115+
getPrimaryFocusDocumentTitle() {
116+
return this.getFocusDocument().find('.document-title');
117+
}
118+
getSecondaryFocusDocument() {
119+
return cy.get('.focus-document.secondary');
120+
}
121+
getSecondaryFocusDocumentTitle() {
122+
return this.getFocusDocument().find('.document-title');
123+
}
102124
}
103125
export default ResourcesPanel;

src/clue/components/teacher/teacher-group-six-pack-fourup.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const TeacherGroupHeader: React.FC<IGroupHeaderProps> = observer(function Teache
8787
{group, navTabName, documentViewMode}){
8888
const { ui, persistentUI, db, groups, user: { isResearcher } } = useStores();
8989

90-
const openDocId = persistentUI.tabs.get(navTabName)?.openDocuments.get(group.id);
90+
const openDocId = persistentUI.tabs.get(navTabName)?.getDocumentGroup(group.id)?.primaryDocumentKey;
9191
const groupModel = groups.getGroupById(group.id);
9292
const focusedGroupUser = getFocusedGroupUser(groupModel, openDocId, documentViewMode);
9393

src/components/chat/commented-documents.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { useFirestore } from "../../hooks/firestore-hooks";
55
import { useStores } from "../../hooks/use-stores";
66
import { useDocumentCaption } from "../../hooks/use-document-caption";
77
import { UserModelType } from "../../models/stores/user";
8-
import { getNavTabOfDocument, getTabsOfCurriculumDoc, isStudentWorkspaceDoc } from "../../models/stores/persistent-ui";
8+
import {
9+
getNavTabOfDocument, getTabsOfCurriculumDoc, isStudentWorkspaceDoc
10+
} from "../../models/stores/persistent-ui/persistent-ui";
911
import { DocumentModelType } from "../../models/document/document";
1012
import { CommentedDocumentsQuery } from "../../models/commented-documents";
1113

src/components/document/document-scroller.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ interface IProps {
2121
export const DocumentScroller: React.FC<IProps> = observer(function DocumentThumbnailCarousel(props: IProps) {
2222
const { documentGroup } = props;
2323
const { documents, networkDocuments, persistentUI, sortedDocuments } = useStores();
24-
const tabState = persistentUI.tabs.get(ENavTab.kSortWork);
25-
const openDocumentKey = tabState?.openSubTab && tabState?.openDocuments.get(tabState.openSubTab);
24+
const maybeTabState = persistentUI.tabs.get(ENavTab.kSortWork);
25+
const openDocumentKey = maybeTabState?.currentDocumentGroup?.primaryDocumentKey;
2626
const documentScrollerRef = useRef<HTMLDivElement>(null);
2727
const documentListRef = useRef<HTMLDivElement>(null);
2828
const [scrollToLocation, setScrollToLocation] = useState(0);
@@ -31,11 +31,12 @@ export const DocumentScroller: React.FC<IProps> = observer(function DocumentThum
3131
const maxScrollTo = scrollWidth - panelWidth;
3232

3333
const handleSelectDocument = async (document: DocumentModelType) => {
34-
if (!tabState?.openSubTab) {
35-
console.error("No openSubTab found in persistentUI");
34+
const tabState = persistentUI.getOrCreateTabState(ENavTab.kSortWork);
35+
if (!tabState.currentDocumentGroupId) {
36+
console.error("No currentDocumentGroupId found in persistentUI");
3637
return;
3738
}
38-
persistentUI.openSubTabDocument(ENavTab.kSortWork, tabState?.openSubTab, document.key);
39+
tabState.openDocumentGroupPrimaryDocument(tabState.currentDocumentGroupId, document.key);
3940
logDocumentViewEvent(document);
4041
};
4142

src/components/document/sort-work-document-area.tsx

+9-6
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export const SortWorkDocumentArea: React.FC<IProps> = observer(function SortWork
2525
const { openDocumentsGroup } = props;
2626
const {appConfig, class: classStore, documents, networkDocuments,
2727
persistentUI, sortedDocuments, ui, unit, user} = useStores();
28-
const tabState = persistentUI.tabs.get(ENavTab.kSortWork);
29-
const openDocumentKey = tabState?.openSubTab && tabState?.openDocuments.get(tabState.openSubTab);
28+
const maybeTabState = persistentUI.tabs.get(ENavTab.kSortWork);
29+
const openDocumentKey = maybeTabState?.currentDocumentGroup?.primaryDocumentKey;
3030
const showScroller = persistentUI.showDocumentScroller;
3131
const [prevBtnEnabled, setPrevBtnEnabled] = useState(openDocumentKey !== openDocumentsGroup.documents.at(0)?.key);
3232
const [nextBtnEnabled, setNextBtnEnabled] = useState(openDocumentKey !== openDocumentsGroup.documents.at(-1)?.key);
@@ -83,7 +83,7 @@ export const SortWorkDocumentArea: React.FC<IProps> = observer(function SortWork
8383
};
8484

8585
const handleCloseButtonClick = () => {
86-
persistentUI.closeSubTabDocument();
86+
persistentUI.closeDocumentGroupPrimaryDocument();
8787
};
8888

8989
const handleToggleScroller = () => {
@@ -95,13 +95,16 @@ export const SortWorkDocumentArea: React.FC<IProps> = observer(function SortWork
9595
const currentIndex = docKeys.indexOf(openDocumentKey);
9696
const newIndex = direction === "previous" ? currentIndex - 1 : currentIndex + 1;
9797
const newKey = docKeys[newIndex];
98-
const openSubTab = persistentUI.tabs.get(ENavTab.kSortWork)?.openSubTab;
98+
// When the document was opened by the SortedSection, the tab state should have been
99+
// created, and currentDocumentGroupId set represent the group that the document was
100+
// opened from.
101+
const openSubTab = maybeTabState?.currentDocumentGroupId;
99102
if (!openSubTab) {
100-
console.error("No openSubTab found in persistentUI");
103+
console.error("No currentDocumentGroupId found in persistentUI");
101104
return;
102105
}
103106
if (newKey) {
104-
persistentUI.openSubTabDocument(ENavTab.kSortWork, openSubTab, newKey);
107+
persistentUI.openDocumentGroupPrimaryDocument(ENavTab.kSortWork, openSubTab, newKey);
105108
}
106109
setPrevBtnEnabled(newIndex !== 0);
107110
setNextBtnEnabled(newIndex !== docKeys.length - 1);

src/components/document/sort-work-view.tsx

+11-6
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,27 @@ export const SortWorkView: React.FC = observer(function SortWorkView() {
7373
const primarySearchTerm = normalizeSortString(primarySortBy) as PrimarySortType;
7474
const sortedDocumentGroups = sortedDocuments.sortBy(primarySearchTerm);
7575
const secondarySearchTerm = normalizeSortString(secondarySortBy) as SecondarySortType;
76-
const tabState = persistentUI.tabs.get(ENavTab.kSortWork);
77-
const openDocumentKey = tabState?.openSubTab && tabState?.openDocuments.get(tabState.openSubTab);
76+
const maybeTabState = persistentUI.tabs.get(ENavTab.kSortWork);
77+
const openDocumentKey = maybeTabState?.currentDocumentGroup?.primaryDocumentKey;
7878

7979
const getOpenDocumentsGroup = () => {
8080
let openGroup;
81-
if (tabState?.openSubTab && openDocumentKey) {
81+
if (maybeTabState?.currentDocumentGroupId && openDocumentKey) {
8282
let openGroupMetadata: IOpenDocumentsGroupMetadata;
8383
try {
84-
openGroupMetadata = JSON.parse(tabState.openSubTab);
84+
// The sort work tab stores the group metadata as the document group id.
85+
// This way it can record both the primary and secondary filter values
86+
// associated with the group.
87+
// TODO: create different tabState and/or document group types so these
88+
// values can be stored as fields in the document group
89+
openGroupMetadata = JSON.parse(maybeTabState.currentDocumentGroupId);
8590
} catch (e) {
86-
persistentUI.closeSubTabDocument(ENavTab.kSortWork);
91+
persistentUI.closeDocumentGroupPrimaryDocument(ENavTab.kSortWork);
8792
return;
8893
}
8994

9095
if (openGroupMetadata.primaryType !== primarySearchTerm) {
91-
persistentUI.closeSubTabDocument(ENavTab.kSortWork);
96+
persistentUI.closeDocumentGroupPrimaryDocument(ENavTab.kSortWork);
9297
} else {
9398
openGroup = sortedDocumentGroups.find(group => group.label === openGroupMetadata.primaryLabel);
9499
if (openGroupMetadata.secondaryType === secondarySearchTerm) {

src/components/document/sorted-section.tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,21 @@ export const SortedSection: React.FC<IProps> = observer(function SortedSection(p
4747

4848
const handleSelectDocument = (docGroup: DocumentGroup) => {
4949
const { label, sortType } = docGroup;
50-
const openSubTabMetadata: IOpenDocumentsGroupMetadata = secondarySort === "None"
50+
const openDocGroupMetadata: IOpenDocumentsGroupMetadata = secondarySort === "None"
5151
? { primaryLabel: label, primaryType: sortType }
5252
: { primaryLabel: documentGroup.label, primaryType: documentGroup.sortType,
5353
secondaryLabel: label, secondaryType: sortType };
5454

5555
return async (document: DocumentModelType | IDocumentMetadataModel) => {
56-
const openSubTab = JSON.stringify(openSubTabMetadata);
57-
persistentUI.openSubTabDocument(ENavTab.kSortWork, openSubTab, document.key);
56+
// The sort work tab stores the group metadata as the document group id.
57+
// This way it can record both the primary and secondary filter values
58+
// associated with the group.
59+
// TODO: create different tabState and/or document group types so these
60+
// values can be stored as fields in the document group
61+
const documentGroupId = JSON.stringify(openDocGroupMetadata);
62+
const tabState = persistentUI.getOrCreateTabState(ENavTab.kSortWork);
63+
tabState.openDocumentGroupPrimaryDocument(documentGroupId, document.key);
64+
5865
logDocumentViewEvent(document);
5966
};
6067
};

src/components/document/student-group-view.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ export const StudentGroupView:React.FC = observer(function StudentGroupView(){
1616

1717
useEffect(() => stores.initializeStudentWorkTab(), [stores]);
1818

19-
const selectedGroupId = persistentUI.tabs.get("student-work")?.openSubTab || "";
19+
const maybeTabUIModel = persistentUI.tabs.get("student-work");
20+
const selectedGroupId = maybeTabUIModel?.currentDocumentGroupId || "";
2021
const group = groups.getGroupById(selectedGroupId);
21-
const openDocId = persistentUI.tabs.get("student-work")?.openDocuments.get(selectedGroupId);
22+
const openDocId = maybeTabUIModel?.getDocumentGroup(selectedGroupId)?.primaryDocumentKey;
2223
const focusedGroupUser = getFocusedGroupUser(group, openDocId, DocumentViewMode.Live);
2324
const isChatPanelShown = persistentUI.showChatPanel;
2425
const documentSelectedForComment = persistentUI.showChatPanel && (ui.selectedTileIds.length === 0)
@@ -53,12 +54,12 @@ const GroupViewTitlebar: React.FC<IGroupComponentProps> = observer(function Grou
5354

5455
const handleFocusedUserChange = (selectedUser: GroupUserModelType) => {
5556
group?.id && selectedUser.problemDocument &&
56-
persistentUI.openSubTabDocument("student-work", group.id, selectedUser.problemDocument.key);
57+
persistentUI.openDocumentGroupPrimaryDocument("student-work", group.id, selectedUser.problemDocument.key);
5758
};
5859

5960
const handleSelectGroup = (id: string) => {
60-
persistentUI.setOpenSubTab("student-work", id);
61-
persistentUI.closeSubTabDocument("student-work", id);
61+
persistentUI.setCurrentDocumentGroupId("student-work", id);
62+
persistentUI.closeDocumentGroupPrimaryDocument("student-work", id);
6263
Logger.log(LogEventName.VIEW_GROUP, {group: id, via: "group-document-titlebar"});
6364
};
6465

src/components/four-up.tsx

+14-9
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ export class FourUpComponent extends BaseComponent<IProps, IState> {
123123
this.resizeObserver.disconnect();
124124
}
125125

126+
private get tabName() {
127+
return getUIStudentWorkTab(this.props.documentViewMode);
128+
}
129+
126130
/**
127131
* When the four-up is used in the dashboard it can be showing published
128132
* documents. In that case we use a fake tab called student-work-published
@@ -131,14 +135,14 @@ export class FourUpComponent extends BaseComponent<IProps, IState> {
131135
*
132136
* @returns
133137
*/
134-
private getNavTabName() {
135-
return getUIStudentWorkTab(this.props.documentViewMode);
138+
private get tabUIModel() {
139+
const {persistentUI} = this.stores;
140+
return persistentUI.tabs.get(this.tabName);
136141
}
137142

138143
private getFocusedUserDocKey() {
139-
const {persistentUI} = this.stores;
140144
const {group} = this.props;
141-
return persistentUI.tabs.get(this.getNavTabName())?.openDocuments.get(group.id);
145+
return this.tabUIModel?.getDocumentGroup(group.id)?.primaryDocumentKey;
142146
}
143147

144148
private getFocusedGroupUser() {
@@ -400,24 +404,25 @@ export class FourUpComponent extends BaseComponent<IProps, IState> {
400404
};
401405

402406
private handleFourUpClick = () => {
403-
const { persistentUI } = this.stores;
404407
const { group } = this.props;
405-
persistentUI.closeSubTabDocument(this.getNavTabName(), group.id);
408+
this.tabUIModel?.getDocumentGroup(group.id)?.closePrimaryDocument();
406409
};
407410

408411
private handleOverlayClick = (groupUser?: GroupUserModelType) => {
409-
const { persistentUI } = this.stores;
410412
const { group } = this.props;
411413
const focusedUser = this.getFocusedGroupUser();
412414
const document = this.getGroupUserDoc(groupUser);
415+
const {persistentUI} = this.stores;
413416

414417
if (groupUser && document) {
415418
const logInfo = {groupId: group.id, studentId: groupUser.id};
416419
if (focusedUser){
417-
persistentUI.closeSubTabDocument(this.getNavTabName(), group.id);
420+
// This needs to create the tabModel if it doesn't exist so we can use this.tabModel
421+
persistentUI.closeDocumentGroupPrimaryDocument(this.tabName, group.id);
418422
Logger.log(LogEventName.DASHBOARD_DESELECT_STUDENT, logInfo);
419423
} else {
420-
persistentUI.setOpenSubTabDocument(this.getNavTabName(), group.id, document.key); //sets the focus document;
424+
// This needs to create the tabModel if it doesn't exist so we can use this.tabModel
425+
persistentUI.setDocumentGroupPrimaryDocument(this.tabName, group.id, document.key); //sets the focus document;
421426
Logger.log(LogEventName.DASHBOARD_SELECT_STUDENT, logInfo);
422427
}
423428
}

src/components/navigation/document-browser-scroller.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import React, { useEffect, useRef, useState } from "react";
22
import classNames from "classnames";
33
import { clamp } from "lodash";
44
import { DocumentModelType } from "../../models/document/document";
5-
import { ISubTabSpec, NavTabModelType } from "../../models/view/nav-tabs";
5+
import { ISubTabModel, NavTabModelType } from "../../models/view/nav-tabs";
66
import { DocumentCollectionList } from "../thumbnail/document-collection-list";
77
import ScrollArrowIcon from "../../assets/scroll-arrow-icon.svg";
88
import CollapseScrollerIcon from "../../assets/show-hide-document-view-icon.svg";
99

1010
import "./document-browser-scroller.scss";
1111

1212
interface DocumentBrowserScrollerProps {
13-
subTab: ISubTabSpec;
13+
subTab: ISubTabModel;
1414
tabSpec: NavTabModelType;
1515
openDocumentKey: string;
1616
openSecondaryDocumentKey: string;

0 commit comments

Comments
 (0)