Merge lp:~ahayzen/music-app/remix-add-multi-select-mode into lp:music-app/remix

Proposed by Andrew Hayzen
Status: Merged
Approved by: Victor Thompson
Approved revision: 681
Merged at revision: 689
Proposed branch: lp:~ahayzen/music-app/remix-add-multi-select-mode
Merge into: lp:music-app/remix
Diff against target: 1307 lines (+669/-182)
9 files modified
MusicNowPlaying.qml (+151/-69)
MusicSearch.qml (+25/-0)
MusicTracks.qml (+91/-7)
MusicaddtoPlaylist.qml (+4/-5)
common/ListItemActions/AddToPlaylist.qml (+1/-1)
common/ListItemActions/CheckBox.qml (+25/-0)
common/ListItemWithActions.qml (+261/-86)
common/SongsPage.qml (+104/-10)
music-app.qml (+7/-4)
To merge this branch: bzr merge lp:~ahayzen/music-app/remix-add-multi-select-mode
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Victor Thompson Approve
Review via email: mp+239098@code.launchpad.net

Commit message

* Pull upstream ListItemWithActions.qml
* Add support for multiselect mode
* Disable triggerOnRelease

Description of the change

* Pull upstream ListItemWithActions.qml
* Add support for multiselect mode
* Disable triggerOnRelease

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

A few initial things need fixing:
1. It seems as though the playlists are filtered again while reordering, this causes the view to flicker and the checked items get confused.
2. After reordering selected items and adding them to a playlist from the queue the items aren't quite right. Perhaps we shouldn't allow reordering when items are being selected.

review: Needs Fixing
Revision history for this message
Victor Thompson (vthompson) wrote :

Also, we may want to talk to design about the "select all"/"select none" icon. Perhaps it could/should toggle to an empty square or a "clear" icon when all is selected?

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

1) Fixed due to the change in 2)
2) Disabled reordering when selectmode is enabled

Revision history for this message
Victor Thompson (vthompson) wrote :

While I think preventing reorder when items are selected is the proper way of doing this... we probably should talk to Design about it.

Revision history for this message
Victor Thompson (vthompson) wrote :

Could we please have 2 signals...

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

LGTM! This should help a lot and put us on the path towards improving performance elsewhere! Thanks!

