Skip to content

Commit

Permalink
Merge pull request #932 from LibraryOfCongress/finish-activity-ui-ass…
Browse files Browse the repository at this point in the history
…et-reservations

Finish activity UI asset reservations
  • Loading branch information
rstorey committed May 8, 2019
2 parents 3930d6e + 601b357 commit 5d89d48
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 90 deletions.
18 changes: 15 additions & 3 deletions concordia/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ def serialize_context(self, context):
class APIListView(APIViewMixin, ListView):
"""ListView which can also return JSON with consistent pagination"""

def get_paginate_by(self, queryset):
per_page = self.request.GET.get("per_page")

if per_page and per_page.isdigit():
return int(per_page)
else:
return self.paginate_by

def serialize_context(self, context):
data = {
"objects": [self.serialize_object(i) for i in context["object_list"]],
Expand All @@ -104,17 +112,21 @@ def serialize_context(self, context):

page_obj = context["page_obj"]
if page_obj:
per_page = context["paginator"].per_page

data["pagination"] = pagination = {
"first": self.request.build_absolute_uri(
"%s?page=%s" % (self.request.path, 1)
"%s?page=%s&per_page=%d" % (self.request.path, 1, per_page)
),
"last": self.request.build_absolute_uri(
"%s?page=%s" % (self.request.path, page_obj.paginator.num_pages)
"%s?page=%s&per_page=%d"
% (self.request.path, page_obj.paginator.num_pages, per_page)
),
}
if page_obj.has_next():
pagination["next"] = self.request.build_absolute_uri(
"%s?page=%s" % (self.request.path, page_obj.next_page_number())
"%s?page=%s&per_page=%d"
% (self.request.path, page_obj.next_page_number(), per_page)
)

return data
23 changes: 15 additions & 8 deletions concordia/static/js/action-app/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ class AssetListItem {
this.el.dataset.id = assetData.id;
this.el.dataset.difficulty = assetData.difficulty;
this.el.dataset.status = assetData.status;

if (!assetData.editable.canEdit) {
this.el.dataset.unavailable = assetData.editable.reason;
} else {
delete this.el.dataset.unavailable;
}

this.el.title = `${assetData.title} (${assetData.project.title})`;
}
}
Expand Down Expand Up @@ -290,6 +297,9 @@ export class AssetList extends List {
assetListObserver
]);

// These will be processed after the next asset list update completes (possibly after a rAF / rIC chain)
this.updateCallbacks = [];

