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
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@
</VListTileAction>
</template>

<template #copy-fail-retry-action>
<span v-if="hasCopyingErrored">
<ActionLink class="copy-retry-btn" :text="$tr('retryCopy')" @click="retryFailedCopy" />
</span>
</template>

<template #copy-fail-remove-action>
<IconButton
v-if="hasCopyingErrored"
icon="close"
:text="$tr('removeNode')"
size="small"
@click="removeFailedCopyNode"
/>
</template>

<template #context-menu="{ showContextMenu, positionX, positionY }">
<ContentNodeContextMenu
:show="showContextMenu"
Expand All @@ -81,17 +97,19 @@

<script>

import { mapGetters } from 'vuex';
import { mapGetters, mapActions } from 'vuex';

import ContentNodeListItem from './ContentNodeListItem';
import ContentNodeOptions from './ContentNodeOptions';
import ContentNodeContextMenu from './ContentNodeContextMenu';
import Checkbox from 'shared/views/form/Checkbox';
import IconButton from 'shared/views/IconButton';
import DraggableItem from 'shared/views/draggable/DraggableItem';
import { COPYING_FLAG } from 'shared/data/constants';
import { ContentNode } from 'shared/data/resources';
import { DragEffect, DropEffect, EffectAllowed } from 'shared/mixins/draggable/constants';
import { DraggableRegions } from 'frontend/channelEdit/constants';
import { withChangeTracker } from 'shared/data/changes';
import { COPYING_STATUS, COPYING_STATUS_VALUES } from 'shared/data/constants';

export default {
name: 'ContentNodeEditListItem',
Expand Down Expand Up @@ -141,7 +159,11 @@
},
computed: {
...mapGetters('currentChannel', ['canEdit']),
...mapGetters('contentNode', ['getContentNode']),
...mapGetters('contentNode', [
'getContentNode',
'isNodeInCopyingState',
'hasNodeCopyingErrored',
]),
...mapGetters('draggable', ['activeDraggableRegionId']),
selected: {
get() {
Expand All @@ -163,7 +185,10 @@
return !this.copying;
},
copying() {
return this.contentNode[COPYING_FLAG];
return this.isNodeInCopyingState(this.nodeId);
},
hasCopyingErrored() {
return this.hasNodeCopyingErrored(this.nodeId);
},
dragEffect() {
return DragEffect.SORT;
Expand Down Expand Up @@ -206,18 +231,57 @@
this.selected = false;
}
},
methods: {
...mapActions(['showSnackbar', 'clearSnackbar']),
...mapActions('contentNode', [
'updateContentNode',
'waitForCopyingStatus',
'deleteContentNode',
]),
retryFailedCopy: withChangeTracker(function(changeTracker) {
this.updateContentNode({
id: this.nodeId,
[COPYING_STATUS]: COPYING_STATUS_VALUES.COPYING,
});

this.showSnackbar({
duration: null,
text: this.$tr('creatingCopies'),
// TODO: determine how to cancel copying while it's in progress,
// TODO: if that's something we want
// actionText: this.$tr('cancel'),
// actionCallback: () => changeTracker.revert(),
});

ContentNode.retryCopyChange(this.nodeId);

return this.waitForCopyingStatus({
contentNodeId: this.nodeId,
startingRev: changeTracker._startingRev,
})
.then(() => {
this.showSnackbar({
text: this.$tr('copiedSnackbar'),
actionText: this.$tr('undo'),
actionCallback: () => changeTracker.revert(),
}).then(() => changeTracker.cleanUp());
})
.catch(() => {
this.clearSnackbar();
changeTracker.cleanUp();
});
}),
removeFailedCopyNode() {
return this.deleteContentNode(this.nodeId);
},
},
$trs: {
optionsTooltip: 'Options',
/* eslint-disable kolibri/vue-no-unused-translations */
/**
* Strings for handling copy failures
*/
removeNode: 'Remove',
retryCopy: 'Retry',
creatingCopies: 'Copying...',
copiedSnackbar: 'Copy operation complete',
undo: 'Undo',
/* eslint-enable kolibri/vue-no-unused-translations */
},
};

