Render media files in the CanvasContentTray
The files are there, though the visuals are incorrect. I just lifted the documents Link component to get them displayed. Rendering per the design will be a later ticket closes COREFE-40 test plan: - load the rce in a course as a teacher - select Media > Course Media > expect course audio and video files displayed in the tray - switch to My Files > expect user audio and video files in the tray > expect the files to have their respective icons - load the rce in a course as a student - select Media > My Media (expect it to be the only option) > expect the students media files to be diplayed in the tray > expect Course Files not to be an option Change-Id: I8836c4e207163b6e0c0dff0d68be12d819ca5bc6 Reviewed-on: https://gerrit.instructure.com/207042 Tested-by: Jenkins Reviewed-by: Clay Diffrient <cdiffrient@instructure.com> QA-Review: Clay Diffrient <cdiffrient@instructure.com> Product-Review: Ed Schiebel <eschiebel@instructure.com>
This commit is contained in:
parent
b5984c7034
commit
56d8210984
|
@ -101,6 +101,16 @@ describe('RCE "Documents" Plugin > Document', () => {
|
|||
expect(queryIconByName(container, 'IconPdf')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('the video icon', () => {
|
||||
const {container} = renderComponent({content_type: 'video/mp4'})
|
||||
expect(queryIconByName(container, 'IconVideo')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('the audio icon', () => {
|
||||
const {container} = renderComponent({content_type: 'audio/mp3'})
|
||||
expect(queryIconByName(container, 'IconAudio')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('the drag handle only on hover', () => {
|
||||
const {container, getByTestId} = renderComponent()
|
||||
|
||||
|
|
|
@ -60,7 +60,10 @@ export default function Images(props) {
|
|||
}, [contextType, files.length, hasMore, isLoading, fetchInitialImages])
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
as="div"
|
||||
data-testid="instructure_links-ImagesPanel"
|
||||
>
|
||||
<Flex alignItems="center" direction="column" justifyItems="space-between" height="100%">
|
||||
<Flex.Item overflowY="visible" width="100%">
|
||||
<ImageList images={files} lastItemRef={lastItemRef} onImageClick={props.onImageEmbed} />
|
||||
|
@ -86,7 +89,7 @@ export default function Images(props) {
|
|||
<Text color="error">{formatMessage("Loading failed.")}</Text>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ describe('Instructure Image Plugin: clickCallback', () => {
|
|||
initializeUpload () {},
|
||||
initializeFlickr () {},
|
||||
initializeImages() {},
|
||||
initializeDocuments() {}
|
||||
initializeDocuments() {},
|
||||
initializeMedia() {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import {arrayOf, bool, func, objectOf, shape, string} from 'prop-types';
|
||||
import {fileShape} from '../../shared/fileShape'
|
||||
import formatMessage from '../../../../format-message';
|
||||
|
||||
import {Text} from '@instructure/ui-elements'
|
||||
import {View} from '@instructure/ui-layout'
|
||||
import Link from '../../instructure_documents/components/Link'
|
||||
import {
|
||||
LoadMoreButton,
|
||||
LoadingIndicator,
|
||||
LoadingStatus,
|
||||
useIncrementalLoading
|
||||
} from '../../../../common/incremental-loading'
|
||||
|
||||
function hasFiles(media) {
|
||||
return media.files.length > 0
|
||||
}
|
||||
|
||||
function isEmpty(media) {
|
||||
return (
|
||||
!hasFiles(media) &&
|
||||
!media.hasMore &&
|
||||
!media.isLoading
|
||||
);
|
||||
}
|
||||
|
||||
function renderLinks(files, handleClick, lastItemRef) {
|
||||
return files.map((f, index) => {
|
||||
let focusRef = null
|
||||
if (index === files.length -1) {
|
||||
focusRef = lastItemRef
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
key={f.id}
|
||||
{...f}
|
||||
onClick={handleClick}
|
||||
focusRef={focusRef}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function renderLoadingError(_error) {
|
||||
return (
|
||||
<View as="div" role="alert" margin="medium">
|
||||
<Text color="error">{formatMessage("Loading failed.")}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default function MediaPanel(props) {
|
||||
const {fetchInitialMedia, fetchNextMedia, contextType} = props
|
||||
const media = props.media[contextType]
|
||||
const {hasMore, isLoading, error, files} = media
|
||||
const lastItemRef = useRef(null)
|
||||
|
||||
const loader = useIncrementalLoading({
|
||||
hasMore,
|
||||
isLoading,
|
||||
lastItemRef,
|
||||
|
||||
onLoadInitial() {
|
||||
fetchInitialMedia()
|
||||
},
|
||||
|
||||
onLoadMore() {
|
||||
fetchNextMedia()
|
||||
},
|
||||
|
||||
records: files
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMore && !isLoading && files.length === 0) {
|
||||
fetchInitialMedia()
|
||||
}
|
||||
}, [contextType, files.length, hasMore, isLoading, fetchInitialMedia])
|
||||
|
||||
const handleFileClick = file => {
|
||||
props.onLinkClick(file)
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
as="div"
|
||||
data-testid="instructure_links-MediaPanel"
|
||||
>
|
||||
|
||||
{renderLinks(files, handleFileClick, lastItemRef)}
|
||||
|
||||
{loader.isLoading && <LoadingIndicator loader={loader} />}
|
||||
|
||||
{!loader.isLoading && loader.hasMore && <LoadMoreButton loader={loader} />}
|
||||
|
||||
<LoadingStatus loader={loader} />
|
||||
|
||||
{error && renderLoadingError(error)}
|
||||
|
||||
{isEmpty(media) && (
|
||||
<View as="div" padding="medium">
|
||||
{formatMessage("No results.")}
|
||||
</View>
|
||||
)}
|
||||
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
MediaPanel.propTypes = {
|
||||
contextType: string.isRequired,
|
||||
fetchInitialMedia: func.isRequired,
|
||||
fetchNextMedia: func.isRequired,
|
||||
onLinkClick: func.isRequired,
|
||||
media: objectOf(shape({
|
||||
files: arrayOf(shape(fileShape)).isRequired,
|
||||
bookmark: string,
|
||||
hasMore: bool,
|
||||
isLoading: bool,
|
||||
error: string
|
||||
})).isRequired
|
||||
}
|
|
@ -63,7 +63,7 @@ const thePanels = {
|
|||
links: React.lazy(() => import('../instructure_links/components/LinksPanel')),
|
||||
images: React.lazy(() => import('../instructure_image/Images')),
|
||||
documents: React.lazy(() => import('../instructure_documents/components/DocumentsPanel')),
|
||||
media: React.lazy(() => import('./UnknownFileTypePanel')),
|
||||
media: React.lazy(() => import('../instructure_record/MediaPanel')),
|
||||
unknown: React.lazy(() => import('./UnknownFileTypePanel'))
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render, fireEvent, act, waitForElement, prettyDOM} from '@testing-library/react'
|
||||
import {render, fireEvent, act, waitForElement} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import {UploadFile, handleSubmit} from '../UploadFile'
|
||||
|
||||
|
@ -31,7 +31,8 @@ describe('UploadFile', () => {
|
|||
initializeUpload () {},
|
||||
initializeFlickr () {},
|
||||
initializeImages() {},
|
||||
initializeDocuments() {}
|
||||
initializeDocuments() {},
|
||||
initializeMedia() {}
|
||||
}
|
||||
}
|
||||
fakeEditor = {}
|
||||
|
|
|
@ -110,6 +110,31 @@ describe('RCE Plugins > CanvasContentTray', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('content panel', () => {
|
||||
beforeEach(() => {
|
||||
renderComponent()
|
||||
})
|
||||
it('is the links panel for links content types', async () => {
|
||||
await showTrayForPlugin('links')
|
||||
expect(component.getByTestId('instructure_links-LinksPanel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('is the documents panel for document content types', async () => {
|
||||
await showTrayForPlugin('course_documents')
|
||||
expect(component.getByTestId('instructure_links-DocumentsPanel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('is the images panel for image content types', async () => {
|
||||
await showTrayForPlugin('images')
|
||||
expect(component.getByTestId('instructure_links-ImagesPanel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it.only('is the media panel for media content types', async () => {
|
||||
await showTrayForPlugin('course_media')
|
||||
expect(component.getByTestId('instructure_links-MediaPanel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('focus', () => {
|
||||
beforeEach(() => {
|
||||
renderComponent()
|
||||
|
|
|
@ -21,10 +21,17 @@ import {
|
|||
IconMsExcelLine,
|
||||
IconMsPptLine,
|
||||
IconMsWordLine,
|
||||
IconPdfLine
|
||||
IconPdfLine,
|
||||
IconVideoLine,
|
||||
IconAudioLine
|
||||
} from '@instructure/ui-icons'
|
||||
|
||||
export function getIconFromType(type) {
|
||||
if (isVideo(type)) {
|
||||
return IconVideoLine
|
||||
} else if (isAudio(type)) {
|
||||
return IconAudioLine
|
||||
}
|
||||
switch(type) {
|
||||
case 'application/msword':
|
||||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const REQUEST_MEDIA = "REQUEST_MEDIA";
|
||||
export const RECEIVE_MEDIA = "RECEIVE_MEDIA";
|
||||
export const FAIL_MEDIA = "FAIL_MEDIA";
|
||||
|
||||
export function requestMedia(contextType) {
|
||||
return { type: REQUEST_MEDIA, payload: {contextType} };
|
||||
}
|
||||
|
||||
export function receiveMedia({response, contextType}) {
|
||||
const { files, bookmark } = response;
|
||||
return { type: RECEIVE_MEDIA, payload: {files, bookmark, contextType }};
|
||||
}
|
||||
|
||||
export function failMedia({error, contextType}) {
|
||||
return { type: FAIL_MEDIA, payload: {error, contextType}};
|
||||
}
|
||||
|
||||
// dispatches the start of the load, requests a page for the collection from
|
||||
// the source, then dispatches the loaded page to the store on success or
|
||||
// clears the load on failure
|
||||
export function fetchMedia() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
dispatch(requestMedia(state.contextType));
|
||||
return state.source
|
||||
.fetchMedia(state)
|
||||
.then(response => dispatch(receiveMedia({response, contextType: state.contextType})))
|
||||
.catch(error => dispatch(failMedia({error, contextType: state.contextType})));
|
||||
};
|
||||
}
|
||||
|
||||
// fetches a page only if a page is not already being loaded and the
|
||||
// collection is not yet completely loaded
|
||||
export function fetchNextMedia() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const media = state.media[state.contextType]
|
||||
|
||||
if (media && !media.isLoading && media.hasMore) {
|
||||
return dispatch(fetchMedia());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// fetches the next page (subject to conditions on fetchNextMedia) only if the
|
||||
// collection is currently empty
|
||||
export function fetchInitialMedia() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const media = state.media[state.contextType]
|
||||
|
||||
if (media && media.hasMore && !media.isLoading && media.files && media.files.length === 0) {
|
||||
return dispatch(fetchMedia());
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright (C) 2018 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import React, { Component } from "react";
|
||||
import { ToggleDetails } from '@instructure/ui-toggle-details'
|
||||
import { View } from '@instructure/ui-layout'
|
||||
import LinkSet from "./LinkSet";
|
||||
import NavigationPanel from "./NavigationPanel";
|
||||
import LinkToNewPage from "./LinkToNewPage";
|
||||
import formatMessage from "../../format-message";
|
||||
|
||||
function AccordionSection({
|
||||
collection,
|
||||
children,
|
||||
onChange,
|
||||
selectedIndex,
|
||||
summary
|
||||
}) {
|
||||
return (
|
||||
<View as="div" margin="xx-small none">
|
||||
<ToggleDetails
|
||||
variant="filled"
|
||||
summary={summary}
|
||||
expanded={selectedIndex === collection}
|
||||
onToggle={(e, expanded) => onChange(expanded ? collection : "")}
|
||||
>
|
||||
<div style={{maxHeight: '20em', overflow: 'auto'}}>{children}</div>
|
||||
</ToggleDetails>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
AccordionSection.propTypes = {
|
||||
collection: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
selectedIndex: PropTypes.string,
|
||||
summary: ToggleDetails.propTypes.summary
|
||||
};
|
||||
|
||||
function CollectionPanel(props) {
|
||||
return (
|
||||
<AccordionSection {...props}>
|
||||
<LinkSet
|
||||
fetchInitialPage={
|
||||
props.fetchInitialPage &&
|
||||
(() => props.fetchInitialPage(props.collection))
|
||||
}
|
||||
fetchNextPage={
|
||||
props.fetchNextPage && (() => props.fetchNextPage(props.collection))
|
||||
}
|
||||
collection={props.collections[props.collection]}
|
||||
onLinkClick={props.onLinkClick}
|
||||
suppressRenderEmpty={props.suppressRenderEmpty}
|
||||
/>
|
||||
{props.renderNewPageLink && (
|
||||
<LinkToNewPage
|
||||
onLinkClick={props.onLinkClick}
|
||||
toggleNewPageForm={props.toggleNewPageForm}
|
||||
newPageLinkExpanded={props.newPageLinkExpanded}
|
||||
contextId={props.contextId}
|
||||
contextType={props.contextType}
|
||||
/>
|
||||
)}
|
||||
</AccordionSection>
|
||||
);
|
||||
}
|
||||
|
||||
CollectionPanel.propTypes = {
|
||||
collection: PropTypes.string.isRequired,
|
||||
renderNewPageLink: PropTypes.bool,
|
||||
suppressRenderEmpty: PropTypes.bool
|
||||
};
|
||||
|
||||
CollectionPanel.defaultProps = {
|
||||
renderNewPageLink: false,
|
||||
suppressRenderEmpty: false
|
||||
};
|
||||
|
||||
function LinksPanel(props) {
|
||||
const isCourse = props.contextType === "course";
|
||||
const isGroup = props.contextType === "group";
|
||||
|
||||
const navigationSummary = isCourse
|
||||
? formatMessage({
|
||||
default: "Course Navigation",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to course pages."
|
||||
})
|
||||
: isGroup
|
||||
? formatMessage({
|
||||
default: "Group Navigation",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to group pages."
|
||||
})
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div data-testid="instructure_links-LinksPanel">
|
||||
<p>
|
||||
{props.contextType === "course"
|
||||
? formatMessage("Link to other content in the course.")
|
||||
: props.contextType === "group"
|
||||
? formatMessage("Link to other content in the group.")
|
||||
: ""}
|
||||
{formatMessage("Click any page to insert a link to that page.")}
|
||||
</p>
|
||||
<div>
|
||||
{(isCourse || isGroup) && (
|
||||
<CollectionPanel
|
||||
{...props}
|
||||
collection="wikiPages"
|
||||
summary={formatMessage({
|
||||
default: "Pages",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to wiki pages."
|
||||
})}
|
||||
renderNewPageLink={props.canCreatePages !== false}
|
||||
suppressRenderEmpty={props.canCreatePages !== false}
|
||||
/>
|
||||
)}
|
||||
{isCourse && (
|
||||
<CollectionPanel
|
||||
{...props}
|
||||
collection="assignments"
|
||||
summary={formatMessage({
|
||||
default: "Assignments",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to assignments."
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{isCourse && (
|
||||
<CollectionPanel
|
||||
{...props}
|
||||
collection="quizzes"
|
||||
summary={formatMessage({
|
||||
default: "Quizzes",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to quizzes."
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{(isCourse || isGroup) && (
|
||||
<CollectionPanel
|
||||
{...props}
|
||||
collection="announcements"
|
||||
summary={formatMessage({
|
||||
default: "Announcements",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to announcements."
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{(isCourse || isGroup) && (
|
||||
<CollectionPanel
|
||||
{...props}
|
||||
collection="discussions"
|
||||
summary={formatMessage({
|
||||
default: "Discussions",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to discussions."
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{isCourse && (
|
||||
<CollectionPanel
|
||||
{...props}
|
||||
collection="modules"
|
||||
summary={formatMessage({
|
||||
default: "Modules",
|
||||
description:
|
||||
"Title of Sidebar accordion tab containing links to course modules."
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<AccordionSection
|
||||
{...props}
|
||||
collection="navigation"
|
||||
summary={navigationSummary}
|
||||
>
|
||||
<NavigationPanel
|
||||
contextType={props.contextType}
|
||||
contextId={props.contextId}
|
||||
onLinkClick={props.onLinkClick}
|
||||
/>
|
||||
</AccordionSection>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LinksPanel.propTypes = {
|
||||
selectedIndex: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
contextType: PropTypes.string.isRequired,
|
||||
contextId: PropTypes.string.isRequired,
|
||||
collections: PropTypes.object.isRequired,
|
||||
fetchInitialPage: PropTypes.func,
|
||||
fetchNextPage: PropTypes.func,
|
||||
onLinkClick: PropTypes.func,
|
||||
toggleNewPageForm: LinkToNewPage.propTypes.toggleNewPageForm,
|
||||
newPageLinkExpanded: PropTypes.bool,
|
||||
canCreatePages: PropTypes.bool
|
||||
};
|
||||
|
||||
LinksPanel.defaultProps = {
|
||||
selectedIndex: ""
|
||||
};
|
||||
|
||||
export default LinksPanel;
|
|
@ -25,6 +25,7 @@ export function propsFromState(state) {
|
|||
files,
|
||||
images,
|
||||
documents,
|
||||
media,
|
||||
folders,
|
||||
rootFolderId,
|
||||
flickr,
|
||||
|
@ -52,6 +53,7 @@ export function propsFromState(state) {
|
|||
files,
|
||||
images,
|
||||
documents,
|
||||
media,
|
||||
folders,
|
||||
rootFolderId,
|
||||
flickr,
|
||||
|
|
|
@ -31,6 +31,7 @@ import { searchFlickr, openOrCloseFlickrForm } from "../actions/flickr";
|
|||
import { toggle as toggleFolder } from "../actions/files";
|
||||
import { openOrCloseNewPageForm } from "../actions/links";
|
||||
import { fetchInitialDocs, fetchNextDocs } from "../actions/documents";
|
||||
import { fetchInitialMedia, fetchNextMedia } from "../actions/media";
|
||||
import { changeContext } from "../actions/context";
|
||||
|
||||
export default function propsFromDispatch(dispatch) {
|
||||
|
@ -55,6 +56,8 @@ export default function propsFromDispatch(dispatch) {
|
|||
saveMediaRecording: (file, editor, dismiss) => dispatch(saveMediaRecording(file, editor, dismiss)),
|
||||
fetchInitialDocs: () => dispatch(fetchInitialDocs()),
|
||||
fetchNextDocs: () => dispatch(fetchNextDocs()),
|
||||
fetchInitialMedia: () => dispatch(fetchInitialMedia()),
|
||||
fetchNextMedia: () => dispatch(fetchNextMedia()),
|
||||
onChangeContext: (newContext) => dispatch(changeContext(newContext))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import folders from "./folders";
|
|||
import rootFolderId from "./rootFolderId";
|
||||
import imagesReducer from "./images";
|
||||
import documentsReducer from "./documents";
|
||||
import mediaReducer from "./media";
|
||||
import upload from "./upload";
|
||||
import flickrReducer from "./flickr";
|
||||
import session from "./session";
|
||||
|
@ -46,6 +47,7 @@ export default combineReducers({
|
|||
rootFolderId,
|
||||
images: imagesReducer,
|
||||
documents: documentsReducer,
|
||||
media: mediaReducer,
|
||||
upload,
|
||||
flickr: flickrReducer,
|
||||
session,
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { REQUEST_MEDIA, RECEIVE_MEDIA, FAIL_MEDIA } from "../actions/media";
|
||||
import { CHANGE_CONTEXT } from "../actions/context"
|
||||
|
||||
// manages the state for a specific collection. assumes the action is intended
|
||||
// for this collection (see collections.js)
|
||||
export default function mediaReducer(prevState = {}, action) {
|
||||
const ctxt = action.payload && action.payload.contextType
|
||||
const state = {...prevState}
|
||||
if (ctxt && !state[ctxt]) {
|
||||
state[ctxt] = {
|
||||
files: [],
|
||||
bookmark: null,
|
||||
isLoading: false,
|
||||
hasMore: true
|
||||
}
|
||||
}
|
||||
switch (action.type) {
|
||||
case REQUEST_MEDIA:
|
||||
state[ctxt].isLoading = true
|
||||
return state
|
||||
|
||||
case RECEIVE_MEDIA:
|
||||
state[ctxt] = {
|
||||
files: state[ctxt].files.concat(action.payload.files),
|
||||
bookmark: action.payload.bookmark,
|
||||
isLoading: false,
|
||||
hasMore: !!action.payload.bookmark
|
||||
}
|
||||
return state
|
||||
|
||||
case FAIL_MEDIA: {
|
||||
state[ctxt] = {
|
||||
isLoading: false,
|
||||
error: action.payload.error
|
||||
};
|
||||
if (action.payload.files && action.payload.files.length === 0) {
|
||||
state[ctxt].bookmark = null;
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
case CHANGE_CONTEXT:
|
||||
return state;
|
||||
|
||||
default:
|
||||
return prevState;
|
||||
}
|
||||
}
|
|
@ -131,6 +131,10 @@ class RceApiSource {
|
|||
}
|
||||
}
|
||||
|
||||
initializeMedia(props) {
|
||||
return this.initializeDocuments(props)
|
||||
}
|
||||
|
||||
initializeFlickr() {
|
||||
return {
|
||||
searchResults: [],
|
||||
|
@ -150,6 +154,12 @@ class RceApiSource {
|
|||
return this.apiFetch(uri, headerFor(this.jwt))
|
||||
}
|
||||
|
||||
fetchMedia(props) {
|
||||
const media = props.media[props.contextType]
|
||||
const uri = media.bookmark || this.uriFor('media', props)
|
||||
return this.apiFetch(uri, headerFor(this.jwt))
|
||||
}
|
||||
|
||||
fetchFiles(uri) {
|
||||
return this.fetchPage(uri).then(({bookmark, files}) => {
|
||||
return {
|
||||
|
@ -369,9 +379,8 @@ class RceApiSource {
|
|||
this.alertFunc({
|
||||
text: formatMessage('Something went wrong uploading, check your connection and try again.'),
|
||||
variant: 'error'
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e)
|
||||
})
|
||||
console.error(e) // eslint-disable-line no-console
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -299,36 +299,40 @@ const FLICKR_RESULTS = {
|
|||
]
|
||||
};
|
||||
|
||||
const DOCUMENTS = {
|
||||
documents1: {
|
||||
files: [1,2,3].map(i => {
|
||||
return {
|
||||
id: i,
|
||||
filename: `file${i}.txt`,
|
||||
content_type: 'text/plain',
|
||||
display_name: `file${i}`,
|
||||
href: `http://the.net/${i}`,
|
||||
date: `2019-05-25T13:0${i}:00Z`,
|
||||
}
|
||||
}),
|
||||
bookmark: 'documents2',
|
||||
hasMore: true
|
||||
},
|
||||
documents2: {
|
||||
files: [4,5,6].map(i => {
|
||||
return {
|
||||
id: i,
|
||||
filename: `file${i}.txt`,
|
||||
content_type: 'text/plain',
|
||||
display_name: `file${i}`,
|
||||
href: `http://the.net/${i}`,
|
||||
date: `2019-05-25T13:0${i}:00Z`,
|
||||
}
|
||||
}),
|
||||
bookmark: null,
|
||||
hasMore: false
|
||||
function makeFiles(bookmark_base, extension, content_type) {
|
||||
return {
|
||||
[`${bookmark_base}1`]: {
|
||||
files: [1,2,3].map(i => {
|
||||
return {
|
||||
id: i,
|
||||
filename: `file${i}.${extension}`,
|
||||
content_type,
|
||||
display_name: `file${i}`,
|
||||
href: `http://the.net/${i}`,
|
||||
date: `2019-05-25T13:0${i}:00Z`,
|
||||
}
|
||||
}),
|
||||
bookmark: `${bookmark_base}2`,
|
||||
hasMore: true
|
||||
},
|
||||
[`${bookmark_base}2`]: {
|
||||
files: [4,5,6].map(i => {
|
||||
return {
|
||||
id: i,
|
||||
filename: `file${i}.${extension}`,
|
||||
content_type,
|
||||
display_name: `file${i}`,
|
||||
href: `http://the.net/${i}`,
|
||||
date: `2019-05-25T13:0${i}:00Z`,
|
||||
}
|
||||
}),
|
||||
bookmark: null,
|
||||
hasMore: false
|
||||
}
|
||||
}
|
||||
}
|
||||
const DOCUMENTS = makeFiles('documents', 'txt', 'text/plain')
|
||||
const MEDIA = makeFiles('media', 'mp3', 'audio/mp3')
|
||||
|
||||
const UNSPLASH_RESULTS = {
|
||||
kittens1: {
|
||||
|
@ -609,11 +613,25 @@ export function initializeCollection(endpoint) {
|
|||
};
|
||||
}
|
||||
|
||||
export function initializeDocuments() {
|
||||
export function initializeDocuments(props) {
|
||||
return {
|
||||
files: [],
|
||||
bookmark: 'documents1',
|
||||
isLoading: false
|
||||
[props.contextType]: {
|
||||
files: [],
|
||||
bookmark: 'documents1',
|
||||
isLoading: false,
|
||||
hasMore: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeMedia(props) {
|
||||
return {
|
||||
[props.contextType]: {
|
||||
files: [],
|
||||
bookmark: 'media1',
|
||||
isLoading: false,
|
||||
hasMore: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -737,10 +755,11 @@ export function getFile(id) {
|
|||
});
|
||||
}
|
||||
|
||||
export function fetchDocs(bookmark) {
|
||||
export function fetchDocs(state) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
let response
|
||||
const bookmark = state.documents[state.contextType] && state.documents[state.contextType].bookmark
|
||||
if (bookmark) {
|
||||
response = DOCUMENTS[bookmark]
|
||||
}
|
||||
|
@ -753,3 +772,22 @@ export function fetchDocs(bookmark) {
|
|||
}, FAKE_TIMEOUT)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function fetchMedia(state) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
let response
|
||||
const bookmark = state.media[state.contextType] && state.media[state.contextType].bookmark
|
||||
if (bookmark) {
|
||||
response = MEDIA[bookmark]
|
||||
}
|
||||
|
||||
if (response) {
|
||||
resolve(response)
|
||||
} else {
|
||||
reject(new Error('Invalid bookmark'))
|
||||
}
|
||||
}, FAKE_TIMEOUT)
|
||||
});
|
||||
}
|
|
@ -50,6 +50,7 @@ export default function(props = {}) {
|
|||
upload,
|
||||
images,
|
||||
documents,
|
||||
media,
|
||||
flickr,
|
||||
newPageLinkExpanded
|
||||
} = props;
|
||||
|
@ -95,6 +96,10 @@ export default function(props = {}) {
|
|||
documents = source.initializeDocuments(props);
|
||||
}
|
||||
|
||||
if (media === undefined) {
|
||||
media = source.initializeMedia(props);
|
||||
}
|
||||
|
||||
if (newPageLinkExpanded === undefined) {
|
||||
newPageLinkExpanded = false;
|
||||
}
|
||||
|
@ -111,6 +116,7 @@ export default function(props = {}) {
|
|||
upload,
|
||||
images,
|
||||
documents,
|
||||
media,
|
||||
flickr,
|
||||
newPageLinkExpanded
|
||||
};
|
||||
|
|
|
@ -39,6 +39,9 @@ describe("Sidebar initialState", () => {
|
|||
},
|
||||
initializeDocuments() {
|
||||
return {}
|
||||
},
|
||||
initializeMedia() {
|
||||
return {}
|
||||
}
|
||||
};
|
||||
apiSource = new RceApiSource();
|
||||
|
|
Loading…
Reference in New Issue