Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pagination #3505

Merged
merged 18 commits into from
Jan 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
- Datasets imported through a datastore that is marked as 'scratch' will now show a construction-like header and error message to encourage moving the datasets to a permanent storage location. [#3500](https://github.com/scalableminds/webknossos/pull/3500)
- Added the experimental feature to dynamically render isosurfaces for segmentation layers (can be enabled in the dataset settings when viewing a dataset). [#3533](https://github.com/scalableminds/webknossos/pull/3495)
- Adds healthchecks to all Dockerfiles for automatic service healing [#3606](https://github.com/scalableminds/webknossos/pull/3606)
- Added possibility to load more tasks or explorative annotations in the dashboard. [#3505](https://github.com/scalableminds/webknossos/pull/3505)
- Adds a second colorful thumbnail for the datasets which have a segmentation layer and this segmentation thumbnail will be shown on hover over the other thumbnail. [#3507](https://github.com/scalableminds/webknossos/pull/3507)

### Changed
Expand Down
8 changes: 6 additions & 2 deletions app/assets/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,20 @@ export async function getUsersWithActiveTasks(projectName: string): Promise<Arra
// ### Annotations
export function getCompactAnnotations(
isFinished: boolean,
pageNumber: number = 0,
): Promise<Array<APIAnnotationTypeCompact>> {
return Request.receiveJSON(`/api/user/annotations?isFinished=${isFinished.toString()}`);
return Request.receiveJSON(
`/api/user/annotations?isFinished=${isFinished.toString()}&pageNumber=${pageNumber}`,
);
}

export function getCompactAnnotationsForUser(
userId: string,
isFinished: boolean,
pageNumber: number = 0,
): Promise<Array<APIAnnotationTypeCompact>> {
return Request.receiveJSON(
`/api/users/${userId}/annotations?isFinished=${isFinished.toString()}`,
`/api/users/${userId}/annotations?isFinished=${isFinished.toString()}&pageNumber=${pageNumber}`,
);
}

Expand Down
143 changes: 90 additions & 53 deletions app/assets/javascripts/dashboard/dashboard_task_list_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ import * as Utils from "libs/utils";
import messages from "messages";

const typeHint: APITaskWithAnnotation[] = [];
const pageLength: number = 1000;

type StateProps = {
activeUser: APIUser,
};

export type TaskModeState = {
tasks: Array<APITaskWithAnnotation>,
loadedAllTasks: boolean,
lastLoadedPage: number,
};

type Props = {
userId: ?string,
isAdminView: boolean,
Expand All @@ -43,11 +50,11 @@ type Props = {

type State = {
showFinishedTasks: boolean,
finishedTasks: Array<APITaskWithAnnotation>,
unfinishedTasks: Array<APITaskWithAnnotation>,
isLoading: boolean,
isTransferModalVisible: boolean,
currentAnnotationId: ?string,
finishedModeState: TaskModeState,
unfinishedModeState: TaskModeState,
};

const persistence: Persistence<State> = new Persistence(
Expand Down Expand Up @@ -86,19 +93,27 @@ const convertAnnotationToTaskWithAnnotationType = (
class DashboardTaskListView extends React.PureComponent<Props, State> {
state = {
showFinishedTasks: false,
finishedTasks: [],
unfinishedTasks: [],
isLoading: false,
isTransferModalVisible: false,
currentAnnotationId: null,
finishedModeState: {
tasks: [],
loadedAllTasks: false,
lastLoadedPage: -1,
},
unfinishedModeState: {
tasks: [],
loadedAllTasks: false,
lastLoadedPage: -1,
},
};

componentWillMount() {
this.setState(persistence.load(this.props.history));
}

componentDidMount() {
this.fetchData();
this.fetchNextPage(0);
}

componentWillUpdate(nextProps, nextState) {
Expand All @@ -107,6 +122,20 @@ class DashboardTaskListView extends React.PureComponent<Props, State> {

getFinishVerb = () => (this.state.showFinishedTasks ? "Unfinished" : "Finished");

getCurrentModeState = () =>
this.state.showFinishedTasks ? this.state.finishedModeState : this.state.unfinishedModeState;

setCurrentModeState = modeShape => {
const showFinishedTasks = this.state.showFinishedTasks;
this.setState(prevState => {
const newSubState = {
...prevState[showFinishedTasks ? "finishedModeState" : "unfinishedModeState"],
...modeShape,
};
return { [showFinishedTasks ? "finishedModeState" : "unfinishedModeState"]: newSubState };
});
};

confirmFinish(task: APITaskWithAnnotation) {
Modal.confirm({
content: messages["annotation.finish"],
Expand All @@ -118,43 +147,61 @@ class DashboardTaskListView extends React.PureComponent<Props, State> {
const changedTask = convertAnnotationToTaskWithAnnotationType(changedAnnotationWithTask);

this.setState(prevState => {
const newUnfinishedTasks = prevState.unfinishedTasks.filter(t => t.id !== task.id);
const newFinishedTasks = [changedTask].concat(prevState.finishedTasks);

const newUnfinishedTasks = prevState.unfinishedModeState.tasks.filter(
t => t.id !== task.id,
);
const newFinishedTasks = [changedTask].concat(prevState.finishedModeState.tasks);

const newUnfinishedModeState = {
...prevState.unfinishedModeState,
tasks: newUnfinishedTasks,
};
const newFinishedModeState = { ...prevState.finishedModeState, tasks: newFinishedTasks };
return {
unfinishedTasks: newUnfinishedTasks,
finishedTasks: newFinishedTasks,
unfinishedModeState: newUnfinishedModeState,
finishedModeState: newFinishedModeState,
};
});
},
});
}

async fetchData(): Promise<void> {
fetchNextPage = async pageNumber => {
// this refers not to the pagination of antd but to the pagination of querying data from SQL
const isFinished = this.state.showFinishedTasks;
const previousTasks = this.getCurrentModeState().tasks;

const url = this.props.userId
? `/api/users/${this.props.userId}/tasks?isFinished=${isFinished.toString()}`
: `/api/user/tasks?isFinished=${isFinished.toString()}`;
? `/api/users/${
this.props.userId
}/tasks?isFinished=${isFinished.toString()}&pageNumber=${pageNumber}`
: `/api/user/tasks?isFinished=${isFinished.toString()}&pageNumber=${pageNumber}`;

try {
this.setState({ isLoading: true });
const annotationsWithTasks = await Request.receiveJSON(url);
const tasks = annotationsWithTasks.map(convertAnnotationToTaskWithAnnotationType);

this.setState({
[isFinished ? "finishedTasks" : "unfinishedTasks"]: tasks,
this.setCurrentModeState({
loadedAllTasks:
annotationsWithTasks.length !== pageLength || annotationsWithTasks.length === 0,
tasks: previousTasks.concat(tasks),
lastLoadedPage: pageNumber,
});
} catch (error) {
handleGenericError(error);
} finally {
this.setState({ isLoading: false });
}
}
};

toggleShowFinished = () => {
this.setState(
prevState => ({ showFinishedTasks: !prevState.showFinishedTasks }),
() => this.fetchData(),
prevState => ({
showFinishedTasks: !prevState.showFinishedTasks,
}),
() => {
if (this.getCurrentModeState().lastLoadedPage === -1) this.fetchNextPage(0);
},
);
};

Expand Down Expand Up @@ -240,7 +287,6 @@ class DashboardTaskListView extends React.PureComponent<Props, State> {
}

cancelAnnotation(annotation: APIAnnotation) {
const wasFinished = this.state.showFinishedTasks;
const annotationId = annotation.id;

Modal.confirm({
Expand All @@ -249,23 +295,15 @@ class DashboardTaskListView extends React.PureComponent<Props, State> {
okText: messages.yes,
onOk: async () => {
await deleteAnnotation(annotationId, annotation.typ);
if (wasFinished) {
this.setState(prevState => ({
finishedTasks: prevState.finishedTasks.filter(t => t.annotation.id !== annotationId),
}));
} else {
this.setState(prevState => ({
unfinishedTasks: prevState.unfinishedTasks.filter(
t => t.annotation.id !== annotationId,
),
}));
}
this.setCurrentModeState({
tasks: this.getCurrentModeState().tasks.filter(t => t.annotation.id !== annotationId),
});
},
});
}

async confirmGetNewTask(): Promise<void> {
if (this.state.unfinishedTasks.length === 0) {
if (this.state.unfinishedModeState.tasks.length === 0) {
return this.getNewTask();
} else {
let modalContent = messages["task.request_new"];
Expand All @@ -288,11 +326,13 @@ class DashboardTaskListView extends React.PureComponent<Props, State> {
this.setState({ isLoading: true });
try {
const newTaskAnnotation = await requestTask();

this.setState(prevState => ({
unfinishedTasks: prevState.unfinishedTasks.concat([
convertAnnotationToTaskWithAnnotationType(newTaskAnnotation),
]),
unfinishedModeState: {
...prevState.unfinishedModeState,
tasks: prevState.unfinishedModeState.tasks.concat([
convertAnnotationToTaskWithAnnotationType(newTaskAnnotation),
]),
},
}));
} catch (ex) {
// catch exception so that promise does not fail and the modal will close
Expand All @@ -306,26 +346,13 @@ class DashboardTaskListView extends React.PureComponent<Props, State> {

const removeTransferredTask = (tasks, currentAnnotationId) =>
tasks.filter(t => t.annotation.id !== currentAnnotationId);

if (this.state.showFinishedTasks) {
this.setState(prevState => ({
finishedTasks: removeTransferredTask(
prevState.finishedTasks,
prevState.currentAnnotationId,
),
}));
} else {
this.setState(prevState => ({
unfinishedTasks: removeTransferredTask(
prevState.unfinishedTasks,
prevState.currentAnnotationId,
),
}));
}
this.setCurrentModeState({
tasks: removeTransferredTask(this.getCurrentTasks(), this.state.currentAnnotationId),
});
}

getCurrentTasks() {
return this.state.showFinishedTasks ? this.state.finishedTasks : this.state.unfinishedTasks;
return this.getCurrentModeState().tasks;
}

renderPlaceholder() {
Expand Down Expand Up @@ -428,6 +455,16 @@ class DashboardTaskListView extends React.PureComponent<Props, State> {
</h3>
<div className="clearfix" style={{ margin: "20px 0px" }} />
{this.renderTaskList()}
<div style={{ textAlign: "right" }}>
{!this.getCurrentModeState().loadedAllTasks ? (
<Link
to="#"
onClick={() => this.fetchNextPage(this.getCurrentModeState().lastLoadedPage + 1)}
>
Load more Tasks
</Link>
) : null}
</div>
<TransferTaskModal
visible={this.state.isTransferModalVisible}
annotationId={this.state.currentAnnotationId}
Expand Down
Loading