diff --git a/web-ui/src/components/course/course-content/course-content.tsx b/web-ui/src/components/course/course-content/course-content.tsx index aae6029..abf3688 100644 --- a/web-ui/src/components/course/course-content/course-content.tsx +++ b/web-ui/src/components/course/course-content/course-content.tsx @@ -80,12 +80,12 @@ export default function CourseContent(props: CourseContentProps) { {/* Separate KTH Play and YouTube videos on desktop */} - + KTH Play - - + + Youtube diff --git a/web-ui/src/components/image/image-upload/image-upload.less b/web-ui/src/components/image/image-upload/image-upload.less index d140654..d8f619b 100644 --- a/web-ui/src/components/image/image-upload/image-upload.less +++ b/web-ui/src/components/image/image-upload/image-upload.less @@ -12,6 +12,20 @@ margin: 0; } + &.compact { + > div:first-child { + height: 150px; + padding: 0px 20px; + } + + *.icon { + text-align: center; + font-size: 30px; + margin-top: 10px; + margin-bottom: 10px; + } + } + > div:first-child { padding: 32px 10px; height: 300px; diff --git a/web-ui/src/components/image/image-upload/image-upload.tsx b/web-ui/src/components/image/image-upload/image-upload.tsx index 942ca74..4a27dbe 100644 --- a/web-ui/src/components/image/image-upload/image-upload.tsx +++ b/web-ui/src/components/image/image-upload/image-upload.tsx @@ -29,11 +29,12 @@ interface ImageResponse extends ServerResponse { interface ImageUploadProps { uploadId?: string; noMargin?: boolean; + compact?: boolean; onUploadComplete: (image: ImageType) => void; } export default function ImageUpload(props: ImageUploadProps) { - const { uploadId, onUploadComplete, noMargin } = props; + const { uploadId, onUploadComplete, noMargin, compact } = props; const [id, setId] = useState(null); const [error, setError] = useState(''); @@ -111,7 +112,9 @@ export default function ImageUpload(props: ImageUploadProps) { {contextHolder} {(id === null || image === null) && ( @@ -124,17 +127,26 @@ export default function ImageUpload(props: ImageUploadProps) { - Ask a question about an assignment + {(compact === undefined || compact === false) && ( + Ask a question about an assignment + )} + {compact === true && ( + + Ask a question about another assignment + + )} - - Click or drag an image of an assignment, lecture slide or lab - to this area to upload - + {(compact === undefined || compact === false) && ( + + Click or drag an image of an assignment, lecture slide or + lab to this area to upload + + )} )} diff --git a/web-ui/src/components/lecture/lecture-list/lecture-list.less b/web-ui/src/components/lecture/lecture-list/lecture-list.less index 6a5a10b..f7f2ae4 100644 --- a/web-ui/src/components/lecture/lecture-list/lecture-list.less +++ b/web-ui/src/components/lecture/lecture-list/lecture-list.less @@ -48,4 +48,18 @@ width: 100% !important; } } + + .item { + margin-bottom: 20px; + } +} + +.load_more { + width: 100%; + margin-top: 80px; + margin-bottom: 200px; +} + +.hits { + margin-top: 10px; } diff --git a/web-ui/src/components/lecture/lecture-list/lecture-list.tsx b/web-ui/src/components/lecture/lecture-list/lecture-list.tsx index ba8462c..c6d59c0 100644 --- a/web-ui/src/components/lecture/lecture-list/lecture-list.tsx +++ b/web-ui/src/components/lecture/lecture-list/lecture-list.tsx @@ -1,6 +1,6 @@ import styles from './lecture-list.less'; import { Lecture } from '@/types/lecture'; -import { Row, Input, Space, Col } from 'antd'; +import { Row, Input, Typography, Col, Button } from 'antd'; import { useMutation } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import apiClient, { ServerErrorResponse, ServerResponse } from '@/http'; @@ -13,13 +13,18 @@ import { CATEGORY_LECTURE_LIST, EVENT_ERROR_RESPONSE, EVENT_GOTO_LECTURE, + EVENT_LOAD_MORE, EVENT_SEARCHED, } from '@/matomo/events'; const { Search } = Input; +const { Paragraph } = Typography; + const AUTO_UPDATE_INTERVAL = 10000; +const PAGINATE_AFTER = 25; + interface LectureResponse extends ServerResponse { data: Lecture[]; } @@ -34,7 +39,10 @@ export default function LectureList(props: LectureListProps) { const [firstLoad, setFirstLoad] = useState(true); const [lectureQuery, setLectureQuery] = useState(''); + const [lastQuery, setLastQuery] = useState(''); + const [lastCourseCode, setLastCourseCode] = useState(''); const [lectures, setLectures] = useState([]); + const [limit, setLimit] = useState(PAGINATE_AFTER); const { isLoading: isSearchingLectures, mutate: doLectureSearch } = useMutation( @@ -60,7 +68,16 @@ export default function LectureList(props: LectureListProps) { data: res.data, }; setLectures(result.data); + if ( + lectureQuery !== lastQuery || + lastCourseCode !== courseCode || + firstLoad + ) { + setLimit(Math.min(PAGINATE_AFTER, result.data.length)); + } setFirstLoad(false); + setLastQuery(lectureQuery); + setLastCourseCode(courseCode); }, onError: (err: ServerErrorResponse) => { console.log(err); @@ -79,6 +96,16 @@ export default function LectureList(props: LectureListProps) { emitEvent(CATEGORY_LECTURE_LIST, EVENT_SEARCHED, query); }; + const loadMore = () => { + if (limit + PAGINATE_AFTER > lectures.length) { + setLimit(lectures.length); + } else { + setLimit(limit + PAGINATE_AFTER); + } + + emitEvent(CATEGORY_LECTURE_LIST, EVENT_LOAD_MORE, ACTION_NONE); + }; + const goToLecture = async (lecture: Lecture, newTab = false) => { const url = `/lectures/${lecture.public_id}/${lecture.language}/questions`; @@ -97,7 +124,11 @@ export default function LectureList(props: LectureListProps) { useEffect(() => { searchLectures(''); - }, [courseCode]); // eslint-disable-line react-hooks/exhaustive-deps + + if (courseCode !== lastCourseCode) { + setFirstLoad(true); + } + }, [courseCode, lastCourseCode]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { const interval = setInterval(() => { @@ -123,34 +154,60 @@ export default function LectureList(props: LectureListProps) { /> - - {isSearchingLectures && firstLoad && ( - - )} - - {lectures.map((lecture, index) => { - return ( - - - {index + 1} - - - goToLecture(lecture)} - onMetaClick={() => goToLecture(lecture, true)} - onCtrlClick={() => goToLecture(lecture, true)} - /> - - - ); - })} - - + {!firstLoad && ( + + {isSearchingLectures && firstLoad && ( + + )} +
+ {lectures.map((lecture, index) => { + if (index + 1 > limit) { + return
; + } + + return ( + + + {index + 1} + + + goToLecture(lecture)} + onMetaClick={() => goToLecture(lecture, true)} + onCtrlClick={() => goToLecture(lecture, true)} + /> + + + ); + })} +
+ +
+ + + + + + + + Showing {limit} / {lectures.length} lectures + + +
+
+ )} ); } diff --git a/web-ui/src/pages/assignments/index.less b/web-ui/src/pages/assignments/index.less index c35c87a..900c272 100644 --- a/web-ui/src/pages/assignments/index.less +++ b/web-ui/src/pages/assignments/index.less @@ -11,3 +11,11 @@ .full_width { width: 100%; } + +.image_preview { + width: 100%; + border: 2px dashed #d9d9d9; + border-radius: 8px; + padding: 10px; + overflow: hidden; +} diff --git a/web-ui/src/pages/assignments/index.tsx b/web-ui/src/pages/assignments/index.tsx index 9ef8cd5..54dc24e 100644 --- a/web-ui/src/pages/assignments/index.tsx +++ b/web-ui/src/pages/assignments/index.tsx @@ -10,11 +10,16 @@ import { Space, Typography, notification, + Image as AntImage, } from 'antd'; import { useMutation } from '@tanstack/react-query'; import { useEffect, useState } from 'react'; import { useParams } from 'umi'; -import apiClient, { ServerErrorResponse, ServerResponse } from '@/http'; +import apiClient, { + ServerErrorResponse, + ServerResponse, + makeUrl, +} from '@/http'; import { Image } from '@/types/search'; import ImageUpload from '@/components/image/image-upload/image-upload'; import ImageProgress from '@/components/image/image-progress/image-progress'; @@ -99,6 +104,8 @@ export default function AssignmentsIndexPage() { fetchImage(); }; + const previewUrl = makeUrl(`/assignments/image/${id}/img`); + useEffect(() => { registerPageLoad(); }, []); @@ -174,10 +181,17 @@ export default function AssignmentsIndexPage() { + + + onImageUploadComplete(image)} />