diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index d779b4ba42..8a067c0733 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -18,6 +18,7 @@ import Qt.labs.settings 1.0 import "../styles-uit" import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls +import "models" as HifiModels // references HMD, Users, UserActivityLogger from root context @@ -44,6 +45,28 @@ Rectangle { property bool punctuationMode: false; HifiConstants { id: hifi; } + RootHttpRequest { id: http; } + HifiModels.PSFListModel { + id: connectionsUserModel; + http: http; + endpoint: "/api/v1/users?filter=connections"; + localSort: true; + property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); + sortProperty: sortColumn ? sortColumn.role : "userName"; + sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder; + itemsPerPage: 9; + listView: connectionsTable; + processPage: function (data) { + return data.users.map(function (user) { + return { + userName: user.username, + connection: user.connection, + profileUrl: user.images.thumbnail, + placeName: (user.location.root || user.location.domain || {}).name || '' + }; + }); + }; + } // The letterbox used for popup messages LetterboxMessage { @@ -106,16 +129,6 @@ Rectangle { }); return sessionIDs; } - function getSelectedConnectionsUserNames() { - var userNames = []; - connectionsTable.selection.forEach(function (userIndex) { - var datum = connectionsUserModelData[userIndex]; - if (datum) { - userNames.push(datum.userName); - } - }); - return userNames; - } function refreshNearbyWithFilter() { // We should just be able to set settings.filtered to inViewCheckbox.checked, but see #3249, so send to .js for saving. var userIds = getSelectedNearbySessionIDs(); @@ -232,9 +245,7 @@ Rectangle { anchors.fill: parent; onClicked: { if (activeTab != "connectionsTab") { - connectionsLoading.visible = false; - connectionsLoading.visible = true; - pal.sendToScript({method: 'refreshConnections'}); + connectionsUserModel.getFirstPage(); } activeTab = "connectionsTab"; connectionsHelpText.color = hifi.colors.blueAccent; @@ -258,11 +269,7 @@ Rectangle { id: reloadConnections; width: reloadConnections.height; glyph: hifi.glyphs.reload; - onClicked: { - connectionsLoading.visible = false; - connectionsLoading.visible = true; - pal.sendToScript({method: 'refreshConnections'}); - } + onClicked: connectionsUserModel.getFirstPage('delayRefresh'); } } // "CONNECTIONS" text @@ -702,7 +709,7 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: 185; anchors.horizontalCenter: parent.horizontalCenter; - visible: true; + visible: !connectionsUserModel.retrievedAtLeastOnePage; onVisibleChanged: { if (visible) { connectionsTimeoutTimer.start(); @@ -747,14 +754,6 @@ Rectangle { headerVisible: true; sortIndicatorColumn: settings.connectionsSortIndicatorColumn; sortIndicatorOrder: settings.connectionsSortIndicatorOrder; - onSortIndicatorColumnChanged: { - settings.connectionsSortIndicatorColumn = sortIndicatorColumn; - sortConnectionsModel(); - } - onSortIndicatorOrderChanged: { - settings.connectionsSortIndicatorOrder = sortIndicatorOrder; - sortConnectionsModel(); - } TableViewColumn { id: connectionsUserNameHeader; @@ -779,8 +778,10 @@ Rectangle { resizable: false; } - model: ListModel { - id: connectionsUserModel; + model: connectionsUserModel.model; + Connections { + target: connectionsTable.flickableItem; + onAtYEndChanged: if (connectionsTable.flickableItem.atYEnd) { connectionsUserModel.getNextPage(); } } // This Rectangle refers to each Row in the connectionsTable. @@ -1130,16 +1131,6 @@ Rectangle { sortModel(); reloadNearby.color = 0; break; - case 'connections': - var data = message.params; - if (pal.debug) { - console.log('Got connection data: ', JSON.stringify(data)); - } - connectionsUserModelData = data; - sortConnectionsModel(); - connectionsLoading.visible = false; - connectionsRefreshProblemText.visible = false; - break; case 'select': var sessionIds = message.params[0]; var selected = message.params[1]; @@ -1239,6 +1230,11 @@ Rectangle { reloadNearby.color = 2; } break; + case 'inspectionCertificate_resetCert': // HRS FIXME what's this about? + break; + case 'http.response': + http.handleHttpResponse(message); + break; default: console.log('Unrecognized message:', JSON.stringify(message)); } @@ -1287,45 +1283,6 @@ Rectangle { nearbyTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning); } } - function sortConnectionsModel() { - var column = connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); - var sortProperty = column ? column.role : "userName"; - var before = (connectionsTable.sortIndicatorOrder === Qt.AscendingOrder) ? -1 : 1; - var after = -1 * before; - // get selection(s) before sorting - var selectedIDs = getSelectedConnectionsUserNames(); - connectionsUserModelData.sort(function (a, b) { - var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase(); - if (!aValue && !bValue) { - return 0; - } else if (!aValue) { - return after; - } else if (!bValue) { - return before; - } - switch (true) { - case (aValue < bValue): return before; - case (aValue > bValue): return after; - default: return 0; - } - }); - connectionsTable.selection.clear(); - - connectionsUserModel.clear(); - var userIndex = 0; - var newSelectedIndexes = []; - connectionsUserModelData.forEach(function (datum) { - datum.userIndex = userIndex++; - connectionsUserModel.append(datum); - if (selectedIDs.indexOf(datum.sessionId) != -1) { - newSelectedIndexes.push(datum.userIndex); - } - }); - if (newSelectedIndexes.length > 0) { - connectionsTable.selection.select(newSelectedIndexes); - connectionsTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning); - } - } signal sendToScript(var message); function noticeSelection() { var userIds = []; diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 716758a3fe..21d803b1ab 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -401,7 +401,6 @@ Item { itemsPerPage: 8; listView: connectionsList; processPage: function (data) { - console.log("processPage", connectionsModel.listModelName, JSON.stringify(data)); return data.users; }; searchFilter: filterBar.text; diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index d9e31cbfa1..9858d76d4a 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -25,15 +25,17 @@ Item { // Parameters. Even if you override getPage, below, please set these for clarity and consistency, when applicable. // E.g., your getPage function could refer to this sortKey, etc. property string endpoint; - property string sortKey; + property string sortProperty; // Currently only handles sorting on one column, which fits with current needs and tables. + property bool sortAscending; + property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "asc" : "desc")); property string searchFilter: ""; property string tagsFilter; // QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early. property bool initialized: false; Component.onCompleted: initialized = true; - onEndpointChanged: if (initialized) { getFirstPage(); } - onSortKeyChanged: if (initialized) { getFirstPage(); } + onEndpointChanged: if (initialized) { getFirstPage('delayClear'); } + onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); } onSearchFilterChanged: { if (!initialized) { return; } if (searchItemTest) { @@ -42,14 +44,15 @@ Item { finalModel.append(filteredCopy); debugView('after searchFilterChanged'); } else { // TODO: fancy timer against fast typing. - getFirstPage(); + getFirstPage('delayClear'); } } - onTagsFilterChanged: if (initialized) { getFirstPage(); } + onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); } property int itemsPerPage: 100; // If the endpoint doesn't do search, tags, sort, these functions can be supplied to do it here. property var searchItemTest: null; + property bool localSort: false; property var copyOfItems: []; // State. @@ -92,21 +95,29 @@ Item { return fail("Mismatched page, expected:" + currentPageToRetrieve); } processed = processPage(response.data || response); + if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { + currentPageToRetrieve = -1; + } if (searchItemTest) { copyOfItems = copyOfItems.concat(processed); if (searchFilter) { processed = applySearchItemTest(processed); } } + if (localSort) { + copyOfItems = copyOfItems.concat(processed); + if (sortProperty) { + sortCopy(sortProperty, sortAscending); + processed = copyOfItems; + delayedClear = true; // see next conditional + } + } if (delayedClear) { finalModel.clear(); delayedClear = false; } finalModel.append(processed); // FIXME keep index steady, and apply any post sort retrievedAtLeastOnePage = true; - if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { - currentPageToRetrieve = -1; - } debugView('after handlePage'); if (searchItemTest && searchFilter && listView && listView.atYEnd && (currentPageToRetrieve >= 0)) { getNextPage(); // too fancy?? @@ -185,85 +196,26 @@ Item { id: finalModel; } - // Used when sorting model data on the CLIENT - // Right now, there is no sorting done on the client for - // any users of PSFListModel, but that could very easily change. - property string sortColumnName: ""; - property bool isSortingDescending: true; - property bool valuesAreNumerical: false; + function sortCopy(sortProperty, isAscending) { + console.debug('client sort', listModelName, sortProperty, isAscending, copyOfItems.length, 'items'); + var before = isAscending ? -1 : 1; + var after = -1 * before; - function swap(a, b) { - if (a < b) { - move(a, b, 1); - move(b - 1, a, 1); - } else if (a > b) { - move(b, a, 1); - move(a - 1, b, 1); - } - } - - function partition(begin, end, pivot) { - if (valuesAreNumerical) { - var piv = get(pivot)[sortColumnName]; - swap(pivot, end - 1); - var store = begin; - var i; - - for (i = begin; i < end - 1; ++i) { - var currentElement = get(i)[sortColumnName]; - if (isSortingDescending) { - if (currentElement > piv) { - swap(store, i); - ++store; - } - } else { - if (currentElement < piv) { - swap(store, i); - ++store; - } - } + copyOfItems.sort(function (a, b) { + var aValue = a[sortProperty].toString().toLowerCase(), + bValue = b[sortProperty].toString().toLowerCase(); + if (!aValue && !bValue) { + return 0; + } else if (!aValue) { + return after; + } else if (!bValue) { + return before; } - swap(end - 1, store); - - return store; - } else { - var piv = get(pivot)[sortColumnName].toLowerCase(); - swap(pivot, end - 1); - var store = begin; - var i; - - for (i = begin; i < end - 1; ++i) { - var currentElement = get(i)[sortColumnName].toLowerCase(); - if (isSortingDescending) { - if (currentElement > piv) { - swap(store, i); - ++store; - } - } else { - if (currentElement < piv) { - swap(store, i); - ++store; - } - } + switch (true) { + case (aValue < bValue): return before; + case (aValue > bValue): return after; + default: return 0; } - swap(end - 1, store); - - return store; - } - } - - function qsort(begin, end) { - if (end - 1 > begin) { - var pivot = begin + Math.floor(Math.random() * (end - begin)); - - pivot = partition(begin, end, pivot); - - qsort(begin, pivot); - qsort(pivot + 1, end); - } - } - - function quickSort() { - qsort(0, count) + }); } } \ No newline at end of file diff --git a/scripts/system/pal.js b/scripts/system/pal.js index c70c2729f5..b122a5170a 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -317,6 +317,8 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } ); break; + case 'http.request': + break; // Handled elsewhere. default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); }