Expand Down Expand Up @@ -298,4 +362,8 @@
justify-content: center;
}

.copy-retry-btn {
font-size: inherit;
}

</style>
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ const TOPIC_NODE = {

function mountComponent(opts = {}) {
return mount(ContentNodeListItem, {
store: createStore(),
store: createStore({
modules: {
contentNode: {
namespaced: true,
getters: {
isNodeInCopyingState: () => jest.fn(),
},
},
},
}),
...opts,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,20 @@
</template>
<template v-else>
<div class="copying">
<p class="caption grey--text pr-2 pt-1">
{{ $tr("copyingTask") }}
<p class="caption pr-3">
<span
class="grey--text"
:style="{ 'cursor': hasCopyingErrored ? 'default' : 'progress' }"
>
{{ copyingMessage }}
</span>
<slot name="copy-fail-retry-action"></slot>
</p>
<ContentNodeCopyTaskProgress :taskId="taskId" size="30" />
<ContentNodeCopyTaskProgress
:node="node"
size="30"
/>
<slot name="copy-fail-remove-action"></slot>
</div>
<div class="disabled-overlay"></div>
</template>
Expand All @@ -190,6 +200,7 @@

<script>

import { mapGetters } from 'vuex';
import camelCase from 'lodash/camelCase';
import ContentNodeCopyTaskProgress from '../../views/progress/ContentNodeCopyTaskProgress';
import ContentNodeChangedIcon from '../ContentNodeChangedIcon';
Expand All @@ -203,7 +214,6 @@
import ContextMenuCloak from 'shared/views/ContextMenuCloak';
import DraggableHandle from 'shared/views/draggable/DraggableHandle';
import { titleMixin, metadataTranslationMixin } from 'shared/mixins';
import { COPYING_FLAG, TASK_ID } from 'shared/data/constants';
import { EffectAllowed } from 'shared/mixins/draggable/constants';
import ContentNodeLearningActivityIcon from 'shared/views/ContentNodeLearningActivityIcon';

Expand Down Expand Up @@ -256,6 +266,7 @@
};
},
computed: {
...mapGetters('contentNode', ['isNodeInCopyingState', 'hasNodeCopyingErrored']),
isCompact() {
return this.compact || !this.$vuetify.breakpoint.mdAndUp;
},
Expand Down Expand Up @@ -290,10 +301,17 @@
return !this.$scopedSlots['context-menu'] || this.copying;
},
copying() {
return this.node[COPYING_FLAG];
return this.isNodeInCopyingState(this.node.id);
},
taskId() {
return this.node[TASK_ID];
hasCopyingErrored() {
return this.hasNodeCopyingErrored(this.node.id);
},
copyingMessage() {
if (this.hasCopyingErrored) {
return this.$tr('copyingError');
} else {
return this.$tr('copyingTask');
}
},
},
watch: {
Expand Down Expand Up @@ -353,12 +371,7 @@
'{value, number, integer} {value, plural, one {resource for coaches} other {resources for coaches}}',
coachTooltip: 'Resource for coaches',
copyingTask: 'Copying',
/* eslint-disable kolibri/vue-no-unused-translations */
/**
* String for handling copy failures
*/
copyingError: 'Copy failed.',
/* eslint-enable kolibri/vue-no-unused-translations */
},
};

Expand Down Expand Up @@ -404,16 +417,15 @@
.copying {
z-index: 2;
display: flex;
padding-top: 44px;
cursor: progress;
align-items: center;
align-self: center;
min-width: max-content;
pointer-events: auto;
cursor: default;

p,
div {
margin: 0 2px;
}

.compact & {
padding-top: 12px;
margin: 0;
}
}

Expand Down Expand Up @@ -466,6 +478,7 @@
}