let assetOpenHandler = event => {
let target = event.target;
if (target && target.classList.contains('asset')) {
Expand Down Expand Up @@ -336,18 +346,15 @@ export class AssetList extends List {

this.el.addEventListener('mouseout', handleTooltipHideEvent);
this.el.addEventListener('focusout', handleTooltipHideEvent);

$('#asset-list-thumbnail-size').addEventListener('input', event => {
this.el.style.setProperty(
'--asset-thumbnail-size',
event.target.value + 'px'
);
this.attemptAssetLazyLoad();
});
}

update(assets) {
super.update(assets);

while (this.updateCallbacks.length) {
let callback = this.updateCallbacks.pop();
callback();
}
}

scrollToActiveAsset() {
Expand Down
162 changes: 92 additions & 70 deletions concordia/static/js/action-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class ActionApp {
});

this.fetchAssetPage(allAssetsURL + '?pk=' + assetId).then(() => {
this.assetListUpdateCallbacks.push(() => {
this.assetList.updateCallbacks.push(() => {
let element = document.getElementById(assetId);
if (!element) {
console.warn('Expected to load asset with ID %s', assetId);
Expand Down Expand Up @@ -134,25 +134,8 @@ export class ActionApp {

$$('button', this.modeSelection).forEach(element => {
element.addEventListener('click', event => {
$$('button', this.modeSelection).forEach(inactiveElement => {
inactiveElement.classList.remove('active');
});

let target = event.target;

target.classList.add('active');

this.currentMode = target.value;
this.addToState('mode', this.currentMode);

this.appElement.dataset.mode = this.currentMode;
$$('.current-mode').forEach(
i => (i.textContent = this.currentMode)
);

this.updateAvailableCampaignFilters();
this.closeViewer();
this.refreshData();
this.switchMode(target.value);
});
});

Expand All @@ -165,6 +148,24 @@ export class ActionApp {
}
}

switchMode(newMode) {
console.info(`Switch mode from ${this.currentMode} to ${newMode}`);
this.currentMode = newMode;
this.appElement.dataset.mode = this.currentMode;
this.addToState('mode', this.currentMode);
this.queuedAssetPageURLs.length = 0;

$$('button', this.modeSelection).forEach(button => {
button.classList.toggle('active', button.value == newMode);
});

$$('.current-mode').forEach(i => (i.textContent = this.currentMode));

this.updateAvailableCampaignFilters();
this.closeViewer();
this.refreshData();
}

setupToolbars() {
let helpToggle = $('#help-toggle');
let helpPanel = $('#help-panel');
Expand Down Expand Up @@ -229,7 +230,9 @@ export class ActionApp {
latest_transcription: message.latest_transcription,
status: message.status
};

this.mergeAssetUpdate(assetId, assetUpdate);

break;
}
case 'asset_reservation_obtained':
Expand All @@ -238,30 +241,30 @@ export class ActionApp {
is not the same as the user who obtained the reservation,
then mark it unavailable
*/
if (
!this.config.currentUser ||
(this.config.currentUser &&
this.config.currentUser != message.user_pk)
) {
console.error(
'// FIXME: handle asset reservation updates'
);
this.markAssetAsUnavailable(
assetId,
'Someone else is working on this'
);
}

this.mergeAssetUpdate(assetId, {
reservationToken: message.reservation_token
});

break;
case 'asset_reservation_released':
// FIXME: we need to test whether the user who reserved it is different than the user we're running as!
console.error('// FIXME: handle asset reservation updates');
this.markAssetAsAvailable(assetId);
this.mergeAssetUpdate(assetId, {
reservationToken: null
});

break;
default:
console.warn(
`Unknown message type ${message.type}: ${message}`
);
}

let assetListItem = this.assetList.lookup[assetId];
if (assetListItem) {
// If this is visible, we want to update the displayed asset
// list icon using the current value:
assetListItem.update(this.assets.get(assetId));
}
});

assetSocket.addEventListener('error', event => {
Expand All @@ -275,6 +278,14 @@ export class ActionApp {
}

refreshData() {
console.time('Refreshing asset editability');

this.assets.forEach(asset => {
asset.editable = this.canEditAsset(asset);
});

console.timeEnd('Refreshing asset editability');

this.updateAssetList();
this.fetchAssetData(); // This starts the fetch process going by calculating the appropriate base URL
}
Expand All @@ -284,9 +295,6 @@ export class ActionApp {
// been fetched which fetchNextAssetPage will empty:
this.queuedAssetPageURLs = [];

// These will be processed after the next asset list update completes (possibly after a rAF / rIC chain)
this.assetListUpdateCallbacks = [];

let loadMoreButton = $('#load-more-assets');
loadMoreButton.addEventListener('click', () =>
this.fetchNextAssetPage()
Expand Down Expand Up @@ -342,6 +350,14 @@ export class ActionApp {
this.addToState('campaign', this.campaignSelect.value);
this.updateAssetList();
});

$('#asset-list-thumbnail-size').addEventListener('input', event => {
this.assetList.el.style.setProperty(
'--asset-thumbnail-size',
event.target.value + 'px'
);
this.attemptAssetLazyLoad();
});
}

updateAvailableCampaignFilters() {
Expand Down Expand Up @@ -398,24 +414,32 @@ export class ActionApp {
}

fetchAssetPage(url) {
let startingMode = this.currentMode;

return fetchJSON(url)
.then(data => {
data.objects.forEach(i => {
i.sent = data.sent;
this.createAsset(i);
});

$('#asset-count').textContent = this.assets.size;

if (data.pagination.next) {
this.queuedAssetPageURLs.push(data.pagination.next);
}

if (this.assets.size < 300) {
// We like to have a fair number of items to start with
window.requestIdleCallback(
this.fetchNextAssetPage.bind(this)
if (this.currentMode != startingMode) {
console.warn(
`Mode changed from ${startingMode} to ${
this.currentMode
} while request for ${url} was being processed; halting chained fetches`
);
} else {
if (data.pagination.next) {
this.queuedAssetPageURLs.push(data.pagination.next);
}

if (this.assets.size < 300) {
// We like to have a fair number of items to start with
window.requestIdleCallback(
this.fetchNextAssetPage.bind(this)
);
}
}
})
.then(() => {
Expand Down Expand Up @@ -472,14 +496,12 @@ export class ActionApp {
}

for (let k of ['status', 'difficulty', 'latest_transcription']) {
mergedData[k] = freshestCopy[k];
if (k in freshestCopy) {
mergedData[k] = freshestCopy[k];
}
}

console.debug(
`Changing asset ${assetId} from ${JSON.stringify(
oldData
)} to ${JSON.stringify(mergedData)}`
);
mergedData.editable = this.canEditAsset(mergedData);

this.assets.set(assetId, mergedData);
}
Expand Down Expand Up @@ -514,9 +536,6 @@ export class ActionApp {
assetID = asset.id;
}

// FIXME: the mergeAssetUpdate() process should trigger a call to this & update the asset list & viewer
// FIXME: decide what call signature will support specifying the displayed reason

if (!asset) {
throw `No information for an asset with ID ${assetID}`;
}
Expand Down Expand Up @@ -554,13 +573,23 @@ export class ActionApp {
asset.status != 'in_progress'
) {
canEdit = false;
reason = 'this asset is not available for transcription';
reason = `assets with status ${
asset.status
} are not available for transcription`;
}
} else {
throw `Unexpected mode ${this.currentMode}`;
}

console.info(
if (
asset.reservationToken &&
asset.reservationToken != this.config.reservationToken
) {
canEdit = false;
reason = 'Another person is working on this asset';
}

console.debug(
'Asset ID %s: editable=%s, reason="%s"',
assetID,
canEdit,
Expand Down Expand Up @@ -595,23 +624,13 @@ export class ActionApp {
this.assetList.update(visibleAssets);
console.timeEnd('Updating asset list');

$('#visible-asset-count').textContent = visibleAssets.length;

this.assetList.scrollToActiveAsset();

this.runAssetListUpdateCallbacks();
this.attemptAssetLazyLoad();
});
});
}

runAssetListUpdateCallbacks() {
while (this.assetListUpdateCallbacks.length) {
let callback = this.assetListUpdateCallbacks.pop();
callback();
}
}

attemptAssetLazyLoad() {
/*
If the list is small enough to display without scrolling we'll
Expand Down Expand Up @@ -690,7 +709,6 @@ export class ActionApp {
currentCampaignId = parseInt(currentCampaignId, 10);
}

// TODO: We should have a cleaner way to filter the assets which are in scope due to the current status & having been fully loaded
let currentStatuses;
let currentMode = this.currentMode;
if (currentMode == 'review') {
Expand Down Expand Up @@ -729,6 +747,10 @@ export class ActionApp {
}

openViewer(assetElement) {
if (this.openAssetElement) {
this.releaseAsset();
}

let asset = this.assets.get(assetElement.dataset.id);

this.addToState('asset', asset.id);
Expand Down
7 changes: 0 additions & 7 deletions concordia/templates/action-app.html
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,4 @@ <h5>Keyboard Shortcuts</h5>
</p>
</div>
</main>
<footer id="action-app-footer" class="footer fixed-bottom bg-light">
<div class="container">
<div class="m-auto text-center text-muted">
Displaying <span id="visible-asset-count">0</span> of <span id="asset-count">0</span> assets
</div>
</div>
</footer>
{% endblock site-main %}

0 comments on commit 5d89d48

Please sign in to comment.