From 2711579227bd72747db5badb2629b50e261a9d57 Mon Sep 17 00:00:00 2001 From: Jordan Yoshihara Date: Tue, 12 Feb 2019 17:18:46 -0800 Subject: [PATCH 01/38] Got channel list to load and set active channels --- .../js/edit_channel/channel_list/constants.js | 16 + .../js/edit_channel/channel_list/store.js | 74 +++++ .../js/edit_channel/channel_list/views.js | 10 + .../channel_list/views/ChannelItem.vue | 289 ++++++++++++++++++ .../channel_list/views/ChannelList.vue | 120 ++++++++ .../channel_list/views/ChannelListPage.vue | 108 +++++++ .../static/js/edit_channel/models.js | 4 - .../static/js/edit_channel/router.js | 12 +- .../static/less/channel_list.less | 70 +++++ 9 files changed, 696 insertions(+), 7 deletions(-) create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/views.js create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue create mode 100644 contentcuration/contentcuration/static/less/channel_list.less diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js b/contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js new file mode 100644 index 0000000000..40813d3899 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js @@ -0,0 +1,16 @@ +const State = require("edit_channel/state"); + +export const ListTypes = { + EDITABLE: 'EDITABLE', + STARRED: 'STARRED', + VIEW_ONLY: 'VIEW_ONLY', + PUBLIC: 'PUBLIC', + // COLLECTIONS: 'COLLECTIONS' +}; + +export const ChannelListGetFunctions = { + [ListTypes.EDITABLE]: State.current_user.get_channels, + [ListTypes.STARRED]: State.current_user.get_bookmarked_channels, + [ListTypes.VIEW_ONLY]: State.current_user.get_view_only_channels, + [ListTypes.PUBLIC]: State.current_user.get_public_channels +} diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js new file mode 100644 index 0000000000..fadecb0582 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js @@ -0,0 +1,74 @@ +import Vue from 'vue'; +import _ from 'underscore'; +var Vuex = require('vuex'); +var { ListTypes, ChannelListGetFunctions } = require('./constants'); +Vue.use(Vuex); + +var store = new Vuex.Store({ + modules: { + "channel_list": { + namespaced: true, + state: { + activeList: ListTypes.EDITABLE, + channels: [], + activeChannel: null + }, + getters: { + activeList(state) { + return state.activeList; + }, + activeChannel(state) { + return state.activeChannel; + }, + channels(state) { + return state.channels; + }, + starredChannels(state) { + return _.where(state.channels, {[ListTypes.STARRED]: true}); + } + }, + mutations: { + RESET_CHANNEL_STATE(state) { + Object.assign(state, { + activeList: ListTypes.EDITABLE, + channelLists: {}, + channels: [], + activeChannel: null + }); + }, + SET_ACTIVE_LIST(state, listType) { + state.activeList = listType; + }, + SET_ACTIVE_CHANNEL(state, channel) { + state.activeChannel = channel; + }, + SET_CHANNEL_LIST(state, payload) { + _.each(payload.channels, (channel)=> { + let match = _.findWhere(state.channels, {id: channel.id}) + if(match) { // If it exists, set the existing channel's listType to true + match[payload.listType] = true; + } else { // Otherwise, add to the list of channels + channel[payload.listType] = true; + state.channels.push(channel); + } + }); + } + }, + actions: { + loadChannelList: function(context, listType) { + return new Promise((resolve, reject) => { + ChannelListGetFunctions[listType]().then(function (channels) { + context.commit('SET_CHANNEL_LIST', { + listType: listType, + channels: channels.toJSON() + }); + resolve(channels); + }); + }) + } + } + } + } +}); + +module.exports = store; diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views.js b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views.js new file mode 100644 index 0000000000..990c4ad466 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views.js @@ -0,0 +1,10 @@ + +import Vue from 'vue'; +var Vuex = require('vuex'); +import ChannelListPageComponent from './views/ChannelListPage.vue'; + +Vue.use(Vuex); + +var ChannelListPage = Vue.extend(ChannelListPageComponent); + +module.exports = ChannelListPage; diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue new file mode 100644 index 0000000000..2a4cfd6db4 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue @@ -0,0 +1,289 @@ + + + + + + diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue new file mode 100644 index 0000000000..e10bfecdc4 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue @@ -0,0 +1,120 @@ + + + + + + + diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue new file mode 100644 index 0000000000..f2db97f5bb --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue @@ -0,0 +1,108 @@ + + + + + + + diff --git a/contentcuration/contentcuration/static/js/edit_channel/models.js b/contentcuration/contentcuration/static/js/edit_channel/models.js index 64101948d3..14526c4d4c 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/models.js +++ b/contentcuration/contentcuration/static/js/edit_channel/models.js @@ -218,7 +218,6 @@ var UserModel = BaseModel.extend({ error: reject, success: function (data) { var collection = new ChannelCollection(data); - collection.each(function (item) { item.set("is_bookmarked", _.contains(self.get("bookmarks"), item.id)); }); resolve(collection); } }); @@ -233,7 +232,6 @@ var UserModel = BaseModel.extend({ error: reject, success: function (data) { var collection = new ChannelCollection(data); - collection.each(function (item) { item.set("is_bookmarked", _.contains(self.get("bookmarks"), item.id)); }); resolve(collection); } }); @@ -248,7 +246,6 @@ var UserModel = BaseModel.extend({ error: reject, success: function (data) { var collection = new ChannelCollection(data); - collection.each(function (item) { item.set("is_bookmarked", true); }); resolve(collection); } }); @@ -263,7 +260,6 @@ var UserModel = BaseModel.extend({ error: reject, success: function (data) { var collection = new ChannelCollection(data); - collection.each(function (item) { item.set("is_bookmarked", _.contains(self.get("bookmarks"), item.id)); }); resolve(collection); } }); diff --git a/contentcuration/contentcuration/static/js/edit_channel/router.js b/contentcuration/contentcuration/static/js/edit_channel/router.js index e6e4417717..f2c4323b20 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/router.js +++ b/contentcuration/contentcuration/static/js/edit_channel/router.js @@ -2,6 +2,8 @@ var _ = require("underscore"); var Backbone = require("backbone"); var State = require("./state"); +import Vue from 'vue'; + //var saveDispatcher = _.clone(Backbone.Events); var URL_CHAR_LIMIT = 7; @@ -19,9 +21,13 @@ var ChannelEditRouter = Backbone.Router.extend({ }, navigate_channel_home: function() { - var ChannelManageView = require("edit_channel/new_channel/views"); - var channel_manager_view = new ChannelManageView.ChannelListPage ({ - el: $("#channel-container"), + var ChannelListPage = require("edit_channel/channel_list/views"); + var store = require("edit_channel/channel_list/store"); + + new Vue({ + el: '#channel-container', + store, + render: h => h(ChannelListPage) }); }, diff --git a/contentcuration/contentcuration/static/less/channel_list.less b/contentcuration/contentcuration/static/less/channel_list.less new file mode 100644 index 0000000000..460e9b6af9 --- /dev/null +++ b/contentcuration/contentcuration/static/less/channel_list.less @@ -0,0 +1,70 @@ +@import "global-variables.less"; + +@channel-preview-width: 650px; +@channel-item-width: 70vw; +@channel-item-max-width: 800px; +@channel-item-min-width: 400px; + +// nested flexbox settings to ensure that the channels list view is vertically responsive. +body { + display: flex; + flex-direction: column; + + #channel-container { + display: flex; + height: 0; + flex-direction: column; + flex-grow: 1; + + #top-navigation { + flex: 0; + } + + #channel-area-wrapper { + display: flex; + max-height: 100%; + flex: 1; + + #channel-list-wrapper { + display: grid; + justify-content: normal; + align-content: flex-start; + + #channel-list-area { + margin: auto; + } + } + + #channel_preview_wrapper { + display: flex; + flex-direction: column; + flex: 1; + + #channel_details_overlay { + display: flex; + flex-direction: column; + flex: 1; + + #channel_details_panel { + display: flex; + flex-direction: column; + flex: 1; + + #channel_details_view_panel { + display: flex; + flex-direction: column; + flex: 1; + + #channel_details { + flex-direction: column; + flex-grow: 1; + width: 100%; + } + } + } + } + } + } + } +} +// end of flexbox settings From 73994198cc720abd34bb77ec408ce9ee2d54b3dd Mon Sep 17 00:00:00 2001 From: Jordan Yoshihara Date: Wed, 13 Feb 2019 13:09:12 -0800 Subject: [PATCH 02/38] Added starring functions --- .../js/edit_channel/channel_list/store.js | 62 +++++++++++++++++-- .../channel_list/views/ChannelItem.vue | 40 +++++++----- .../channel_list/views/ChannelList.vue | 5 +- 3 files changed, 84 insertions(+), 23 deletions(-) diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js index fadecb0582..eadf477429 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js @@ -1,15 +1,35 @@ import Vue from 'vue'; import _ from 'underscore'; +import State from 'edit_channel/state'; + var Vuex = require('vuex'); var { ListTypes, ChannelListGetFunctions } = require('./constants'); Vue.use(Vuex); +console.log(window.location.hash.substr(1)) + +let defaultListType = ListTypes.EDITABLE; +switch(window.location.hash.substr(1)) { + case "starred": + defaultListType = ListTypes.STARRED; + break; + case "viewonly": + defaultListType = ListTypes.VIEWONLY; + break; + case "public": + defaultListType = ListTypes.PUBLIC; + break; + // case "collection": + // defaultListType = ListTypes.COLLECTION; + // break; +} + var store = new Vuex.Store({ modules: { "channel_list": { namespaced: true, state: { - activeList: ListTypes.EDITABLE, + activeList: defaultListType, channels: [], activeChannel: null }, @@ -22,9 +42,6 @@ var store = new Vuex.Store({ }, channels(state) { return state.channels; - }, - starredChannels(state) { - return _.where(state.channels, {[ListTypes.STARRED]: true}); } }, mutations: { @@ -43,12 +60,15 @@ var store = new Vuex.Store({ state.activeChannel = channel; }, SET_CHANNEL_LIST(state, payload) { + let listValues = _.values(ListTypes); _.each(payload.channels, (channel)=> { let match = _.findWhere(state.channels, {id: channel.id}) if(match) { // If it exists, set the existing channel's listType to true match[payload.listType] = true; } else { // Otherwise, add to the list of channels - channel[payload.listType] = true; + _.each(listValues, (type) => { // Need to set all attributes so vue will listen know to listen to them + channel[type] = payload.listType === type; + }); state.channels.push(channel); } }); @@ -64,7 +84,37 @@ var store = new Vuex.Store({ }); resolve(channels); }); - }) + }); + }, + addStar: function(context, channel) { + channel.STARRING = true; + $.ajax({ + method: "POST", + data: JSON.stringify({ + "channel_id": channel.id, + "user_id": State.current_user.id + }), + url: window.Urls.add_bookmark(), + success: () => { + channel.STARRED = true; + channel.STARRING = false; + } + }); + }, + removeStar: function(context, channel) { + channel.STARRING = true; + $.ajax({ + method: "POST", + data: JSON.stringify({ + "channel_id": channel.id, + "user_id": State.current_user.id + }), + url: window.Urls.remove_bookmark(), + success: () => { + channel.STARRED = false; + channel.STARRING = false; + } + }); } } } diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue index 2a4cfd6db4..07ddb1adee 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue @@ -17,7 +17,7 @@ {{copyIcon}} @@ -25,16 +25,17 @@
{{ $tr('unpublishedText') }}
+
{{channel.STARRED}}

- {{isStarred? 'star' : 'star_border'}}

{{channel.name}}

@@ -52,7 +53,7 @@ + + + diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelSetList.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelSetList.vue new file mode 100644 index 0000000000..89e1c676a8 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelSetList.vue @@ -0,0 +1,94 @@ + + + + + + diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/CopyToken.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/CopyToken.vue new file mode 100644 index 0000000000..161bd353f4 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/CopyToken.vue @@ -0,0 +1,91 @@ + + + + + + diff --git a/contentcuration/contentcuration/static/less/channel_list.less b/contentcuration/contentcuration/static/less/channel_list.less index 460e9b6af9..bb7459edd4 100644 --- a/contentcuration/contentcuration/static/less/channel_list.less +++ b/contentcuration/contentcuration/static/less/channel_list.less @@ -4,6 +4,9 @@ @channel-item-width: 70vw; @channel-item-max-width: 800px; @channel-item-min-width: 400px; +@channel-container-height: 250px; +@channel-profile-width: 150px; +@channel-thumbnail-size: 130px; // nested flexbox settings to ensure that the channels list view is vertically responsive. body { @@ -68,3 +71,100 @@ body { } } // end of flexbox settings + +.channel-container-wrapper { + background-color: @panel-container-color; + width: @channel-item-width; + max-width: @channel-item-max-width; + min-width: @channel-item-min-width; + box-shadow: @box-shadow; + margin-bottom: 30px; + padding: 10px; + border:4px solid @panel-container-color; + cursor:pointer; + position: relative; + + .profile { + width: @channel-profile-width; + margin: 5px 15px 5px 5px; + text-align: center; + float: left; + img{ + width:@channel-thumbnail-size; + height:@channel-thumbnail-size; + object-fit: cover; + } + span { + font-size: 65pt; + color: @gray-700; + } + } + .channel-metadata { + color: @annotation-gray; + font-size: 11pt; + div { + display: inline-block; + &:not(:last-child)::after { + content: ' • '; + } + + .channel-language { + .truncate; + max-width: 135px; + } + .copy-id-btn{ + padding:3px; + font-size: 16pt; + vertical-align: sub; + &:hover { color:@blue-500; } + } + input { + display: inline-block; + padding: 2px; + background-color: @gray-300; + font-size: 11pt; + border:none; + font-weight: bold; + width: 120px; + text-align: center; + color: @gray-700; + } + } + } + + h4 { + font-size: 18pt; + font-weight: bold; + color: @body-font-color; + .option { + display:inline-block; + float: right; + font-size: 20pt; + padding:10px; + color: @gray-700; + position: relative; + top: -40px; + &.star-option::before { + .material-icons; + content: "star_border"; + } + &.star-option:hover{ + color: @blue-500; + } + &.starred{ + color: @blue-500; + &::before { + content: "star"; + } + &:hover{ + color: @blue-200; + } + } + } + } + .description { + .wordwrap; + font-size: 11pt; + margin-bottom:5px; + } +} From 1f503a12d8d5c46b1b582e529647a019e7593e6a Mon Sep 17 00:00:00 2001 From: Jordan Yoshihara Date: Wed, 13 Feb 2019 19:22:14 -0800 Subject: [PATCH 04/38] Added invitations --- .../js/edit_channel/channel_list/store.js | 48 ++++- .../views/ChannelInvitationItem.vue | 190 ++++++++++++++++++ .../views/ChannelInvitationList.vue | 61 ++++++ .../channel_list/views/ChannelListPage.vue | 49 ++++- .../static/less/channel_list.less | 25 ++- .../static/less/global-variables.less | 4 +- 6 files changed, 356 insertions(+), 21 deletions(-) create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationItem.vue create mode 100644 contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationList.vue diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js index 563add2c44..4bcf73fff9 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js @@ -14,7 +14,7 @@ switch(window.location.hash.substr(1)) { defaultListType = ListTypes.STARRED; break; case "viewonly": - defaultListType = ListTypes.VIEWONLY; + defaultListType = ListTypes.VIEW_ONLY; break; case "public": defaultListType = ListTypes.PUBLIC; @@ -24,6 +24,13 @@ switch(window.location.hash.substr(1)) { break; } +const ListValues = _.values(ListTypes); +function prepChannel(channel) { + _.each(ListValues, (type) => { // Need to set all attributes so vue will listen know to listen to them + channel[type] = false; + }); +} + var store = new Vuex.Store({ modules: { "channel_list": { @@ -75,9 +82,8 @@ var store = new Vuex.Store({ if(match) { // If it exists, set the existing channel's listType to true match[payload.listType] = true; } else { // Otherwise, add to the list of channels - _.each(listValues, (type) => { // Need to set all attributes so vue will listen know to listen to them - channel[type] = payload.listType === type; - }); + prepChannel(channel); + channel[payload.listType] = true; state.channels.push(channel); } }); @@ -92,6 +98,14 @@ var store = new Vuex.Store({ state.channelSets = _.reject(state.channelSets, (set)=> { return set.id === channelSet.id; }); + }, + REMOVE_INVITATION(state, invitationID) { + state.invitations = _.reject(state.invitations, (invitation)=> { + return invitation.id === invitationID; + }); + }, + ADD_CHANNEL(state, channel) { + state.channels.unshift(channel); } }, actions: { @@ -158,6 +172,32 @@ var store = new Vuex.Store({ context.commit('REMOVE_CHANNELSET', channelSet); } }); + }, + acceptInvitation: function(context, invitation) { + return new Promise((resolve, reject) => { + let invite = new Models.InvitationModel(invitation); + invite.accept_invitation().then((channel) => { + channel = channel.toJSON(); + prepChannel(channel); + + switch(invitation.share_mode) { + case 'edit': + channel[ListTypes.EDITABLE] = true; + break; + case 'view': + channel[ListTypes.VIEW_ONLY] = true; + break; + } + context.commit('ADD_CHANNEL', channel); + resolve(channel); + }).catch((error)=>{ reject(error.responseText); }); + }); + }, + declineInvitation: function(context, invitation) { + return new Promise((resolve, reject) => { + let invite = new Models.InvitationModel(invitation); + invite.destroy({ success: resolve, error: reject }) + }); } } } diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationItem.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationItem.vue new file mode 100644 index 0000000000..a5fdcc0727 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationItem.vue @@ -0,0 +1,190 @@ + + + + + + diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationList.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationList.vue new file mode 100644 index 0000000000..a1b20a7449 --- /dev/null +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelInvitationList.vue @@ -0,0 +1,61 @@ + + + + + + diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue index f2db97f5bb..c16af4ae8d 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue @@ -2,7 +2,7 @@
- +
  • star {{ $tr(listType) }}
  • +
  • + {{ $tr('CHANNEL_SETS') }} +
+
@@ -34,6 +44,8 @@ import _ from 'underscore'; import { mapGetters, mapMutations } from 'vuex'; import { ListTypes } from '../constants'; import ChannelList from './ChannelList.vue'; +import ChannelSetList from './ChannelSetList.vue'; +import ChannelInvitationList from './ChannelInvitationList.vue'; export default { name: 'ChannelListPage', @@ -42,10 +54,12 @@ export default { [ListTypes.VIEW_ONLY]: 'View-Only', [ListTypes.PUBLIC]: 'Public', [ListTypes.STARRED]: 'Starred', - [ListTypes.COLLECTIONS]: 'Collections' + CHANNEL_SETS: 'Collections' }, components: { - ChannelList + ChannelList, + ChannelSetList, + ChannelInvitationList }, computed: Object.assign( mapGetters('channel_list', [ @@ -68,7 +82,7 @@ export default { - diff --git a/contentcuration/contentcuration/static/less/channel_list.less b/contentcuration/contentcuration/static/less/channel_list.less index bb7459edd4..6489a1ff69 100644 --- a/contentcuration/contentcuration/static/less/channel_list.less +++ b/contentcuration/contentcuration/static/less/channel_list.less @@ -72,17 +72,22 @@ body { } // end of flexbox settings +.channel-list-width { + width: @channel-item-width; + max-width: @channel-item-max-width; + min-width: @channel-item-min-width; + margin: 50px auto; +} + .channel-container-wrapper { - background-color: @panel-container-color; - width: @channel-item-width; - max-width: @channel-item-max-width; - min-width: @channel-item-min-width; - box-shadow: @box-shadow; - margin-bottom: 30px; - padding: 10px; - border:4px solid @panel-container-color; - cursor:pointer; - position: relative; + .channel-list-width; + background-color: @panel-container-color; + box-shadow: @box-shadow; + margin-bottom: 30px; + padding: 10px; + border:4px solid @panel-container-color; + cursor:pointer; + position: relative; .profile { width: @channel-profile-width; diff --git a/contentcuration/contentcuration/static/less/global-variables.less b/contentcuration/contentcuration/static/less/global-variables.less index a1bd5f05f7..faa37279c7 100644 --- a/contentcuration/contentcuration/static/less/global-variables.less +++ b/contentcuration/contentcuration/static/less/global-variables.less @@ -58,7 +58,7 @@ @yellow-bg-color: #F5DC14; .action-button { background: @blue-500; - border: 1px solid @blue-500 !important; + border: 1px solid @blue-500; color: white; padding: 5px 16px; border-radius: 2px; @@ -66,7 +66,7 @@ text-decoration: none; &:hover, &:focus { background: white; - color: @blue-500 !important; + color: @blue-500; font-weight:bold; text-decoration: none; } From 21231e0a5a813abc899bd82ac7a587f7d59d1b7c Mon Sep 17 00:00:00 2001 From: Jordan Yoshihara Date: Thu, 14 Feb 2019 15:30:22 -0800 Subject: [PATCH 05/38] Added opening channel set modal --- .../js/edit_channel/channel_list/constants.js | 13 +- .../js/edit_channel/channel_list/store.js | 138 ++++++++---- .../channel_list/views/ChannelItem.vue | 110 +++++---- .../channel_list/views/ChannelList.vue | 1 + .../channel_list/views/ChannelListPage.vue | 83 +++---- .../channel_list/views/ChannelSetItem.vue | 38 ++-- .../channel_list/views/ChannelSetList.vue | 9 +- .../static/less/channel_list.less | 213 ++++++++++-------- 8 files changed, 349 insertions(+), 256 deletions(-) diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js b/contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js index cec33dabc2..a2e7034c29 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/constants.js @@ -4,12 +4,13 @@ export const ListTypes = { EDITABLE: 'EDITABLE', STARRED: 'STARRED', VIEW_ONLY: 'VIEW_ONLY', - PUBLIC: 'PUBLIC' + PUBLIC: 'PUBLIC', + CHANNEL_SETS: 'CHANNEL_SETS', }; -export const ChannelListGetFunctions = { - [ListTypes.EDITABLE]: State.current_user.get_channels, - [ListTypes.STARRED]: State.current_user.get_bookmarked_channels, - [ListTypes.VIEW_ONLY]: State.current_user.get_view_only_channels, - [ListTypes.PUBLIC]: State.current_user.get_public_channels +export const ChannelListUrls = { + [ListTypes.EDITABLE]: window.Urls.get_user_edit_channels(), + [ListTypes.STARRED]: window.Urls.get_user_bookmarked_channels(), + [ListTypes.VIEW_ONLY]: window.Urls.get_user_view_channels(), + [ListTypes.PUBLIC]: window.Urls.get_user_public_channels() } diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js index 4bcf73fff9..3e3d66c88c 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/store.js @@ -3,12 +3,14 @@ import _ from 'underscore'; import State from 'edit_channel/state'; import Models from 'edit_channel/models'; +import { ChannelSetModalView } from 'edit_channel/channel_set/views'; + var Vuex = require('vuex'); -var { ListTypes, ChannelListGetFunctions } = require('./constants'); +var { ListTypes, ChannelListUrls } = require('./constants'); Vue.use(Vuex); -let defaultListType = 'CHANNEL_SETS'; //ListTypes.EDITABLE; +let defaultListType = ListTypes.CHANNEL_SETS; //ListTypes.EDITABLE; switch(window.location.hash.substr(1)) { case "starred": defaultListType = ListTypes.STARRED; @@ -20,7 +22,7 @@ switch(window.location.hash.substr(1)) { defaultListType = ListTypes.PUBLIC; break; case "collection": - defaultListType = 'CHANNEL_SETS'; + defaultListType = ListTypes.CHANNEL_SETS; break; } @@ -72,6 +74,8 @@ var store = new Vuex.Store({ SET_ACTIVE_LIST(state, listType) { state.activeList = listType; }, + + /* Channel mutations */ SET_ACTIVE_CHANNEL(state, channel) { state.activeChannel = channel; }, @@ -88,52 +92,79 @@ var store = new Vuex.Store({ } }); }, + ADD_CHANNEL(state, channel) { + state.channels.unshift(channel); + }, + REMOVE_CHANNEL(state, channel) { + state.channels = _.reject(state.channels, (c) => { + return c.id === channel.id; + }); + }, + + /* Channel set mutations */ SET_CHANNELSET_LIST(state, channelSets) { state.channelSets = channelSets; }, - SET_INVITATION_LIST(state, invitations) { - state.invitations = invitations; + ADD_CHANNELSET(state, channelSet) { + state.channelSets.push(channelSet); }, REMOVE_CHANNELSET(state, channelSet) { state.channelSets = _.reject(state.channelSets, (set)=> { return set.id === channelSet.id; }); }, + + /* Invitation mutations */ + SET_INVITATION_LIST(state, invitations) { + state.invitations = invitations; + }, REMOVE_INVITATION(state, invitationID) { state.invitations = _.reject(state.invitations, (invitation)=> { return invitation.id === invitationID; }); - }, - ADD_CHANNEL(state, channel) { - state.channels.unshift(channel); } }, actions: { loadChannelList: function(context, listType) { return new Promise((resolve, reject) => { - ChannelListGetFunctions[listType]().then(function (channels) { - context.commit('SET_CHANNEL_LIST', { - listType: listType, - channels: channels.toJSON() - }); - resolve(channels); - }); + $.ajax({ + method: "GET", + url: ChannelListUrls[listType], + error: reject, + success: (channels) => { + context.commit('SET_CHANNEL_LIST', { + listType: listType, + channels: channels + }); + resolve(channels); + } + }); }); }, loadChannelSetList: function(context) { return new Promise((resolve, reject) => { - State.current_user.get_user_channel_collections().then(function(sets){ - context.commit('SET_CHANNELSET_LIST', sets.toJSON()); - resolve(sets); - }); + $.ajax({ + method: "GET", + url: window.Urls.get_user_channel_sets(), + error: reject, + success: (channelSets) => { + context.commit('SET_CHANNELSET_LIST', channelSets); + resolve(channelSets); + } + }); }); }, loadChannelInvitationList: function(context) { return new Promise((resolve, reject) => { - State.current_user.get_pending_invites().then(function(invitations){ - context.commit('SET_INVITATION_LIST', invitations.toJSON()); - resolve(invitations); - }); + $.ajax({ + method: "GET", + url: window.Urls.get_user_pending_channels(), + error: reject, + success: (invitations) => { + context.commit('SET_INVITATION_LIST', invitations); + resolve(invitations); + } + }); }); }, addStar: function(context, channel) { @@ -166,7 +197,32 @@ var store = new Vuex.Store({ } }); }, + newChannelSet: function(context) { + /* TODO: REMOVE BACKBONE, move to ChannelSetList.vue */ + let channelSetView = new ChannelSetModalView({ + modal: true, + isNew: true, + model: new Models.ChannelSetModel(), + onsave: (channelset)=> { + context.commit('ADD_CHANNELSET', channelset.toJSON()); + } + }); + }, + openChannelSet: function(context, channelSet) { + /* TODO: REMOVE BACKBONE */ + let channelSetView = new ChannelSetModalView({ + modal: true, + isNew: false, + model: new Models.ChannelSetModel(channelSet), + onsave: (channelset) => { + _.each(channelset.pairs(), (attr) => { + channelSet[attr[0]] = attr[1]; + }) + } + }); + }, deleteChannelSet: function(context, channelSet) { + /* TODO: REMOVE BACKBONE */ new Models.ChannelSetModel(channelSet).destroy({ success: function() { context.commit('REMOVE_CHANNELSET', channelSet); @@ -175,25 +231,29 @@ var store = new Vuex.Store({ }, acceptInvitation: function(context, invitation) { return new Promise((resolve, reject) => { - let invite = new Models.InvitationModel(invitation); - invite.accept_invitation().then((channel) => { - channel = channel.toJSON(); - prepChannel(channel); - - switch(invitation.share_mode) { - case 'edit': - channel[ListTypes.EDITABLE] = true; - break; - case 'view': - channel[ListTypes.VIEW_ONLY] = true; - break; - } - context.commit('ADD_CHANNEL', channel); - resolve(channel); - }).catch((error)=>{ reject(error.responseText); }); + $.ajax({ + method: "POST", + url: window.Urls.accept_channel_invite(), + data: JSON.stringify({ "invitation_id": invitation.id }), + error: reject, + success: (channel) => { + prepChannel(channel); + switch(invitation.share_mode) { + case 'edit': + channel[ListTypes.EDITABLE] = true; + break; + case 'view': + channel[ListTypes.VIEW_ONLY] = true; + break; + } + context.commit('ADD_CHANNEL', channel); + resolve(channel); + } + }); }); }, declineInvitation: function(context, invitation) { + /* TODO: REMOVE BACKBONE */ return new Promise((resolve, reject) => { let invite = new Models.InvitationModel(invitation); invite.destroy({ success: resolve, error: reject }) diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue index c4d04405f9..9cc581323a 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelItem.vue @@ -1,46 +1,44 @@ @@ -49,7 +47,10 @@ import _ from 'underscore'; import { mapActions, mapGetters, mapMutations, mapState } from 'vuex'; import Constants from 'edit_channel/constants/index'; -import CopyToken from './CopyToken.vue' +import CopyToken from './CopyToken.vue'; +import ChannelDetailsView from 'edit_channel/details/views'; + +import Models from 'edit_channel/models'; export default { name: 'ChannelItem', @@ -105,14 +106,35 @@ export default { methods: Object.assign( mapActions('channel_list', [ 'addStar', - 'removeStar', + 'removeStar' ]), mapMutations('channel_list', { setActiveChannel: 'SET_ACTIVE_CHANNEL', + removeChannel: 'REMOVE_CHANNEL' }), { toggleStar() { (this.channel.STARRED)? this.removeStar(this.channel) : this.addStar(this.channel); + }, + openChannel(event) { + if(event && (event.metaKey || event.ctrlKey)) { + if(this.channel.EDITABLE) { + window.open(window.Urls.channel(this.channel.id), "_blank"); + } else { + window.open(window.Urls.channel_view_only(this.channel.id), "_blank"); + } + } else if (!this.activeChannel || this.channel.id !== this.activeChannel.id) { + this.setActiveChannel(this.channel); + let detail_view = new ChannelDetailsView({ + model: new Models.ChannelModel(this.channel), + allow_edit: this.channel.EDITABLE && !this.channel.ricecooker_version, + ondelete: () => { + this.removeChannel(this.channel); + }, + onstar: () => {this.channelSTARRED = true;}, + onunstar: () => {this.channelSTARRED = false;} + }); + } } } ) @@ -130,18 +152,24 @@ export default { @channel-thumbnail-size: 130px; .is-selected { - float: right; padding-top: 9%; + margin-left: -10px; + margin-right: -50px; + z-index: 1; span { font-size: 45pt; font-weight: bold; + visibility: hidden; } } .channel-item { + display: flex; + flex-direction: row; &.active { .is-selected span { color: @topnav-bg-color; + visibility: visible; } .channel-container-wrapper { border-color: @topnav-bg-color; @@ -149,7 +177,7 @@ export default { } &:hover:not(.optionHighlighted) { - .is-selected span, .channel_name { + .is-selected span, h4 { color: @blue-500; } .channel-container-wrapper { diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue index 81c3d9d903..943c96a01f 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelList.vue @@ -84,5 +84,6 @@ export default { diff --git a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue index c16af4ae8d..b2ba220d54 100644 --- a/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue +++ b/contentcuration/contentcuration/static/js/edit_channel/channel_list/views/ChannelListPage.vue @@ -1,36 +1,33 @@