.description-col {
flex-shrink: 1 !important;
width: calc(100% - @thumbnail-width - 206px);
word-break: break-word;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import { mapActions, mapGetters } from 'vuex';
import { RouteNames, TabNames } from '../constants';
import MoveModal from './move/MoveModal';
import { ContentNode } from 'shared/data/resources';
import { withChangeTracker } from 'shared/data/changes';
import { RELATIVE_TREE_POSITIONS } from 'shared/data/constants';

Expand Down Expand Up @@ -104,8 +103,13 @@
},
},
methods: {
...mapActions(['showSnackbar']),
...mapActions('contentNode', ['createContentNode', 'moveContentNodes', 'copyContentNode']),
...mapActions(['showSnackbar', 'clearSnackbar']),
...mapActions('contentNode', [
'createContentNode',
'moveContentNodes',
'copyContentNode',
'waitForCopyingStatus',
]),
...mapActions('clipboard', ['copy']),
newTopicNode() {
this.trackAction('New topic');
Expand Down Expand Up @@ -177,7 +181,7 @@
}
);
}),
duplicateNode: withChangeTracker(function(changeTracker) {
duplicateNode: withChangeTracker(async function(changeTracker) {
this.trackAction('Copy');
this.showSnackbar({
duration: null,
Expand All @@ -187,19 +191,27 @@
// actionText: this.$tr('cancel'),
// actionCallback: () => changeTracker.revert(),
});
return this.copyContentNode({
const copiedContentNode = await this.copyContentNode({
id: this.nodeId,
target: this.nodeId,
position: RELATIVE_TREE_POSITIONS.RIGHT,
}).then(node => {
ContentNode.waitForCopying([node.id]).then(() => {
});

this.waitForCopyingStatus({
contentNodeId: copiedContentNode.id,
startingRev: changeTracker._startingRev,
})
.then(() => {
this.showSnackbar({
text: this.$tr('copiedSnackbar'),
actionText: this.$tr('undo'),
actionCallback: () => changeTracker.revert(),
}).then(() => changeTracker.cleanUp());
})
.catch(() => {
this.clearSnackbar();
changeTracker.cleanUp();
});
});
}),
trackAction(eventLabel) {
this.$analytics.trackAction('channel_editor_node', 'Click', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const initWrapper = ({ getters = {}, mutations = {}, actions = {}, propsData = {
getContentNode: () => jest.fn(),
getContentNodeChildren: () => jest.fn(),
nodeExpanded: () => jest.fn(),
isNodeInCopyingState: () => jest.fn(),
...getters,
},
mutations: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@
>
<ContentNodeCopyTaskProgress
class="progress-loader"
:taskId="taskId"
:node="node"
size="24"
showTooltip
/>
</VFlex>
<VFlex
Expand Down Expand Up @@ -194,7 +195,6 @@
import DraggableItem from 'shared/views/draggable/DraggableItem';
import DraggableHandle from 'shared/views/draggable/DraggableHandle';
import { titleMixin } from 'shared/mixins';
import { COPYING_FLAG, TASK_ID } from 'shared/data/constants';
import { DropEffect, EffectAllowed } from 'shared/mixins/draggable/constants';
import { objectValuesValidator } from 'shared/mixins/draggable/utils';

Expand Down Expand Up @@ -263,7 +263,12 @@
...mapGetters('currentChannel', ['canEdit']),
...mapState('draggable', ['activeDraggableUniverse']),
...mapGetters('draggable', ['deepestActiveDraggable']),
...mapGetters('contentNode', ['getContentNode', 'getContentNodeChildren', 'nodeExpanded']),
...mapGetters('contentNode', [
'getContentNode',
'getContentNodeChildren',
'nodeExpanded',
'isNodeInCopyingState',
]),
node() {
return this.getContentNode(this.nodeId);
},
Expand Down Expand Up @@ -301,10 +306,7 @@
return EffectAllowed.NONE;
},
copying() {
return this.node && this.node[COPYING_FLAG];
},
taskId() {
return this.node && this.node[TASK_ID];
return this.node && this.isNodeInCopyingState(this.node.id);
},
activeDropEffect() {
// Don't allow dropping into itself
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const GETTERS = {
getContentNodeChildren: () => jest.fn(),
getContentNodeAncestors: () => jest.fn(),
getContentNode: () => jest.fn(),
isNodeInCopyingState: () => jest.fn(),
},
};

Expand Down
Loading