review: Approve
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'MusicNowPlaying.qml'
--- MusicNowPlaying.qml 2014-10-21 16:22:20 +0000
+++ MusicNowPlaying.qml 2014-10-21 21:53:07 +0000
@@ -25,6 +25,7 @@
25import "common"25import "common"
26import "common/ListItemActions"26import "common/ListItemActions"
27import "settings.js" as Settings27import "settings.js" as Settings
28import "playlists.js" as Playlists
2829
29MusicPage {30MusicPage {
30 id: nowPlaying31 id: nowPlaying
@@ -41,43 +42,110 @@
41 }42 }
42 }43 }
4344
44 head.backAction: Action {
45 iconName: "back";
46 objectName: "backButton"
47 onTriggered: {
48 mainPageStack.pop();
49
50 while (mainPageStack.depth > 1) { // jump back to the tab layer if via SongsPage
51 mainPageStack.pop();
52 }
53 }
54 }
55
56 head {
57 actions: [
58 Action {
59 objectName: "clearQueue"
60 iconName: "delete"
61 visible: isListView
62 onTriggered: {
63 head.backAction.trigger()
64 trackQueue.model.clear()
65 }
66 },
67 Action {
68 objectName: "toggleView"
69 iconName: "media-playlist"
70 onTriggered: {
71 isListView = !isListView
72 }
73 }
74 ]
75 }
76
77 function positionAt(index) {45 function positionAt(index) {
78 queuelist.positionViewAtIndex(index, ListView.Center);46 queuelist.positionViewAtIndex(index, ListView.Center);
79 }47 }
8048
49 state: isListView && queuelist.state === "multiselectable" ? "selection" : "default"
50 states: [
51 PageHeadState {
52 id: defaultState
53
54 name: "default"
55 backAction: Action {
56 iconName: "back";
57 objectName: "backButton"
58 onTriggered: {
59 mainPageStack.pop();
60
61 while (mainPageStack.depth > 1) { // jump back to the tab layer if via SongsPage
62 mainPageStack.pop();
63 }
64 }
65 }
66 actions: [
67 Action {
68 objectName: "clearQueue"
69 iconName: "delete"
70 visible: isListView
71 onTriggered: {
72 head.backAction.trigger()
73 trackQueue.model.clear()
74 }
75 },
76 Action {
77 objectName: "toggleView"
78 iconName: "media-playlist"
79 onTriggered: {
80 isListView = !isListView
81 }
82 }
83 ]
84 PropertyChanges {
85 target: nowPlaying.head
86 backAction: defaultState.backAction
87 actions: defaultState.actions
88 }
89 },
90 PageHeadState {
91 id: selectionState
92
93 name: "selection"
94 backAction: Action {
95 text: i18n.tr("Cancel selection")
96 iconName: "back"
97 onTriggered: queuelist.state = "normal"
98 }
99 actions: [
100 Action {
101 text: i18n.tr("Select All")
102 iconName: "select"
103 onTriggered: {
104 if (queuelist.selectedItems.length === queuelist.model.count) {
105 queuelist.clearSelection()
106 } else {
107 queuelist.selectAll()
108 }
109 }
110 },
111 Action {
112 enabled: queuelist.selectedItems.length > 0
113 iconName: "add-to-playlist"
114 text: i18n.tr("Add to playlist")
115 onTriggered: {
116 var items = []
117
118 for (var i=0; i < queuelist.selectedItems.length; i++) {
119 items.push(makeDict(trackQueue.model.get(queuelist.selectedItems[i])));
120 }
121
122 chosenElements = items;
123 mainPageStack.push(addtoPlaylist)
124
125 queuelist.closeSelection()
126 }
127 },
128 Action {
129 enabled: queuelist.selectedItems.length > 0
130 iconName: "delete"
131 text: i18n.tr("Delete")
132 onTriggered: {
133 for (var i=0; i < queuelist.selectedItems.length; i++) {
134 removeQueue(queuelist.selectedItems[i])
135 }
136
137 queuelist.closeSelection()
138 }
139 }
140 ]
141 PropertyChanges {
142 target: nowPlaying.head
143 backAction: selectionState.backAction
144 actions: selectionState.actions
145 }
146 }
147 ]
148
81 Rectangle {149 Rectangle {
82 id: fullview150 id: fullview
83 anchors.fill: parent151 anchors.fill: parent
@@ -376,6 +444,25 @@
376 }444 }
377 }445 }
378446
447 function removeQueue(index)
448 {
449 var removedIndex = index
450
451 if (queuelist.count === 1) {
452 player.stop()
453 musicToolbar.goBack()
454 } else if (index === player.currentIndex) {
455 player.nextSong(player.isPlaying);
456 }
457
458 queuelist.model.remove(index);
459
460 if (removedIndex < player.currentIndex) {
461 // update index as the old has been removed
462 player.currentIndex -= 1;
463 }
464 }
465
379 ListView {466 ListView {
380 id: queuelist467 id: queuelist
381 anchors {468 anchors {
@@ -388,23 +475,6 @@
388 }475 }
389 model: trackQueue.model476 model: trackQueue.model
390 objectName: "nowPlayingQueueList"477 objectName: "nowPlayingQueueList"
391 state: "normal"
392 states: [
393 State {
394 name: "normal"
395 PropertyChanges {
396 target: queuelist
397 interactive: true
398 }
399 },
400 State {
401 name: "reorder"
402 PropertyChanges {
403 target: queuelist
404 interactive: false
405 }
406 }
407 ]
408 visible: isListView478 visible: isListView
409479
410 property int normalHeight: units.gu(6)480 property int normalHeight: units.gu(6)
@@ -414,6 +484,35 @@
414 customdebug("Queue: Now has: " + queuelist.count + " tracks")484 customdebug("Queue: Now has: " + queuelist.count + " tracks")
415 }485 }
416486
487 // Requirements for ListItemWithActions
488 property var selectedItems: []
489
490 signal clearSelection()
491 signal closeSelection()
492 signal selectAll()
493
494 onClearSelection: selectedItems = []
495 onCloseSelection: {
496 clearSelection()
497 state = "normal"
498 }
499 onSelectAll: {
500 var tmp = selectedItems
501
502 for (var i=0; i < model.count; i++) {
503 if (tmp.indexOf(i) === -1) {
504 tmp.push(i)
505 }
506 }
507
508 selectedItems = tmp
509 }
510 onVisibleChanged: {
511 if (!visible) {
512 clearSelection(true)
513 }
514 }
515
417 Component {516 Component {
418 id: queueDelegate517 id: queueDelegate
419 ListItemWithActions {518 ListItemWithActions {
@@ -421,35 +520,18 @@
421 color: player.currentIndex === index ? "#2c2c34" : "transparent"520 color: player.currentIndex === index ? "#2c2c34" : "transparent"
422 height: queuelist.normalHeight521 height: queuelist.normalHeight
423 objectName: "nowPlayingListItem" + index522 objectName: "nowPlayingListItem" + index
424 showDivider: false
425 state: ""523 state: ""
426524
427 leftSideAction: Remove {525 leftSideAction: Remove {
428 onTriggered: {526 onTriggered: removeQueue(index)
429 var removedIndex = index
430
431 if (queuelist.count === 1) {
432 player.stop()
433 musicToolbar.goBack()
434 } else if (index === player.currentIndex) {
435 player.nextSong(player.isPlaying);
436 }
437
438 queuelist.model.remove(index);
439
440 if (removedIndex < player.currentIndex) {
441 // update index as the old has been removed
442 player.currentIndex -= 1;
443 }
444 }
445 }527 }
528 multiselectable: true
446 reorderable: true529 reorderable: true
447 rightSideActions: [530 rightSideActions: [
448 AddToPlaylist{531 AddToPlaylist{
449532
450 }533 }
451 ]534 ]
452 triggerActionOnMouseRelease: true
453535
454 onItemClicked: {536 onItemClicked: {
455 customdebug("File: " + model.filename) // debugger537 customdebug("File: " + model.filename) // debugger
456538
=== modified file 'MusicSearch.qml'
--- MusicSearch.qml 2014-09-20 15:41:33 +0000
+++ MusicSearch.qml 2014-10-21 21:53:07 +0000
@@ -154,6 +154,31 @@
154 searchTrackView.forceActiveFocus()154 searchTrackView.forceActiveFocus()
155 }155 }
156156
157 // Requirements for ListItemWithActions
158 property var selectedItems: []
159
160 signal clearSelection(bool closeSelection)
161 signal selectAll()
162
163 onClearSelection: {
164 selectedItems = []
165
166 if (closeSelection || closeSelection === undefined) {
167 state = "normal"
168 }
169 }
170 onSelectAll: {
171 for (var i=0; i < model.count; i++) {
172 if (selectedItems.indexOf(i) === -1) {
173 selectedItems.push(i)
174 }
175 }
176 }
177 onVisibleChanged: {
178 if (!visible) {
179 clearSelection(true)
180 }
181 }
157 delegate: ListItemWithActions {182 delegate: ListItemWithActions {
158 id: search183 id: search
159 color: "transparent"184 color: "transparent"
160185
=== modified file 'MusicTracks.qml'
--- MusicTracks.qml 2014-10-10 05:50:32 +0000
+++ MusicTracks.qml 2014-10-21 21:53:07 +0000
@@ -34,6 +34,66 @@
34 objectName: "tracksPage"34 objectName: "tracksPage"
35 title: i18n.tr("Songs")35 title: i18n.tr("Songs")
3636
37 state: tracklist.state === "multiselectable" ? "selection" : "default"
38 states: [
39 PageHeadState {
40 id: selectionState
41 name: "selection"
42 backAction: Action {
43 text: i18n.tr("Cancel selection")
44 iconName: "back"
45 onTriggered: tracklist.state = "normal"
46 }
47 actions: [
48 Action {
49 iconName: "select"
50 text: i18n.tr("Select All")
51 onTriggered: {
52 if (tracklist.selectedItems.length === tracklist.model.count) {
53 tracklist.clearSelection()
54 } else {
55 tracklist.selectAll()
56 }
57 }
58 },
59 Action {
60 enabled: tracklist.selectedItems.length !== 0
61 iconName: "add-to-playlist"
62 text: i18n.tr("Add to playlist")
63 onTriggered: {
64 var items = []
65
66 for (var i=0; i < tracklist.selectedItems.length; i++) {
67 items.push(makeDict(tracklist.model.get(tracklist.selectedItems[i], tracklist.model.RoleModelData)));
68 }
69
70 chosenElements = items;
71 mainPageStack.push(addtoPlaylist)
72
73 tracklist.closeSelection()
74 }
75 },
76 Action {
77 enabled: tracklist.selectedItems.length > 0
78 iconName: "add"
79 text: i18n.tr("Add to queue")
80 onTriggered: {
81 for (var i=0; i < tracklist.selectedItems.length; i++) {
82 trackQueue.model.append(makeDict(tracklist.model.get(tracklist.selectedItems[i], tracklist.model.RoleModelData)));
83 }
84
85 tracklist.closeSelection()
86 }
87 }
88 ]
89 PropertyChanges {
90 target: mainpage.head
91 backAction: selectionState.backAction
92 actions: selectionState.actions
93 }
94 }
95 ]
96
37 ListView {97 ListView {
38 id: tracklist98 id: tracklist
39 anchors {99 anchors {
@@ -52,18 +112,46 @@
52 sort.property: "title"112 sort.property: "title"
53 sort.order: Qt.AscendingOrder113 sort.order: Qt.AscendingOrder
54 }114 }
115
116 // Requirements for ListItemWithActions
117 property var selectedItems: []
118
119 signal clearSelection()
120 signal closeSelection()
121 signal selectAll()
122
123 onClearSelection: selectedItems = []
124 onCloseSelection: {
125 clearSelection()
126 state = "normal"
127 }
128 onSelectAll: {
129 var tmp = selectedItems
130
131 for (var i=0; i < model.count; i++) {
132 if (tmp.indexOf(i) === -1) {
133 tmp.push(i)
134 }
135 }
136
137 selectedItems = tmp
138 }
139 onVisibleChanged: {
140 if (!visible) {
141 clearSelection(true)
142 }
143 }
144
55 delegate: trackDelegate145 delegate: trackDelegate
56 Component {146 Component {
57 id: trackDelegate147 id: trackDelegate
58148
59 ListItemWithActions {149 ListItemWithActions {
60 id: track150 id: track
61 color: "transparent"
62 objectName: "tracksPageListItem" + index151 objectName: "tracksPageListItem" + index
63 width: parent.width
64 height: units.gu(7)152 height: units.gu(7)
65 showDivider: false
66153
154 multiselectable: true
67 rightSideActions: [155 rightSideActions: [
68 AddToQueue {156 AddToQueue {
69 },157 },
@@ -71,10 +159,6 @@
71159
72 }160 }
73 ]161 ]
74 triggerActionOnMouseRelease: true
75
76 // TODO: If http://pad.lv/1354753 is fixed to expose whether the Shape should appear pressed, update this as well.
77 onPressedChanged: musicRow.pressed = pressed
78162
79 onItemClicked: trackClicked(tracklist.model, index) // play track163 onItemClicked: trackClicked(tracklist.model, index) // play track
80164
81165
=== modified file 'MusicaddtoPlaylist.qml'
--- MusicaddtoPlaylist.qml 2014-09-30 15:18:25 +0000
+++ MusicaddtoPlaylist.qml 2014-10-21 21:53:07 +0000
@@ -81,18 +81,17 @@
81 property string count: model.count81 property string count: model.count
8282
83 onClicked: {83 onClicked: {
84 console.debug("Debug: "+chosenElement.filename+" added to "+name)84 for (var i=0; i < chosenElements.length; i++) {
85 console.debug("Debug: "+chosenElements[i].filename+" added to "+name)
8586
86 Playlists.addToPlaylist(name, chosenElement)87 Playlists.addToPlaylist(name, chosenElements[i])
88 }
8789
88 playlistModel.filterPlaylists();90 playlistModel.filterPlaylists();
8991
90 musicToolbar.goBack(); // go back to the previous page92 musicToolbar.goBack(); // go back to the previous page
91 }93 }
9294
93 // TODO: If http://pad.lv/1354753 is fixed to expose whether the Shape should appear pressed, update this as well.
94 onPressedChanged: musicRow.pressed = pressed
95
96 MusicRow {95 MusicRow {
97 id: musicRow96 id: musicRow
98 covers: Playlists.getPlaylistCovers(playlist.name)97 covers: Playlists.getPlaylistCovers(playlist.name)
9998
=== modified file 'common/ListItemActions/AddToPlaylist.qml'
--- common/ListItemActions/AddToPlaylist.qml 2014-09-20 10:50:45 +0000
+++ common/ListItemActions/AddToPlaylist.qml 2014-10-21 21:53:07 +0000
@@ -27,7 +27,7 @@
27 property bool primed: false27 property bool primed: false
2828
29 onTriggered: {29 onTriggered: {
30 chosenElement = makeDict(model);30 chosenElements = [makeDict(model)];
31 console.debug("Debug: Add track to playlist");31 console.debug("Debug: Add track to playlist");
32 mainPageStack.push(addtoPlaylist)32 mainPageStack.push(addtoPlaylist)
33 }33 }
3434
=== added file 'common/ListItemActions/CheckBox.qml'
--- common/ListItemActions/CheckBox.qml 1970-01-01 00:00:00 +0000
+++ common/ListItemActions/CheckBox.qml 2014-10-21 21:53:07 +0000
@@ -0,0 +1,25 @@
1/*
2 * Copyright (C) 2012-2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.2
18import Ubuntu.Components 1.1
19
20CheckBox {
21 checked: root.selected
22 width: implicitWidth
23 // disable item mouse area to avoid conflicts with parent mouse area
24 __mouseArea.enabled: false
25}
026
=== modified file 'common/ListItemWithActions.qml'
--- common/ListItemWithActions.qml 2014-09-20 10:50:45 +0000
+++ common/ListItemWithActions.qml 2014-10-21 21:53:07 +0000
@@ -19,116 +19,170 @@
19import Ubuntu.Components.ListItems 1.0 as ListItem19import Ubuntu.Components.ListItems 1.0 as ListItem
2020
2121
22ListItem.Standard { // CUSTOM22Item {
23//Item {
24 id: root23 id: root
24 width: parent.width
2525
26 property Action leftSideAction: null26 property Action leftSideAction: null
27 property list<Action> rightSideActions27 property list<Action> rightSideActions
28 property double defaultHeight: units.gu(8)28 property double defaultHeight: units.gu(8)
29 property bool locked: false29 property bool locked: false
30 property bool pressed: false
31 property Action activeAction: null30 property Action activeAction: null
32 property var activeItem: null31 property var activeItem: null
33 property bool triggerActionOnMouseRelease: false32 property bool triggerActionOnMouseRelease: false
34 property alias color: main.color33 property color color: Theme.palette.normal.background
35 default property alias contents: main.children34 property color selectedColor: "#3d3d45" // "#E6E6E6" // CUSTOM
3635 property bool selected: false
37 property bool reorderable: false // CUSTOM36 property bool selectionMode: false
38 property bool reordering: false // CUSTOM37 property alias internalAnchors: mainContents.anchors
3938 default property alias contents: mainContents.children
40 readonly property double actionWidth: units.gu(5)39
40 readonly property double actionWidth: units.gu(4) // CUSTOM 5?
41 readonly property double leftActionWidth: units.gu(10)41 readonly property double leftActionWidth: units.gu(10)
42 readonly property double actionThreshold: actionWidth * 0.442 readonly property double actionThreshold: actionWidth * 0.4
43 readonly property double threshold: 0.443 readonly property double threshold: 0.4
44 readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft"44 readonly property string swipeState: main.x == 0 ? "Normal" : main.x > 0 ? "LeftToRight" : "RightToLeft"
45 readonly property alias swipping: mainItemMoving.running45 readonly property alias swipping: mainItemMoving.running
46 readonly property bool _showActions: mouseArea.pressed || swipeState != "Normal" || swipping
47
48 property bool reorderable: false // CUSTOM
49 property bool reordering: false // CUSTOM
50 property bool multiselectable: false // CUSTOM
51
52 property int previousListItemIndex: -1 // CUSTOM
53 property int listItemIndex: index // CUSTOM
54
55 /* internal */
56 property var _visibleRightSideActions: filterVisibleActions(rightSideActions)
4657
47 signal itemClicked(var mouse)58 signal itemClicked(var mouse)
48 signal itemPressAndHold(var mouse)59 signal itemPressAndHold(var mouse)
4960
50 signal reorder(int from, int to) // CUSTOM61 signal reorder(int from, int to) // CUSTOM
5162
52 onItemPressAndHold: reordering = reorderable && !reordering // CUSTOM63 onItemPressAndHold: {
53 onReorderingChanged: { // CUSTOM64 //reordering = reorderable && !reordering // CUSTOM
54 if (reordering) {65 if (multiselectable) {
66 selectionMode = true
67 }
68 }
69 onListItemIndexChanged: {
70 var i = parent.parent.selectedItems.lastIndexOf(previousListItemIndex)
71
72 if (i !== -1) {
73 parent.parent.selectedItems[i] = listItemIndex
74 }
75
76 previousListItemIndex = listItemIndex
77 }
78
79 onSelectedChanged: {
80 if (selectionMode) {
81 var tmp = parent.parent.selectedItems
82
83 if (selected) {
84 if (parent.parent.selectedItems.indexOf(listItemIndex) === -1) {
85 tmp.push(listItemIndex)
86 parent.parent.selectedItems = tmp
87 }
88 } else {
89 tmp.splice(parent.parent.selectedItems.indexOf(listItemIndex), 1)
90 parent.parent.selectedItems = tmp
91 }
92 }
93 }
94
95 onSelectionModeChanged: { // CUSTOM
96 if (reorderable && selectionMode) {
55 resetSwipe()97 resetSwipe()
56 }98 }
5799
58 for (var j=0; j < main.children.length; j++) {100 for (var j=0; j < main.children.length; j++) {
59 main.children[j].anchors.rightMargin = reordering ? actionReorder.width + units.gu(2) : 0101 main.children[j].anchors.rightMargin = reorderable && selectionMode ? actionReorder.width + units.gu(2) : 0
60 }102 }
61103
62 parent.state = reordering ? "reorder" : "normal"104 parent.parent.state = selectionMode ? "multiselectable" : "normal"
105
106 if (!selectionMode) {
107 selected = false
108 }
63 }109 }
64110
65 function returnToBoundsRTL()111 function returnToBoundsRTL(direction)
66 {112 {
67 var actionFullWidth = actionWidth + units.gu(2)113 var actionFullWidth = actionWidth + units.gu(2)
114
115 // go back to normal state if swipping reverse
116 if (direction === "LTR") {
117 updatePosition(0)
118 return
119 } else if (!triggerActionOnMouseRelease) {
120 updatePosition(-rightActionsView.width + units.gu(2))
121 return
122 }
123
68 var xOffset = Math.abs(main.x)124 var xOffset = Math.abs(main.x)
69 var index = Math.min(Math.floor(xOffset / actionFullWidth), rightSideActions.length)125 var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
70 var j; // CUSTOM126 var newX = 0
71127 var j // CUSTOM
72 if (index < 1) {128
73 main.x = 0129 if (index === _visibleRightSideActions.length) {
74130 newX = -(rightActionsView.width - units.gu(2))
75 resetPrimed() // CUSTOM
76 } else if (index === rightSideActions.length) {
77 main.x = -rightActionsView.width
78131
79 for (j=0; j < rightSideActions.length; j++) { // CUSTOM132 for (j=0; j < rightSideActions.length; j++) { // CUSTOM
80 rightActionsRepeater.itemAt(j).primed = true133 rightActionsRepeater.itemAt(j).primed = true
81 }134 }
82 } else {135 } else if (index >= 1) {
83 main.x = -(actionFullWidth * index)136 newX = -(actionFullWidth * index)
84137
85 for (j=0; j < rightSideActions.length; j++) { // CUSTOM138 for (j=0; j < rightSideActions.length; j++) { // CUSTOM
86 rightActionsRepeater.itemAt(j).primed = j === index139 rightActionsRepeater.itemAt(j).primed = j === index
87 }140 }
88 }141 }
142
143 updatePosition(newX)
89 }144 }
90145
91 function returnToBoundsLTR()146 function returnToBoundsLTR(direction)
92 {147 {
93 var finalX = leftActionWidth148 var finalX = leftActionWidth
94 if (main.x > (finalX * root.threshold))149 if ((direction === "RTL") || (main.x <= (finalX * root.threshold)))
95 main.x = finalX150 finalX = 0
96 else {151 updatePosition(finalX)
97 main.x = 0
98
99 resetPrimed() // CUSTOM
100 }
101152
102 if (leftSideAction !== null) { // CUSTOM153 if (leftSideAction !== null) { // CUSTOM
103 leftActionIcon.primed = main.x > (finalX * root.threshold)154 leftActionIcon.primed = main.x > (finalX * root.threshold)
104 }155 }
105 }156 }
106157
107 function returnToBounds()158 function returnToBounds(direction)
108 {159 {
109 if (main.x < 0) {160 if (main.x < 0) {
110 returnToBoundsRTL()161 returnToBoundsRTL(direction)
111 } else if (main.x > 0) {162 } else if (main.x > 0) {
112 returnToBoundsLTR()163 returnToBoundsLTR(direction)
113 } else { // CUSTOM164 } else {
114 resetPrimed() // CUSTOM165 updatePosition(0)
115 }166 }
116 }167 }
117168
118 function contains(item, point)169 function contains(item, point, marginX)
119 {170 {
120 return (point.x >= item.x) && (point.x <= (item.x + item.width)) && (point.y >= item.y) && (point.y <= (item.y + item.height));171 var itemStartX = item.x - marginX
172 var itemEndX = item.x + item.width + marginX
173 return (point.x >= itemStartX) && (point.x <= itemEndX) &&
174 (point.y >= item.y) && (point.y <= (item.y + item.height));
121 }175 }
122176
123 function getActionAt(point)177 function getActionAt(point)
124 {178 {
125 if (contains(leftActionView, point)) {179 if (contains(leftActionView, point, 0)) {
126 return leftSideAction180 return leftSideAction
127 } else if (contains(rightActionsView, point)) {181 } else if (contains(rightActionsView, point, 0)) {
128 var newPoint = root.mapToItem(rightActionsView, point.x, point.y)182 var newPoint = root.mapToItem(rightActionsView, point.x, point.y)
129 for (var i = 0; i < rightActionsRepeater.count; i++) {183 for (var i = 0; i < rightActionsRepeater.count; i++) {
130 var child = rightActionsRepeater.itemAt(i)184 var child = rightActionsRepeater.itemAt(i)
131 if (contains(child, newPoint)) {185 if (contains(child, newPoint, units.gu(1))) {
132 return i186 return i
133 }187 }
134 }188 }
@@ -138,15 +192,16 @@
138192
139 function updateActiveAction()193 function updateActiveAction()
140 {194 {
141 if ((main.x <= -root.actionWidth) &&195 if (triggerActionOnMouseRelease &&
142 (main.x > -rightActionsView.width)) {196 (main.x <= -(root.actionWidth + units.gu(2))) &&
197 (main.x > -(rightActionsView.width - units.gu(2)))) {
143 var actionFullWidth = actionWidth + units.gu(2)198 var actionFullWidth = actionWidth + units.gu(2)
144 var xOffset = Math.abs(main.x)199 var xOffset = Math.abs(main.x)
145 var index = Math.min(Math.floor(xOffset / actionFullWidth), rightSideActions.length)200 var index = Math.min(Math.floor(xOffset / actionFullWidth), _visibleRightSideActions.length)
146 index = index - 1201 index = index - 1
147 if (index > -1) {202 if (index > -1) {
148 root.activeItem = rightActionsRepeater.itemAt(index)203 root.activeItem = rightActionsRepeater.itemAt(index)
149 root.activeAction = root.rightSideActions[index]204 root.activeAction = root._visibleRightSideActions[index]
150 }205 }
151 } else {206 } else {
152 root.activeAction = null207 root.activeAction = null
@@ -160,15 +215,40 @@
160 }215 }
161216
162 for (var j=0; j < rightSideActions.length; j++) {217 for (var j=0; j < rightSideActions.length; j++) {
218 console.debug(rightActionsRepeater.itemAt(j));
163 rightActionsRepeater.itemAt(j).primed = false219 rightActionsRepeater.itemAt(j).primed = false
164 }220 }
165 }221 }
166222
167 function resetSwipe()223 function resetSwipe()
168 {224 {
169 main.x = 0225 updatePosition(0)
170226 }
171 resetPrimed() // CUSTOM227
228 function filterVisibleActions(actions)
229 {
230 var visibleActions = []
231 for(var i = 0; i < actions.length; i++) {
232 var action = actions[i]
233 if (action.visible) {
234 visibleActions.push(action)
235 }
236 }
237 return visibleActions
238 }
239
240 function updatePosition(pos)
241 {
242 if (!root.triggerActionOnMouseRelease && (pos !== 0)) {
243 mouseArea.state = pos > 0 ? "RightToLeft" : "LeftToRight"
244 } else {
245 mouseArea.state = ""
246 }
247 main.x = pos
248
249 if (pos === 0) { // CUSTOM
250 //resetPrimed()
251 }
172 }252 }
173253
174 Connections { // CUSTOM254 Connections { // CUSTOM
@@ -181,8 +261,10 @@
181 }261 }
182262
183 Connections { // CUSTOM263 Connections { // CUSTOM
184 target: root.parent264 target: root.parent.parent
185 onStateChanged: reordering = root.parent.state === "reorder"265 onClearSelection: selected = false
266 onSelectAll: selected = true
267 onStateChanged: selectionMode = root.parent.parent.state === "multiselectable"
186 onVisibleChanged: {268 onVisibleChanged: {
187 if (!visible) {269 if (!visible) {
188 reordering = false270 reordering = false
@@ -190,7 +272,13 @@
190 }272 }
191 }273 }
192274
193 Component.onCompleted: reordering = root.parent.state === "reorder" // CUSTOM275 Component.onCompleted: { // CUSTOM
276 if (parent.parent.selectedItems.indexOf(index) !== -1) { // FIXME:
277 selected = true
278 }
279
280 selectionMode = root.parent.parent.state === "multiselectable"
281 }
194282
195 /* CUSTOM Dim Component */283 /* CUSTOM Dim Component */
196 Rectangle {284 Rectangle {
@@ -228,6 +316,26 @@
228 }316 }
229 }317 }
230318
319 states: [
320 State {
321 name: "select"
322 when: selectionMode || selected
323 PropertyChanges {
324 target: selectionIcon
325 source: Qt.resolvedUrl("ListItemActions/CheckBox.qml")
326 anchors.leftMargin: units.gu(2)
327 }
328 PropertyChanges {
329 target: root
330 locked: true
331 }
332 PropertyChanges {
333 target: main
334 x: 0
335 }
336 }
337 ]
338
231 height: defaultHeight339 height: defaultHeight
232 clip: height !== defaultHeight340 clip: height !== defaultHeight
233341
@@ -241,16 +349,15 @@
241 }349 }
242 width: root.leftActionWidth + actionThreshold350 width: root.leftActionWidth + actionThreshold
243 visible: leftSideAction351 visible: leftSideAction
244 color: "red"352 color: UbuntuColors.red
245353
246 Icon {354 Icon {
247 id: leftActionIcon
248 anchors {355 anchors {
249 centerIn: parent356 centerIn: parent
250 horizontalCenterOffset: actionThreshold / 2357 horizontalCenterOffset: actionThreshold / 2
251 }358 }
252 objectName: "swipeDeleteAction" // CUSTOM359 objectName: "swipeDeleteAction" // CUSTOM
253 name: leftSideAction ? leftSideAction.iconName : ""360 name: leftSideAction && _showActions ? leftSideAction.iconName : ""
254 color: Theme.palette.selected.field361 color: Theme.palette.selected.field
255 height: units.gu(3)362 height: units.gu(3)
256 width: units.gu(3)363 width: units.gu(3)
@@ -259,7 +366,8 @@
259 }366 }
260 }367 }
261368
262 Item {369 //Rectangle {
370 Item { // CUSTOM
263 id: rightActionsView371 id: rightActionsView
264372
265 anchors {373 anchors {
@@ -268,8 +376,9 @@
268 leftMargin: reordering ? actionReorder.width : units.gu(1) // CUSTOM376 leftMargin: reordering ? actionReorder.width : units.gu(1) // CUSTOM
269 bottom: main.bottom377 bottom: main.bottom
270 }378 }
271 visible: rightSideActions.length > 0379 visible: _visibleRightSideActions.length > 0
272 width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + actionThreshold : 0380 width: rightActionsRepeater.count > 0 ? rightActionsRepeater.count * (root.actionWidth + units.gu(2)) + root.actionThreshold + units.gu(2) : 0
381 // color: "white" // CUSTOM
273382
274 Rectangle { // CUSTOM383 Rectangle { // CUSTOM
275 anchors {384 anchors {
@@ -283,22 +392,23 @@
283 }392 }
284393
285 Row {394 Row {
286 anchors {395 anchors{
287 fill: parent396 top: parent.top
288 leftMargin: units.gu(2) // CUSTOM397 left: parent.left
398 leftMargin: units.gu(2)
399 right: parent.right
400 rightMargin: units.gu(2)
401 bottom: parent.bottom
289 }402 }
290 spacing: units.gu(2)403 spacing: units.gu(2)
291 Repeater {404 Repeater {
292 id: rightActionsRepeater405 id: rightActionsRepeater
293406
294 model: rightSideActions407 model: _showActions ? _visibleRightSideActions : []
295 Item {408 Item {
296 property alias image: img409 property alias image: img
297410
298 anchors {411 height: rightActionsView.height
299 top: parent.top
300 bottom: parent.bottom
301 }
302 width: root.actionWidth412 width: root.actionWidth
303413
304 property alias primed: img.primed // CUSTOM414 property alias primed: img.primed // CUSTOM
@@ -310,8 +420,8 @@
310 objectName: rightSideActions[index].objectName // CUSTOM420 objectName: rightSideActions[index].objectName // CUSTOM
311 width: units.gu(3)421 width: units.gu(3)
312 height: units.gu(3)422 height: units.gu(3)
313 name: iconName423 name: modelData.iconName
314 color: root.activeAction === modelData || !root.triggerActionOnMouseRelease ? UbuntuColors.orange : styleMusic.common.white // CUSTOM424 color: root.activeAction === modelData ? UbuntuColors.orange : styleMusic.common.white // CUSTOM
315425
316 property bool primed: false // CUSTOM426 property bool primed: false // CUSTOM
317 }427 }
@@ -330,6 +440,38 @@
330 }440 }
331441
332 width: parent.width442 width: parent.width
443 color: root.selected ? root.selectedColor : root.color
444
445 Loader {
446 id: selectionIcon
447
448 anchors {
449 left: main.left
450 verticalCenter: main.verticalCenter
451 }
452 width: (status === Loader.Ready) ? item.implicitWidth : 0
453 visible: (status === Loader.Ready) && (item.width === item.implicitWidth)
454 Behavior on width {
455 NumberAnimation {
456 duration: UbuntuAnimation.SnapDuration
457 }
458 }
459 }
460
461 Item {
462 id: mainContents
463
464 anchors {
465 left: selectionIcon.right
466 //leftMargin: units.gu(2) // CUSTOM
467 top: parent.top
468 //topMargin: units.gu(1) // CUSTOM
469 right: parent.right
470 //rightMargin: units.gu(2) // CUSTOM
471 bottom: parent.bottom
472 //bottomMargin: units.gu(1) // CUSTOM
473 }
474 }
333475
334 Behavior on x {476 Behavior on x {
335 UbuntuNumberAnimation {477 UbuntuNumberAnimation {
@@ -342,7 +484,7 @@
342 }484 }
343485
344 /* CUSTOM Reorder Component */486 /* CUSTOM Reorder Component */
345 Rectangle {487 Item {
346 id: actionReorder488 id: actionReorder
347 anchors {489 anchors {
348 bottom: parent.bottom490 bottom: parent.bottom
@@ -350,9 +492,8 @@
350 rightMargin: units.gu(1)492 rightMargin: units.gu(1)
351 top: parent.top493 top: parent.top
352 }494 }
353 color: "transparent"
354 width: units.gu(4)495 width: units.gu(4)
355 visible: reordering496 visible: reorderable && selectionMode && root.parent.parent.selectedItems.length === 0
356497
357 Icon {498 Icon {
358 anchors {499 anchors {
@@ -469,7 +610,10 @@
469 value: 1.0610 value: 1.0
470 }611 }
471 ScriptAction {612 ScriptAction {
472 script: root.activeAction.triggered(root)613 script: {
614 root.activeAction.triggered(root)
615 mouseArea.state = ""
616 }
473 }617 }
474 PauseAnimation {618 PauseAnimation {
475 duration: 500619 duration: 500
@@ -479,23 +623,53 @@
479 property: "x"623 property: "x"
480 to: 0624 to: 0
481 }625 }
482 ScriptAction {
483 script: resetPrimed()
484 }
485 }626 }
486627
487 MouseArea {628 MouseArea {
488 id: mouseArea629 id: mouseArea
489630
490 property bool locked: root.locked || ((root.leftSideAction === null) && (root.rightSideActions.count === 0)) || reordering // CUSTOM631 property bool locked: root.locked || ((root.leftSideAction === null) && (root._visibleRightSideActions.count === 0)) || reordering // CUSTOM
491 property bool manual: false632 property bool manual: false
633 property string direction: "None"
634 property real lastX: -1
492635
493 anchors.fill: parent636 anchors.fill: parent
494 drag {637 drag {
495 target: locked ? null : main638 target: locked ? null : main
496 axis: Drag.XAxis639 axis: Drag.XAxis
497 minimumX: rightActionsView.visible ? -(rightActionsView.width + root.actionThreshold) : 0640 minimumX: rightActionsView.visible ? -(rightActionsView.width) : 0
498 maximumX: leftActionView.visible ? leftActionView.width : 0641 maximumX: leftActionView.visible ? leftActionView.width : 0
642 threshold: root.actionThreshold
643 }
644
645 states: [
646 State {
647 name: "LeftToRight"
648 PropertyChanges {
649 target: mouseArea
650 drag.maximumX: 0
651 }
652 },
653 State {
654 name: "RightToLeft"
655 PropertyChanges {
656 target: mouseArea
657 drag.minimumX: 0
658 }
659 }
660 ]
661
662 onMouseXChanged: {
663 var offset = (lastX - mouseX)
664 if (Math.abs(offset) <= root.actionThreshold) {
665 return
666 }
667 lastX = mouseX
668 direction = offset > 0 ? "RTL" : "LTR";
669 }
670
671 onPressed: {
672 lastX = mouse.x
499 }673 }
500674
501 onReleased: {675 onReleased: {
@@ -505,10 +679,13 @@
505 root.returnToBounds()679 root.returnToBounds()
506 root.activeAction = null680 root.activeAction = null
507 }681 }
682 lastX = -1
683 direction = "None"
508 }684 }
509 onClicked: {685 onClicked: {
510 if (reordering) { // CUSTOM686 if (selectionMode) { // CUSTOM
511 reordering = false687 selected = !selected
688 return
512 }689 }
513 else if (main.x === 0) {690 else if (main.x === 0) {
514 root.itemClicked(mouse)691 root.itemClicked(mouse)
@@ -544,8 +721,6 @@
544 }721 }
545 }722 }
546723
547 onPressedChanged: root.pressed = pressed
548
549 z: -1724 z: -1
550 }725 }
551}726}
552727
=== modified file 'common/SongsPage.qml'
--- common/SongsPage.qml 2014-10-21 17:30:51 +0000
+++ common/SongsPage.qml 2014-10-21 21:53:07 +0000
@@ -44,7 +44,7 @@
44 property alias album: songsModel.album44 property alias album: songsModel.album
45 property alias genre: songsModel.genre45 property alias genre: songsModel.genre
4646
47 state: songStackPage.line1 === i18n.tr("Playlist") ? "playlist" : "album"47 state: albumtrackslist.state === "multiselectable" ? "selection" : (songStackPage.line1 === i18n.tr("Playlist") ? "playlist" : "album")
48 states: [48 states: [
49 PageHeadState {49 PageHeadState {
50 id: albumState50 id: albumState
@@ -57,7 +57,6 @@
57 },57 },
58 PageHeadState {58 PageHeadState {
59 id: playlistState59 id: playlistState
60
61 name: "playlist"60 name: "playlist"
62 actions: [61 actions: [
63 Action {62 Action {
@@ -82,6 +81,78 @@
82 backAction: playlistState.backAction81 backAction: playlistState.backAction
83 actions: playlistState.actions82 actions: playlistState.actions
84 }83 }
84 },
85 PageHeadState {
86 id: selectionState
87 name: "selection"
88 backAction: Action {
89 text: i18n.tr("Cancel selection")
90 iconName: "back"
91 onTriggered: albumtrackslist.state = "normal"
92 }
93 actions: [
94 Action {
95 iconName: "select"
96 text: i18n.tr("Select All")
97 onTriggered: {
98 if (albumtrackslist.selectedItems.length === albumtrackslist.model.count) {
99 albumtrackslist.clearSelection()
100 } else {
101 albumtrackslist.selectAll()
102 }
103 }
104 },
105 Action {
106 enabled: albumtrackslist.selectedItems.length > 0
107 iconName: "add-to-playlist"
108 text: i18n.tr("Add to playlist")
109 onTriggered: {
110 var items = []
111
112 for (var i=0; i < albumtrackslist.selectedItems.length; i++) {
113 items.push(makeDict(albumtrackslist.model.get(albumtrackslist.selectedItems[i], albumtrackslist.model.RoleModelData)));
114 }
115
116 chosenElements = items;
117 mainPageStack.push(addtoPlaylist)
118
119 albumtrackslist.closeSelection()
120 }
121 },
122 Action {
123 enabled: albumtrackslist.selectedItems.length > 0
124 iconName: "add"
125 text: i18n.tr("Add to queue")
126 onTriggered: {
127 for (var i=0; i < albumtrackslist.selectedItems.length; i++) {
128 trackQueue.model.append(makeDict(albumtrackslist.model.get(albumtrackslist.selectedItems[i], albumtrackslist.model.RoleModelData)));
129 }
130
131 albumtrackslist.closeSelection()
132 }
133 },
134 Action {
135 enabled: albumtrackslist.selectedItems.length > 0
136 iconName: "delete"
137 text: i18n.tr("Delete")
138 visible: songStackPage.line1 === i18n.tr("Playlist")
139 onTriggered: {
140 for (var i=0; i < albumtrackslist.selectedItems.length; i++) {
141 Playlists.removeFromPlaylist(songStackPage.line2, albumtrackslist.selectedItems[i] - i)
142 }
143
144 albumtrackslist.closeSelection()
145
146 albumTracksModel.filterPlaylistTracks(songStackPage.line2)
147 playlistModel.filterPlaylists()
148 }
149 }
150 ]
151 PropertyChanges {
152 target: songStackPage.head
153 backAction: selectionState.backAction
154 actions: selectionState.actions
155 }
85 }156 }
86 ]157 ]
87158
@@ -99,6 +170,36 @@
99 model: isAlbum ? songsModel : albumTracksModel.model170 model: isAlbum ? songsModel : albumTracksModel.model
100 objectName: "songspage-listview"171 objectName: "songspage-listview"
101 width: parent.width172 width: parent.width
173
174 // Requirements for ListItemWithActions
175 property var selectedItems: []
176
177 signal clearSelection()
178 signal closeSelection()
179 signal selectAll()
180
181 onClearSelection: selectedItems = []
182 onCloseSelection: {
183 clearSelection()
184 state = "normal"
185 }
186 onSelectAll: {
187 var tmp = selectedItems
188
189 for (var i=0; i < model.count; i++) {
190 if (tmp.indexOf(i) === -1) {
191 tmp.push(i)
192 }
193 }
194
195 selectedItems = tmp
196 }
197 onVisibleChanged: {
198 if (!visible) {
199 clearSelection(true)
200 }
201 }
202
102 header: BlurredHeader {203 header: BlurredHeader {
103 rightColumn: Column {204 rightColumn: Column {
104 spacing: units.gu(2)205 spacing: units.gu(2)
@@ -238,15 +339,12 @@
238339
239 ListItemWithActions {340 ListItemWithActions {
240 id: track341 id: track
241 color: "transparent"
242 objectName: "songsPageListItem" + index342 objectName: "songsPageListItem" + index
243 iconFrame: false
244 progression: false
245 showDivider: false
246 height: units.gu(6)343 height: units.gu(6)
247344
248 leftSideAction: songStackPage.line1 === i18n.tr("Playlist")345 leftSideAction: songStackPage.line1 === i18n.tr("Playlist")
249 ? playlistRemoveAction.item : null346 ? playlistRemoveAction.item : null
347 multiselectable: true
250 reorderable: songStackPage.line1 === i18n.tr("Playlist")348 reorderable: songStackPage.line1 === i18n.tr("Playlist")
251 rightSideActions: [349 rightSideActions: [
252 AddToQueue {350 AddToQueue {
@@ -256,7 +354,6 @@
256354
257 }355 }
258 ]356 ]
259 triggerActionOnMouseRelease: true
260357
261 onItemClicked: {358 onItemClicked: {
262 trackClicked(albumtrackslist.model, index) // play track359 trackClicked(albumtrackslist.model, index) // play track
@@ -289,9 +386,6 @@
289 }386 }
290 }387 }
291388
292 // TODO: If http://pad.lv/1354753 is fixed to expose whether the Shape should appear pressed, update this as well.
293 onPressedChanged: musicRow.pressed = pressed
294
295 MusicRow {389 MusicRow {
296 id: musicRow390 id: musicRow
297 covers: []391 covers: []
298392
=== modified file 'music-app.qml'
--- music-app.qml 2014-10-21 14:59:51 +0000
+++ music-app.qml 2014-10-21 21:53:07 +0000
@@ -594,7 +594,7 @@
594 property string lastfmusername594 property string lastfmusername
595 property string lastfmpassword595 property string lastfmpassword
596 property string timestamp // used to scrobble596 property string timestamp // used to scrobble
597 property var chosenElement: null597 property var chosenElements: []
598 property bool toolbarShown: musicToolbar.visible598 property bool toolbarShown: musicToolbar.visible
599 property bool selectedAlbum: false599 property bool selectedAlbum: false
600600
@@ -875,7 +875,7 @@
875 }875 }
876876
877 // Popover for tracks, queue and add to playlist, for example877 // Popover for tracks, queue and add to playlist, for example
878 Component {878 Component { // TODO: needed anymore? remove?
879 id: trackPopoverComponent879 id: trackPopoverComponent
880 Popover {880 Popover {
881 id: trackPopover881 id: trackPopover
@@ -895,9 +895,12 @@
895 anchors.verticalCenter: parent.verticalCenter895 anchors.verticalCenter: parent.verticalCenter
896 }896 }
897 onClicked: {897 onClicked: {
898 console.debug("Debug: Add track to queue: " + JSON.stringify(chosenElement))898 console.debug("Debug: Add track to queue: " + JSON.stringify(chosenElements))
899 PopupUtils.close(trackPopover)899 PopupUtils.close(trackPopover)
900 trackQueue.append(chosenElement)900
901 for (var i=0; i < chosenElements.length; i++) {
902 trackQueue.append(chosenElements[i])
903 }
901 }904 }
902 }905 }
903 ListItem.Standard {906 ListItem.Standard {

Subscribers

People subscribed via source and target branches