Remove Dashcard Reorder Feature Flag

Closes ADMIN-257

Test Plan:
* Ensure that you are able to reorder/drag-n-drop dashcards
  without having to enable any feature flags

Change-Id: Ia09c4ee821eb6a867a9521fdff5ccda5c599bcca
Reviewed-on: https://gerrit.instructure.com/166529
Tested-by: Jenkins
Reviewed-by: Ed Schiebel <eschiebel@instructure.com>
Reviewed-by: Carl Kibler <ckibler@instructure.com>
QA-Review: Anju Reddy <areddy@instructure.com>
Product-Review: Carl Kibler <ckibler@instructure.com>
This commit is contained in:
Dan Minkevitch 2018-10-01 15:51:57 -07:00 committed by Carl Kibler
parent 07132b9f92
commit 98a4d3c419
17 changed files with 321 additions and 390 deletions

View File

@ -155,10 +155,7 @@ module DashboardHelper
presenter.to_h
end
if @domain_root_account.feature_enabled?(:dashcard_reordering)
mapped = mapped.sort_by {|h| h[:position] || ::CanvasSort::Last}
end
mapped
mapped.sort_by {|h| h[:position] || ::CanvasSort::Last}
end
end

View File

@ -18,7 +18,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import DashboardCardBox from '../dashboard_card/DashboardCardBox'
import getDroppableDashboardCardBox from '../dashboard_card/getDroppableDashboardCardBox'
import axios from 'axios'
@ -26,17 +25,17 @@ let promiseToGetDashboardCards
const sessionStorageKey = `dashcards_for_user_${ENV && ENV.current_user_id}`
export default function loadCardDashboard () {
const Box = ENV.DASHBOARD_REORDERING_ENABLED ? getDroppableDashboardCardBox() : DashboardCardBox
export default function loadCardDashboard() {
const Box = getDroppableDashboardCardBox()
const dashboardContainer = document.getElementById('DashboardCard_Container')
function render(dashboardCards) {
ReactDOM.render(
<Box
courseCards={dashboardCards}
reorderingEnabled={ENV.DASHBOARD_REORDERING_ENABLED}
hideColorOverlays={ENV.PREFERENCES.hide_dashcard_color_overlays}
/>, dashboardContainer
/>,
dashboardContainer
)
}
@ -47,7 +46,7 @@ export default function loadCardDashboard () {
if (!promiseToGetDashboardCards) {
promiseToGetDashboardCards = axios.get('/dashboard/dashboard_cards').then(({data}) => data)
promiseToGetDashboardCards.then((dashboardCards) =>
promiseToGetDashboardCards.then(dashboardCards =>
sessionStorage.setItem(sessionStorageKey, JSON.stringify(dashboardCards))
)
}

View File

@ -17,7 +17,7 @@
*/
import _ from 'underscore'
import React, { Component } from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import I18n from 'i18n!dashcards'
import DashboardCardAction from './DashboardCardAction'
@ -25,7 +25,6 @@ import CourseActivitySummaryStore from './CourseActivitySummaryStore'
import DashboardCardMenu from './DashboardCardMenu'
export default class DashboardCard extends Component {
// ===============
// CONFIG
// ===============
@ -43,7 +42,6 @@ export default class DashboardCard extends Component {
image: PropTypes.string,
handleColorChange: PropTypes.func,
hideColorOverlays: PropTypes.bool,
reorderingEnabled: PropTypes.bool,
isDragging: PropTypes.bool,
connectDragSource: PropTypes.func,
connectDropTarget: PropTypes.func,
@ -59,20 +57,19 @@ export default class DashboardCard extends Component {
hideColorOverlays: false,
handleColorChange: () => {},
image: '',
reorderingEnabled: false,
isDragging: false,
connectDragSource: () => {},
connectDropTarget: () => {},
connectDragSource: c => c,
connectDropTarget: c => c,
moveCard: () => {},
totalCards: 0,
position: 0
}
constructor (props) {
constructor(props) {
super()
this.state = _.extend(
{ nicknameInfo: this.nicknameInfo(props.shortName, props.originalName, props.id) },
{nicknameInfo: this.nicknameInfo(props.shortName, props.originalName, props.id)},
CourseActivitySummaryStore.getStateForCourse(props.id)
)
}
@ -81,12 +78,12 @@ export default class DashboardCard extends Component {
// LIFECYCLE
// ===============
componentDidMount () {
componentDidMount() {
CourseActivitySummaryStore.addChangeListener(this.handleStoreChange)
this.parentNode = this.cardDiv
}
componentWillUnmount () {
componentWillUnmount() {
CourseActivitySummaryStore.removeChangeListener(this.handleStoreChange)
}
@ -94,48 +91,54 @@ export default class DashboardCard extends Component {
// ACTIONS
// ===============
settingsClick = (e) => {
if (e) { e.preventDefault(); }
this.toggleEditing();
settingsClick = e => {
if (e) {
e.preventDefault()
}
this.toggleEditing()
}
getCardPosition () {
getCardPosition() {
return typeof this.props.position === 'function' ? this.props.position() : this.props.position
}
handleNicknameChange = (nickname) => {
this.setState({ nicknameInfo: this.nicknameInfo(nickname, this.props.originalName, this.props.id) })
handleNicknameChange = nickname => {
this.setState({
nicknameInfo: this.nicknameInfo(nickname, this.props.originalName, this.props.id)
})
}
handleStoreChange = () => {
this.setState(
CourseActivitySummaryStore.getStateForCourse(this.props.id)
);
this.setState(CourseActivitySummaryStore.getStateForCourse(this.props.id))
}
toggleEditing = () => {
const currentState = !!this.state.editing;
this.setState({editing: !currentState});
const currentState = !!this.state.editing
this.setState({editing: !currentState})
}
headerClick = (e) => {
if (e) { e.preventDefault(); }
window.location = this.props.href;
headerClick = e => {
if (e) {
e.preventDefault()
}
window.location = this.props.href
}
doneEditing = () => {
this.setState({editing: false})
this.settingsToggle.focus();
this.settingsToggle.focus()
}
handleColorChange = (color) => {
const hexColor = `#${color}`;
handleColorChange = color => {
const hexColor = `#${color}`
this.props.handleColorChange(hexColor)
}
handleMove = (assetString, atIndex) => {
if (typeof this.props.moveCard === 'function') {
this.props.moveCard(assetString, atIndex, () => { this.settingsToggle.focus() })
this.props.moveCard(assetString, atIndex, () => {
this.settingsToggle.focus()
})
}
}
@ -143,7 +146,7 @@ export default class DashboardCard extends Component {
// HELPERS
// ===============
nicknameInfo (nickname, originalName, courseId) {
nicknameInfo(nickname, originalName, courseId) {
return {
nickname,
originalName,
@ -152,28 +155,30 @@ export default class DashboardCard extends Component {
}
}
unreadCount (icon, stream) {
unreadCount(icon, stream) {
const activityType = {
'icon-announcement': 'Announcement',
'icon-assignment': 'Message',
'icon-discussion': 'DiscussionTopic'
}[icon];
}[icon]
const itemStream = stream || [];
const streamItem = _.find(itemStream, item => (
// only return 'Message' type if category is 'Due Date' (for assignments)
item.type === activityType &&
const itemStream = stream || []
const streamItem = _.find(
itemStream,
item =>
// only return 'Message' type if category is 'Due Date' (for assignments)
item.type === activityType &&
(activityType !== 'Message' || item.notification_category === I18n.t('Due Date'))
));
)
// TODO: unread count is always 0 for assignments (see CNVS-21227)
return (streamItem) ? streamItem.unread_count : 0;
return streamItem ? streamItem.unread_count : 0
}
calculateMenuOptions () {
calculateMenuOptions() {
const position = this.getCardPosition()
const isFirstCard = position === 0;
const isLastCard = position === this.props.totalCards - 1;
const isFirstCard = position === 0
const isLastCard = position === this.props.totalCards - 1
return {
canMoveLeft: !isFirstCard,
canMoveRight: !isLastCard,
@ -186,10 +191,10 @@ export default class DashboardCard extends Component {
// RENDERING
// ===============
linksForCard () {
return this.props.links.map((link) => {
linksForCard() {
return this.props.links.map(link => {
if (!link.hidden) {
const screenReaderLabel = `${link.label} - ${this.state.nicknameInfo.nickname}`;
const screenReaderLabel = `${link.label} - ${this.state.nicknameInfo.nickname}`
return (
<DashboardCardAction
unreadCount={this.unreadCount(link.icon, this.state.stream)}
@ -199,25 +204,18 @@ export default class DashboardCard extends Component {
screenReaderLabel={screenReaderLabel}
key={link.path}
/>
);
)
}
return null;
});
return null
})
}
renderHeaderHero () {
const {
image,
backgroundColor,
hideColorOverlays
} = this.props;
renderHeaderHero() {
const {image, backgroundColor, hideColorOverlays} = this.props
if (image) {
return (
<div
className="ic-DashboardCard__header_image"
style={{backgroundImage: `url(${image})`}}
>
<div className="ic-DashboardCard__header_image" style={{backgroundImage: `url(${image})`}}>
<div
className="ic-DashboardCard__header_hero"
style={{backgroundColor, opacity: hideColorOverlays ? 0 : 0.6}}
@ -225,7 +223,7 @@ export default class DashboardCard extends Component {
aria-hidden="true"
/>
</div>
);
)
}
return (
@ -235,17 +233,13 @@ export default class DashboardCard extends Component {
onClick={this.headerClick}
aria-hidden="true"
/>
);
)
}
renderHeaderButton () {
const {
backgroundColor,
hideColorOverlays
} = this.props;
renderHeaderButton() {
const {backgroundColor, hideColorOverlays} = this.props
const reorderingProps = this.props.reorderingEnabled && {
reorderingEnabled: this.props.reorderingEnabled,
const reorderingProps = {
handleMove: this.handleMove,
currentPosition: this.getCardPosition(),
lastPosition: this.props.totalCards - 1,
@ -269,14 +263,15 @@ export default class DashboardCard extends Component {
trigger={
<button
className="Button Button--icon-action-rev ic-DashboardCard__header-button"
ref={(c) => { this.settingsToggle = c }}
ref={c => {
this.settingsToggle = c
}}
>
<i className="icon-more" aria-hidden="true" />
<span className="screenreader-only">
{ this.props.reorderingEnabled
? I18n.t('Choose a color or course nickname or move course card for %{course}', { course: nickname })
: I18n.t('Choose a color or course nickname for %{course}', { course: nickname })
}
{I18n.t('Choose a color or course nickname or move course card for %{course}', {
course: nickname
})}
</span>
</button>
}
@ -285,26 +280,31 @@ export default class DashboardCard extends Component {
)
}
render () {
render() {
const dashboardCard = (
<div
className="ic-DashboardCard"
ref={(c) => { this.cardDiv = c }}
style={{ opacity: (this.props.reorderingEnabled && this.props.isDragging) ? 0 : 1 }}
ref={c => {
this.cardDiv = c
}}
style={{opacity: this.props.isDragging ? 0 : 1}}
aria-label={this.props.originalName}
>
<div className="ic-DashboardCard__header">
<span className="screenreader-only">
{
this.props.image ?
I18n.t('Course image for %{course}', {course: this.state.nicknameInfo.nickname})
: I18n.t('Course card color region for %{course}', {course: this.state.nicknameInfo.nickname})
}
{this.props.image
? I18n.t('Course image for %{course}', {course: this.state.nicknameInfo.nickname})
: I18n.t('Course card color region for %{course}', {
course: this.state.nicknameInfo.nickname
})}
</span>
{this.renderHeaderHero()}
<a href={this.props.href} className="ic-DashboardCard__link">
<div className="ic-DashboardCard__header_content">
<h2 className="ic-DashboardCard__header-title ellipsis" title={this.props.originalName}>
<h2
className="ic-DashboardCard__header-title ellipsis"
title={this.props.originalName}
>
<span style={{color: this.props.backgroundColor}}>
{this.state.nicknameInfo.nickname}
</span>
@ -315,30 +315,23 @@ export default class DashboardCard extends Component {
>
{this.props.courseCode}
</div>
<div
className="ic-DashboardCard__header-term ellipsis"
title={this.props.term}
>
{(this.props.term) ? this.props.term : null}
<div className="ic-DashboardCard__header-term ellipsis" title={this.props.term}>
{this.props.term ? this.props.term : null}
</div>
</div>
</a>
{ this.renderHeaderButton() }
{this.renderHeaderButton()}
</div>
<nav
className="ic-DashboardCard__action-container"
aria-label={I18n.t('Actions for %{course}', {course: this.state.nicknameInfo.nickname})}
>
{ this.linksForCard() }
{this.linksForCard()}
</nav>
</div>
);
)
if (this.props.reorderingEnabled) {
const { connectDragSource, connectDropTarget } = this.props;
return connectDragSource(connectDropTarget(dashboardCard));
}
return dashboardCard;
const {connectDragSource, connectDropTarget} = this.props
return connectDragSource(connectDropTarget(dashboardCard))
}
}

View File

@ -20,105 +20,108 @@ import _ from 'underscore'
import createStore from '../shared/helpers/createStore'
import ContextColorer from 'compiled/contextColorer'
var DEFAULT_COLOR_OPTIONS = [
'#008400',
'#91349B',
'#E1185C',
'#D41E00',
'#0076B8',
'#626E7B',
'#4D3D4D',
'#254284',
'#9F7217',
'#177B63',
'#324A4D',
'#3C4F36'
];
const DEFAULT_COLOR_OPTIONS = [
'#008400',
'#91349B',
'#E1185C',
'#D41E00',
'#0076B8',
'#626E7B',
'#4D3D4D',
'#254284',
'#9F7217',
'#177B63',
'#324A4D',
'#3C4F36'
]
var customColorsHash = ENV.PREFERENCES && ENV.PREFERENCES.custom_colors || {};
const customColorsHash = (ENV.PREFERENCES && ENV.PREFERENCES.custom_colors) || {}
var DashboardCardBackgroundStore = createStore({
courseColors: customColorsHash,
usedDefaults: []
})
const DashboardCardBackgroundStore = createStore({
courseColors: customColorsHash,
usedDefaults: []
})
// ===============
// GET STATE
// ===============
// ===============
// GET STATE
// ===============
DashboardCardBackgroundStore.colorForCourse = function(courseAssetString){
return this.getCourseColors()[courseAssetString];
}
DashboardCardBackgroundStore.colorForCourse = function(courseAssetString) {
return this.getCourseColors()[courseAssetString]
}
DashboardCardBackgroundStore.getCourseColors = function(){
return this.getState()["courseColors"];
}
DashboardCardBackgroundStore.getCourseColors = function() {
return this.getState().courseColors
}
DashboardCardBackgroundStore.getUsedDefaults = function(){
return this.getState()["usedDefaults"];
}
DashboardCardBackgroundStore.getUsedDefaults = function() {
return this.getState().usedDefaults
}
// ===============
// SET STATE
// ===============
// ===============
// SET STATE
// ===============
DashboardCardBackgroundStore.setColorForCourse = function(courseAssetString, colorCode) {
const originalColors = this.getCourseColors()
const tmp = {}
tmp[courseAssetString] = colorCode
const newColors = _.extend({}, originalColors, tmp)
this.setState({courseColors: newColors})
}
DashboardCardBackgroundStore.setColorForCourse = function(courseAssetString, colorCode){
var originalColors = this.getCourseColors();
var tmp = {};
tmp[courseAssetString] = colorCode
var newColors = _.extend({}, originalColors, tmp);
this.setState({courseColors: newColors});
}
DashboardCardBackgroundStore.setDefaultColors = function(allCourseAssetStrings) {
const customCourseAssetStrings = _.keys(this.getCourseColors())
const nonCustomStrings = _.difference(allCourseAssetStrings, customCourseAssetStrings)
_.each(nonCustomStrings, courseString => this.setDefaultColor(courseString))
}
DashboardCardBackgroundStore.setDefaultColors = function(allCourseAssetStrings){
var customCourseAssetStrings = _.keys(this.getCourseColors());
var nonCustomStrings = _.difference(allCourseAssetStrings, customCourseAssetStrings)
_.each(nonCustomStrings, (courseString) =>
this.setDefaultColor(courseString)
);
}
DashboardCardBackgroundStore.setDefaultColor = function(courseAssetString) {
const colorForCourse = _.sample(this.leastUsedDefaults())
this.setColorForCourse(courseAssetString, colorForCourse)
this.markColorUsed(colorForCourse)
this.persistNewColor(courseAssetString, colorForCourse)
}
DashboardCardBackgroundStore.setDefaultColor = function(courseAssetString){
var colorForCourse = _.sample(this.leastUsedDefaults())
this.setColorForCourse(courseAssetString, colorForCourse)
this.markColorUsed(colorForCourse)
this.persistNewColor(courseAssetString, colorForCourse)
}
// ===============
// HELPERS
// ===============
// ===============
// HELPERS
// ===============
DashboardCardBackgroundStore.leastUsedDefaults = function() {
const usedDefaults = this.getUsedDefaults()
DashboardCardBackgroundStore.leastUsedDefaults = function(){
var usedDefaults = this.getUsedDefaults();
const usedColorsByFrequency = _.groupBy(
usedDefaults,
x => _.filter(usedDefaults, y => x === y).length
)
var usedColorsByFrequency = _.groupBy(usedDefaults, function(x) {
return _.filter(usedDefaults, function(y) { return x === y }).length
})
const mostCommonColors = _.uniq(
usedColorsByFrequency[
_.chain(usedColorsByFrequency)
.keys()
.max()
.value()
]
)
var mostCommonColors = _.uniq(usedColorsByFrequency[
_.chain(usedColorsByFrequency).keys().max().value()
])
return _.difference(DEFAULT_COLOR_OPTIONS, mostCommonColors).length === 0
? mostCommonColors
: _.difference(DEFAULT_COLOR_OPTIONS, mostCommonColors)
}
return _.difference(DEFAULT_COLOR_OPTIONS, mostCommonColors).length === 0 ?
mostCommonColors :
_.difference(DEFAULT_COLOR_OPTIONS, mostCommonColors)
}
// ===============
// ACTIONS
// ===============
// ===============
// ACTIONS
// ===============
DashboardCardBackgroundStore.markColorUsed = function(usedColor) {
const newUsedColors = this.getUsedDefaults().concat(usedColor)
this.setState({usedDefaults: newUsedColors})
}
DashboardCardBackgroundStore.markColorUsed = function(usedColor){
var newUsedColors = this.getUsedDefaults().concat(usedColor);
this.setState({usedDefaults: newUsedColors})
}
DashboardCardBackgroundStore.persistNewColor = function(courseAssetString, colorForCourse){
var tmp = {};
tmp[courseAssetString] = colorForCourse;
ContextColorer.persistContextColors(tmp, ENV.current_user_id);
}
DashboardCardBackgroundStore.persistNewColor = function(courseAssetString, colorForCourse) {
const tmp = {}
tmp[courseAssetString] = colorForCourse
ContextColorer.persistContextColors(tmp, ENV.current_user_id)
}
export default DashboardCardBackgroundStore

View File

@ -16,26 +16,23 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import $ from 'jquery'
import React from 'react'
import PropTypes from 'prop-types'
import DashboardCard from './DashboardCard'
import DraggableDashboardCard from './DraggableDashboardCard'
import DashboardCardBackgroundStore from './DashboardCardBackgroundStore'
import MovementUtils from './MovementUtils'
export default class DashboardCardBox extends React.Component {
static propTypes = {
courseCards: PropTypes.array,
reorderingEnabled: PropTypes.bool,
courseCards: PropTypes.arrayOf(PropTypes.object),
hideColorOverlays: PropTypes.bool,
connectDropTarget: PropTypes.func
}
static defaultProps = {
courseCards: [],
hideColorOverlays: false
hideColorOverlays: false,
connectDropTarget: el => el
}
componentWillMount() {
@ -49,10 +46,6 @@ export default class DashboardCardBox extends React.Component {
DashboardCardBackgroundStore.setDefaultColors(this.allCourseAssetStrings())
}
componentWillUnmount() {
DashboardCardBackgroundStore.removeChangeListener(this.colorsUpdated)
}
componentWillReceiveProps(newProps) {
DashboardCardBackgroundStore.setDefaultColors(this.allCourseAssetStrings())
@ -61,6 +54,10 @@ export default class DashboardCardBox extends React.Component {
})
}
componentWillUnmount() {
DashboardCardBackgroundStore.removeChangeListener(this.colorsUpdated)
}
colorsUpdated = () => {
this.forceUpdate()
}
@ -99,7 +96,7 @@ export default class DashboardCardBox extends React.Component {
}
render() {
const Component = this.props.reorderingEnabled ? DraggableDashboardCard : DashboardCard
const Component = DraggableDashboardCard
const cards = this.state.courseCards.map((card, index) => {
const position =
card.position != null ? card.position : this.getOriginalIndex.bind(this, card.assetString)
@ -117,7 +114,6 @@ export default class DashboardCardBox extends React.Component {
backgroundColor={this.colorForCard(card.assetString)}
handleColorChange={this.handleColorChange.bind(this, card.assetString)}
image={card.image}
reorderingEnabled={this.props.reorderingEnabled}
hideColorOverlays={this.props.hideColorOverlays}
position={position}
currentIndex={index}
@ -129,12 +125,7 @@ export default class DashboardCardBox extends React.Component {
const dashboardCardBox = <div className="ic-DashboardCard__box">{cards}</div>
if (this.props.reorderingEnabled) {
const {connectDropTarget} = this.props
return connectDropTarget(dashboardCardBox)
}
return dashboardCardBox
const {connectDropTarget} = this.props
return connectDropTarget(dashboardCardBox)
}
}

View File

@ -41,7 +41,6 @@ export default class DashboardCardMenu extends React.Component {
}).isRequired,
trigger: PropTypes.node.isRequired,
assetString: PropTypes.string.isRequired,
reorderingEnabled: PropTypes.bool,
popoverContentRef: PropTypes.func,
handleShow: PropTypes.func,
handleMove: PropTypes.func,
@ -56,7 +55,6 @@ export default class DashboardCardMenu extends React.Component {
}
static defaultProps = {
reorderingEnabled: false,
popoverContentRef: () => {},
handleShow: () => {},
handleMove: () => {},
@ -92,7 +90,6 @@ export default class DashboardCardMenu extends React.Component {
afterUpdateColor,
currentColor,
nicknameInfo,
reorderingEnabled,
handleMove,
handleShow,
popoverContentRef,
@ -125,7 +122,7 @@ export default class DashboardCardMenu extends React.Component {
</div>
)
const movementMenu = reorderingEnabled ? (
const movementMenu = (
<DashboardCardMovementMenu
ref={c => (this._movementMenu = c)}
cardTitle={nicknameInfo.nickname}
@ -136,12 +133,12 @@ export default class DashboardCardMenu extends React.Component {
handleMove={handleMove}
onMenuSelect={this.handleMovementMenuSelect}
/>
) : null
)
const menuStyles = {
width: 190,
height: reorderingEnabled ? 310 : 262,
paddingTop: reorderingEnabled ? 0 : 6
height: 310,
paddingTop: 0
}
return (
@ -151,9 +148,7 @@ export default class DashboardCardMenu extends React.Component {
onToggle={this.handleMenuToggle}
shouldContainFocus
shouldReturnFocus
defaultFocusElement={() =>
reorderingEnabled ? this._colorTab : document.getElementById('NicknameInput')
}
defaultFocusElement={() => this._colorTab}
onShow={handleShow}
contentRef={popoverContentRef}
>
@ -167,7 +162,7 @@ export default class DashboardCardMenu extends React.Component {
{I18n.t('Close')}
</CloseButton>
<div style={menuStyles}>
{reorderingEnabled ? (
{(
<div>
<TabList
ref={c => (this._tabList = c)}
@ -187,8 +182,6 @@ export default class DashboardCardMenu extends React.Component {
</TabPanel>
</TabList>
</div>
) : (
<div className="DashboardCardMenu">{colorPicker}</div>
)}
</div>
</PopoverContent>

View File

@ -16,51 +16,52 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { DropTarget, DragSource } from 'react-dnd'
import {DropTarget, DragSource} from 'react-dnd'
import compose from '../shared/helpers/compose'
import ItemTypes from './Types'
import DashboardCard from './DashboardCard'
const cardSource = {
beginDrag(props) {
return {
assetString: props.assetString,
originalIndex: props.currentIndex
};
},
isDragging(props, monitor) {
return monitor.getItem().assetString === props.assetString;
},
endDrag(props, monitor) {
const { assetString: draggedAssetString } = monitor.getItem();
if (!monitor.didDrop()) {
props.moveCard(draggedAssetString, props.position);
}
// TODO: Call something to actually move things to the right positions on the server
}
};
const cardTarget = {
canDrop() {
return false;
},
hover(props, monitor) {
const { assetString: draggedAssetString } = monitor.getItem();
const { assetString: overAssetString } = props;
if (draggedAssetString !== overAssetString) {
const { currentIndex: overIndex } = props;
props.moveCard(draggedAssetString, overIndex);
}
const cardSource = {
beginDrag(props) {
return {
assetString: props.assetString,
originalIndex: props.currentIndex
}
};
},
isDragging(props, monitor) {
return monitor.getItem().assetString === props.assetString
},
endDrag(props, monitor) {
const {assetString: draggedAssetString} = monitor.getItem()
if (!monitor.didDrop()) {
props.moveCard(draggedAssetString, props.position)
}
// TODO: Call something to actually move things to the right positions on the server
}
}
/* eslint-disable new-cap */
const cardTarget = {
canDrop() {
return false
},
hover(props, monitor) {
const {assetString: draggedAssetString} = monitor.getItem()
const {assetString: overAssetString} = props
if (draggedAssetString !== overAssetString) {
const {currentIndex: overIndex} = props
props.moveCard(draggedAssetString, overIndex)
}
}
}
/* eslint-disable new-cap */
export default compose(
DropTarget(ItemTypes.CARD, cardTarget, connect => ({
connectDropTarget: connect.dropTarget()
})),
DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}))
)(DashboardCard);
/* eslint-enable new-cap */
DropTarget(ItemTypes.CARD, cardTarget, connect => ({
connectDropTarget: connect.dropTarget()
})),
DragSource(ItemTypes.CARD, cardSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}))
)(DashboardCard)
/* eslint-enable new-cap */

View File

@ -16,24 +16,24 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { DragDropContext, DropTarget } from 'react-dnd'
import {DragDropContext, DropTarget} from 'react-dnd'
import ReactDnDHTML5Backend from 'react-dnd-html5-backend'
import compose from '../shared/helpers/compose'
import ItemTypes from './Types'
import DashboardCardBox from './DashboardCardBox'
const cardTarget = {
drop () {}
};
const getDroppableDashboardCardBox = (backend = ReactDnDHTML5Backend) => (
/* eslint-disable new-cap */
compose(
DragDropContext(backend),
DropTarget(ItemTypes.CARD, cardTarget, connect => ({
connectDropTarget: connect.dropTarget()
}))
)(DashboardCardBox)
/* eslint-enable new-cap */
);
const cardTarget = {
drop() {}
}
const getDroppableDashboardCardBox = (backend = ReactDnDHTML5Backend) =>
/* eslint-disable new-cap */
compose(
DragDropContext(backend),
DropTarget(ItemTypes.CARD, cardTarget, connect => ({
connectDropTarget: connect.dropTarget()
}))
)(DashboardCardBox)
/* eslint-enable new-cap */
export default getDroppableDashboardCardBox

View File

@ -41,7 +41,7 @@ class CourseForMenuPresenter
enrollmentType: course.primary_enrollment_type,
id: course.id,
image: course.feature_enabled?(:course_card_images) ? course.image : nil,
position: @context&.feature_enabled?(:dashcard_reordering) ? @user.dashboard_positions[course.asset_string] : nil,
position: @user.dashboard_positions[course.asset_string] || nil,
}.tap do |hash|
if @opts[:tabs]
tabs = course.tabs_available(@user, {

View File

@ -17,8 +17,6 @@
css_bundle :dashboard_card
js_env(:DASHBOARD_REORDERING_ENABLED => @domain_root_account.feature_enabled?(:dashcard_reordering))
default_number_of_fake_dashcards_to_show = 5
number_of_fake_cards_to_show =

View File

@ -450,16 +450,6 @@ END
root_opt_in: true,
beta: true
},
'dashcard_reordering' =>
{
display_name: -> { I18n.t('Allow Reorder Dashboard Cards') },
description: -> { I18n.t('Allow dashboard cards to be reordered for each user.') },
applies_to: 'RootAccount',
state: 'hidden',
beta: true,
development: true,
root_opt_in: false
},
'responsive_layout' =>
{
display_name: -> { I18n.t('Responsive Layout') },

View File

@ -19,7 +19,8 @@
import React from 'react'
import ReactDOM from 'react-dom'
import TestUtils from 'react-dom/test-utils'
import DashboardCardBox from 'jsx/dashboard_card/DashboardCardBox'
import ReactDndTestBackend from 'react-dnd-test-backend'
import getDroppableDashboardCardBox from 'jsx/dashboard_card/getDroppableDashboardCardBox'
import CourseActivitySummaryStore from 'jsx/dashboard_card/CourseActivitySummaryStore'
QUnit.module('DashboardCardBox', {
@ -45,7 +46,8 @@ QUnit.module('DashboardCardBox', {
})
test('should render div.ic-DashboardCard per provided courseCard', function() {
const CardBox = <DashboardCardBox courseCards={this.courseCards} />
const Box = getDroppableDashboardCardBox(ReactDndTestBackend)
const CardBox = <Box connectDropTarget={el => el} courseCards={this.courseCards} />
this.component = TestUtils.renderIntoDocument(CardBox)
const $html = $(ReactDOM.findDOMNode(this.component))
equal($html.children('div.ic-DashboardCard').length, this.courseCards.length)

View File

@ -47,6 +47,8 @@ QUnit.module('DashboardCard', {
id: '1',
backgroundColor: '#EF4437',
image: null,
connectDragSource: c => c,
connectDropTarget: c => c
}
return sandbox.stub(CourseActivitySummaryStore, 'getStateForCourse').returns({})
},

View File

@ -74,10 +74,9 @@ describe DashboardHelper do
end
describe "map_courses_for_menu" do
context "with Dashcard Reordering feature enabled" do
context "Dashcard Reordering" do
before(:each) do
@account = Account.default
@account.enable_feature! :dashcard_reordering
@domain_root_account = @account
end

View File

@ -35,7 +35,6 @@ const defaultProps = () => ({
})
const defaultMovementMenuProps = () => ({
reorderingEnabled: true,
menuOptions: {
canMoveLeft: false,
canMoveRight: true,
@ -49,31 +48,7 @@ const getTabWithText = (text) => {
return tabs.filter(tab => tab.textContent.trim() === text)[0]
}
QUnit.module('DashboardCardMenu - reordering disabled', {
setup () {
this.wrapper = mount(<DashboardCardMenu {...defaultProps()} />)
},
teardown () {
this.wrapper.unmount()
}
})
test('it should render with only a color picker if reordering is not enabled', function (assert) {
const done = assert.async()
const handleShow = () => {
ok(this.wrapper.instance()._colorPicker)
notOk(this.wrapper.instance()._tabList)
done()
}
this.wrapper.setProps({ handleShow }, () => {
this.wrapper.find('button').simulate('click')
})
})
QUnit.module('DashboardCardMenu - reordering enabled', {
QUnit.module('DashboardCardMenu - reordering', {
setup () {
this.wrapper = mount(<DashboardCardMenu {...defaultProps()} {...defaultMovementMenuProps()} />)
},
@ -83,7 +58,7 @@ QUnit.module('DashboardCardMenu - reordering enabled', {
}
})
test('it should render a tabList with colorpicker and movement menu with reordering enabled', function (assert) {
test('it should render a tabList with colorpicker and movement menu', function (assert) {
const done = assert.async()
const handleShow = () => {

View File

@ -16,98 +16,87 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom/test-utils';
import { DragDropContext } from 'react-dnd';
import ReactDndTestBackend from 'react-dnd-test-backend';
import DraggableDashboardCard from 'jsx/dashboard_card/DraggableDashboardCard';
import getDroppableDashboardCardBox from 'jsx/dashboard_card/getDroppableDashboardCardBox';
import DashboardCardBox from 'jsx/dashboard_card/DashboardCardBox';
import DashboardCard from 'jsx/dashboard_card/DashboardCard';
import DashboardCardMovementMenu from 'jsx/dashboard_card/DashboardCardMovementMenu';
import fakeENV from 'helpers/fakeENV';
import React from 'react'
import TestUtils from 'react-dom/test-utils'
import ReactDndTestBackend from 'react-dnd-test-backend'
import DraggableDashboardCard from 'jsx/dashboard_card/DraggableDashboardCard'
import getDroppableDashboardCardBox from 'jsx/dashboard_card/getDroppableDashboardCardBox'
import fakeENV from 'helpers/fakeENV'
let cards;
let fakeServer;
let cards
let fakeServer
QUnit.module('DashboardCard Reordering', {
setup () {
fakeENV.setup({
DASHBOARD_REORDERING_ENABLED: true
});
cards = [{
QUnit.module('DashboardCard Reordering', {
setup() {
cards = [
{
id: 1,
assetString: 'course_1',
position: 0,
originalName: 'Intro to Dashcards 1',
shortName: 'Dash 101'
}, {
},
{
id: 2,
assetString: 'course_2',
position: 1,
originalName: 'Intermediate Dashcarding',
shortName: 'Dash 201'
}, {
},
{
id: 3,
assetString: 'course_3',
originalName: 'Advanced Dashcards',
shortName: 'Dash 301'
}];
}
]
fakeServer = sinon.fakeServer.create();
},
teardown () {
fakeENV.teardown();
cards = null;
fakeServer.restore();
}
});
fakeServer = sinon.fakeServer.create()
},
teardown() {
fakeENV.teardown()
cards = null
fakeServer.restore()
}
})
test('it renders', () => {
const Box = getDroppableDashboardCardBox()
const root = TestUtils.renderIntoDocument(
<Box reorderingEnabled courseCards={cards} />
);
ok(root);
});
test('it renders', () => {
const Box = getDroppableDashboardCardBox()
const root = TestUtils.renderIntoDocument(<Box courseCards={cards} />)
ok(root)
})
test('cards have opacity of 0 while moving', () => {
const Card = DraggableDashboardCard.DecoratedComponent;
const card = TestUtils.renderIntoDocument(
<Card
{...cards[0]}
connectDragSource={el => el}
connectDropTarget={el => el}
isDragging
reorderingEnabled
/>
);
const div = TestUtils.findRenderedDOMComponentWithClass(card, 'ic-DashboardCard')
equal(div.style.opacity, 0);
});
test('cards have opacity of 0 while moving', () => {
const Card = DraggableDashboardCard.DecoratedComponent
const card = TestUtils.renderIntoDocument(
<Card {...cards[0]} connectDragSource={el => el} connectDropTarget={el => el} isDragging />
)
const div = TestUtils.findRenderedDOMComponentWithClass(card, 'ic-DashboardCard')
equal(div.style.opacity, 0)
})
test('moving a card adjusts the position property', () => {
const Box = getDroppableDashboardCardBox(ReactDndTestBackend);
const root = TestUtils.renderIntoDocument(
<Box
reorderingEnabled
courseCards={cards}
connectDropTarget={el => el}
/>
);
test('moving a card adjusts the position property', () => {
const Box = getDroppableDashboardCardBox(ReactDndTestBackend)
const root = TestUtils.renderIntoDocument(
<Box courseCards={cards} connectDropTarget={el => el} />
)
const backend = root.getManager().getBackend();
const renderedCardComponents = TestUtils.scryRenderedComponentsWithType(root, DraggableDashboardCard);
const sourceHandlerId = renderedCardComponents[0].getDecoratedComponentInstance().getHandlerId();
const targetHandlerId = renderedCardComponents[1].getHandlerId();
const backend = root.getManager().getBackend()
const renderedCardComponents = TestUtils.scryRenderedComponentsWithType(
root,
DraggableDashboardCard
)
const sourceHandlerId = renderedCardComponents[0].getDecoratedComponentInstance().getHandlerId()
const targetHandlerId = renderedCardComponents[1].getHandlerId()
backend.simulateBeginDrag([sourceHandlerId]);
backend.simulateHover([targetHandlerId]);
backend.simulateDrop();
backend.simulateBeginDrag([sourceHandlerId])
backend.simulateHover([targetHandlerId])
backend.simulateDrop()
const renderedAfterDragNDrop = TestUtils.scryRenderedDOMComponentsWithClass(root, 'ic-DashboardCard');
equal(renderedAfterDragNDrop[0].getAttribute('aria-label'), 'Intermediate Dashcarding');
equal(renderedAfterDragNDrop[1].getAttribute('aria-label'), 'Intro to Dashcards 1');
});
const renderedAfterDragNDrop = TestUtils.scryRenderedDOMComponentsWithClass(
root,
'ic-DashboardCard'
)
equal(renderedAfterDragNDrop[0].getAttribute('aria-label'), 'Intermediate Dashcarding')
equal(renderedAfterDragNDrop[1].getAttribute('aria-label'), 'Intro to Dashcards 1')
})

View File

@ -71,10 +71,9 @@ describe CourseForMenuPresenter do
expect(h[:shortName]).to eq 'nickname'
end
context 'with Dashcard Reordering feature enabled' do
context 'Dashcard Reordering' do
before(:each) do
@account = Account.default
@account.enable_feature! :dashcard_reordering
end
it 'returns a position if one is set' do