React-UI: settings (#1164)

* Image filters: brightness, contrast, saturation
* Auto saving
* Frame auto fit
* Player speed
* Leave confirmation for unsaved changes
main
Dmitry Kalinin 6 years ago committed by GitHub
parent 90d594d706
commit 5645cdf7c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -77,12 +77,6 @@
return result; return result;
}, },
async hasUnsavedChanges() {
const result = await PluginRegistry
.apiWrapper.call(this, prototype.annotations.hasUnsavedChanges);
return result;
},
async merge(objectStates) { async merge(objectStates) {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper.call(this, prototype.annotations.merge, objectStates); .apiWrapper.call(this, prototype.annotations.merge, objectStates);
@ -107,6 +101,12 @@
.apiWrapper.call(this, prototype.annotations.exportDataset, format); .apiWrapper.call(this, prototype.annotations.exportDataset, format);
return result; return result;
}, },
hasUnsavedChanges() {
const result = prototype.annotations
.hasUnsavedChanges.implementation.call(this);
return result;
},
}, },
writable: true, writable: true,
}), }),
@ -381,14 +381,14 @@
* @async * @async
*/ */
/** /**
* Indicate if there are any changes in * Method indicates if there are any changes in
* annotations which haven't been saved on a server * annotations which haven't been saved on a server
* </br><b> This function cannot be wrapped with a plugin </b>
* @method hasUnsavedChanges * @method hasUnsavedChanges
* @memberof Session.annotations * @memberof Session.annotations
* @returns {boolean} * @returns {boolean}
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @instance * @instance
* @async
*/ */
/** /**
* Export as a dataset. * Export as a dataset.

@ -341,11 +341,11 @@ describe('Feature: save annotations', () => {
zOrder: 0, zOrder: 0,
}); });
expect(await task.annotations.hasUnsavedChanges()).toBe(false); expect(task.annotations.hasUnsavedChanges()).toBe(false);
await task.annotations.put([state]); await task.annotations.put([state]);
expect(await task.annotations.hasUnsavedChanges()).toBe(true); expect(task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.save(); await task.annotations.save();
expect(await task.annotations.hasUnsavedChanges()).toBe(false); expect(task.annotations.hasUnsavedChanges()).toBe(false);
annotations = await task.annotations.get(0); annotations = await task.annotations.get(0);
expect(annotations).toHaveLength(length + 1); expect(annotations).toHaveLength(length + 1);
}); });
@ -354,23 +354,23 @@ describe('Feature: save annotations', () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0); const annotations = await task.annotations.get(0);
expect(await task.annotations.hasUnsavedChanges()).toBe(false); expect(task.annotations.hasUnsavedChanges()).toBe(false);
annotations[0].occluded = true; annotations[0].occluded = true;
await annotations[0].save(); await annotations[0].save();
expect(await task.annotations.hasUnsavedChanges()).toBe(true); expect(task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.save(); await task.annotations.save();
expect(await task.annotations.hasUnsavedChanges()).toBe(false); expect(task.annotations.hasUnsavedChanges()).toBe(false);
}); });
test('delete & save annotations for a task', async () => { test('delete & save annotations for a task', async () => {
const task = (await window.cvat.tasks.get({ id: 101 }))[0]; const task = (await window.cvat.tasks.get({ id: 101 }))[0];
const annotations = await task.annotations.get(0); const annotations = await task.annotations.get(0);
expect(await task.annotations.hasUnsavedChanges()).toBe(false); expect(task.annotations.hasUnsavedChanges()).toBe(false);
await annotations[0].delete(); await annotations[0].delete();
expect(await task.annotations.hasUnsavedChanges()).toBe(true); expect(task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.save(); await task.annotations.save();
expect(await task.annotations.hasUnsavedChanges()).toBe(false); expect(task.annotations.hasUnsavedChanges()).toBe(false);
}); });
test('create & save annotations for a job', async () => { test('create & save annotations for a job', async () => {
@ -387,11 +387,11 @@ describe('Feature: save annotations', () => {
zOrder: 0, zOrder: 0,
}); });
expect(await job.annotations.hasUnsavedChanges()).toBe(false); expect(job.annotations.hasUnsavedChanges()).toBe(false);
await job.annotations.put([state]); await job.annotations.put([state]);
expect(await job.annotations.hasUnsavedChanges()).toBe(true); expect(job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.save(); await job.annotations.save();
expect(await job.annotations.hasUnsavedChanges()).toBe(false); expect(job.annotations.hasUnsavedChanges()).toBe(false);
annotations = await job.annotations.get(0); annotations = await job.annotations.get(0);
expect(annotations).toHaveLength(length + 1); expect(annotations).toHaveLength(length + 1);
}); });
@ -400,23 +400,23 @@ describe('Feature: save annotations', () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const annotations = await job.annotations.get(0); const annotations = await job.annotations.get(0);
expect(await job.annotations.hasUnsavedChanges()).toBe(false); expect(job.annotations.hasUnsavedChanges()).toBe(false);
annotations[0].points = [0, 100, 200, 300]; annotations[0].points = [0, 100, 200, 300];
await annotations[0].save(); await annotations[0].save();
expect(await job.annotations.hasUnsavedChanges()).toBe(true); expect(job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.save(); await job.annotations.save();
expect(await job.annotations.hasUnsavedChanges()).toBe(false); expect(job.annotations.hasUnsavedChanges()).toBe(false);
}); });
test('delete & save annotations for a job', async () => { test('delete & save annotations for a job', async () => {
const job = (await window.cvat.jobs.get({ jobID: 100 }))[0]; const job = (await window.cvat.jobs.get({ jobID: 100 }))[0];
const annotations = await job.annotations.get(0); const annotations = await job.annotations.get(0);
expect(await job.annotations.hasUnsavedChanges()).toBe(false); expect(job.annotations.hasUnsavedChanges()).toBe(false);
await annotations[0].delete(); await annotations[0].delete();
expect(await job.annotations.hasUnsavedChanges()).toBe(true); expect(job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.save(); await job.annotations.save();
expect(await job.annotations.hasUnsavedChanges()).toBe(false); expect(job.annotations.hasUnsavedChanges()).toBe(false);
}); });
test('delete & save annotations for a job when there are a track and a shape with the same id', async () => { test('delete & save annotations for a job when there are a track and a shape with the same id', async () => {
@ -658,11 +658,11 @@ describe('Feature: clear annotations', () => {
expect(annotations.length).not.toBe(0); expect(annotations.length).not.toBe(0);
annotations[0].occluded = true; annotations[0].occluded = true;
await annotations[0].save(); await annotations[0].save();
expect(await task.annotations.hasUnsavedChanges()).toBe(true); expect(task.annotations.hasUnsavedChanges()).toBe(true);
await task.annotations.clear(true); await task.annotations.clear(true);
annotations = await task.annotations.get(0); annotations = await task.annotations.get(0);
expect(annotations.length).not.toBe(0); expect(annotations.length).not.toBe(0);
expect(await task.annotations.hasUnsavedChanges()).toBe(false); expect(task.annotations.hasUnsavedChanges()).toBe(false);
}); });
test('clear annotations with reload in a job', async () => { test('clear annotations with reload in a job', async () => {
@ -671,11 +671,11 @@ describe('Feature: clear annotations', () => {
expect(annotations.length).not.toBe(0); expect(annotations.length).not.toBe(0);
annotations[0].occluded = true; annotations[0].occluded = true;
await annotations[0].save(); await annotations[0].save();
expect(await job.annotations.hasUnsavedChanges()).toBe(true); expect(job.annotations.hasUnsavedChanges()).toBe(true);
await job.annotations.clear(true); await job.annotations.clear(true);
annotations = await job.annotations.get(0); annotations = await job.annotations.get(0);
expect(annotations.length).not.toBe(0); expect(annotations.length).not.toBe(0);
expect(await job.annotations.hasUnsavedChanges()).toBe(false); expect(job.annotations.hasUnsavedChanges()).toBe(false);
}); });
test('clear annotations with bad reload parameter', async () => { test('clear annotations with bad reload parameter', async () => {

@ -12,6 +12,7 @@ import {
ShapeType, ShapeType,
ObjectType, ObjectType,
Task, Task,
FrameSpeed,
} from 'reducers/interfaces'; } from 'reducers/interfaces';
import getCore from 'cvat-core'; import getCore from 'cvat-core';
@ -27,7 +28,8 @@ function getStore(): Store<CombinedState> {
return store; return store;
} }
function receiveAnnotationsParameters(): { filters: string[]; frame: number } { function receiveAnnotationsParameters():
{ filters: string[]; frame: number; showAllInterpolationTracks: boolean } {
if (store === null) { if (store === null) {
store = getCVATStore(); store = getCVATStore();
} }
@ -35,10 +37,11 @@ function receiveAnnotationsParameters(): { filters: string[]; frame: number } {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const { filters } = state.annotation.annotations; const { filters } = state.annotation.annotations;
const frame = state.annotation.player.frame.number; const frame = state.annotation.player.frame.number;
const { showAllInterpolationTracks } = state.settings.workspace;
return { return {
filters, filters,
frame, frame,
showAllInterpolationTracks,
}; };
} }
@ -57,6 +60,7 @@ export enum AnnotationActionTypes {
GET_JOB = 'GET_JOB', GET_JOB = 'GET_JOB',
GET_JOB_SUCCESS = 'GET_JOB_SUCCESS', GET_JOB_SUCCESS = 'GET_JOB_SUCCESS',
GET_JOB_FAILED = 'GET_JOB_FAILED', GET_JOB_FAILED = 'GET_JOB_FAILED',
CLOSE_JOB = 'CLOSE_JOB',
CHANGE_FRAME = 'CHANGE_FRAME', CHANGE_FRAME = 'CHANGE_FRAME',
CHANGE_FRAME_SUCCESS = 'CHANGE_FRAME_SUCCESS', CHANGE_FRAME_SUCCESS = 'CHANGE_FRAME_SUCCESS',
CHANGE_FRAME_FAILED = 'CHANGE_FRAME_FAILED', CHANGE_FRAME_FAILED = 'CHANGE_FRAME_FAILED',
@ -143,8 +147,9 @@ export function fetchAnnotationsAsync(sessionInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters, frame } = receiveAnnotationsParameters(); const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters();
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
dispatch({ dispatch({
@ -179,12 +184,13 @@ export function undoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
// TODO: use affected IDs as an optimization // TODO: use affected IDs as an optimization
await sessionInstance.actions.undo(); await sessionInstance.actions.undo();
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
dispatch({ dispatch({
@ -211,12 +217,13 @@ export function redoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
// TODO: use affected IDs as an optimization // TODO: use affected IDs as an optimization
await sessionInstance.actions.redo(); await sessionInstance.actions.redo();
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
dispatch({ dispatch({
@ -280,7 +287,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
if (state.tasks.activities.loads[job.task.id]) { if (state.tasks.activities.loads[job.task.id]) {
throw Error('Annotations is being uploaded for the task'); throw Error('Annotations is being uploaded for the task');
@ -314,7 +321,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
await job.annotations.clear(true); await job.annotations.clear(true);
await job.actions.clear(); await job.actions.clear();
const history = await job.actions.get(); const history = await job.actions.get();
const states = await job.annotations.get(frame, false, filters); const states = await job.annotations.get(frame, showAllInterpolationTracks, filters);
setTimeout(() => { setTimeout(() => {
dispatch({ dispatch({
@ -422,6 +429,7 @@ export function propagateObjectAsync(
? objectState.objectType : ObjectType.SHAPE, ? objectState.objectType : ObjectType.SHAPE,
shapeType: objectState.shapeType, shapeType: objectState.shapeType,
label: objectState.label, label: objectState.label,
zOrder: objectState.zOrder,
frame: from, frame: from,
}; };
@ -583,7 +591,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const { instance: job } = state.annotation.job; const { instance: job } = state.annotation.job;
const { filters, frame } = receiveAnnotationsParameters(); const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters();
try { try {
if (toFrame < job.startFrame || toFrame > job.stopFrame) { if (toFrame < job.startFrame || toFrame > job.stopFrame) {
@ -610,8 +618,25 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}); });
const data = await job.frames.get(toFrame); const data = await job.frames.get(toFrame);
const states = await job.annotations.get(toFrame, false, filters); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
const currentTime = new Date().getTime();
let frameSpeed;
switch (state.settings.player.frameSpeed) {
case (FrameSpeed.Fast): {
frameSpeed = (FrameSpeed.Fast as number) / 2;
break;
}
case (FrameSpeed.Fastest): {
frameSpeed = (FrameSpeed.Fastest as number) / 3;
break;
}
default: {
frameSpeed = state.settings.player.frameSpeed as number;
}
}
const delay = Math.max(0, Math.round(1000 / frameSpeed)
- currentTime + (state.annotation.player.frame.changeTime as number));
dispatch({ dispatch({
type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS,
payload: { payload: {
@ -620,6 +645,8 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
states, states,
minZ, minZ,
maxZ, maxZ,
changeTime: currentTime + delay,
delay,
}, },
}); });
} catch (error) { } catch (error) {
@ -681,8 +708,16 @@ export function getJobAsync(
try { try {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const filters = initialFilters; const filters = initialFilters;
const { showAllInterpolationTracks } = state.settings.workspace;
// Check if already loaded job is different from asking one
if (state.annotation.job.instance && state.annotation.job.instance.id !== jid) {
dispatch({
type: AnnotationActionTypes.CLOSE_JOB,
});
}
// First check state if the task is already there // Check state if the task is already there
let task = state.tasks.current let task = state.tasks.current
.filter((_task: Task) => _task.instance.id === tid) .filter((_task: Task) => _task.instance.id === tid)
.map((_task: Task) => _task.instance)[0]; .map((_task: Task) => _task.instance)[0];
@ -701,7 +736,8 @@ export function getJobAsync(
const frameNumber = Math.max(Math.min(job.stopFrame, initialFrame), job.startFrame); const frameNumber = Math.max(Math.min(job.stopFrame, initialFrame), job.startFrame);
const frameData = await job.frames.get(frameNumber); const frameData = await job.frames.get(frameNumber);
const states = await job.annotations.get(frameNumber, false, filters); const states = await job.annotations
.get(frameNumber, showAllInterpolationTracks, filters);
const [minZ, maxZ] = computeZRange(states); const [minZ, maxZ] = computeZRange(states);
const colors = [...cvat.enums.colors]; const colors = [...cvat.enums.colors];
@ -845,8 +881,9 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}, },
}); });
} catch (error) { } catch (error) {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
dispatch({ dispatch({
type: AnnotationActionTypes.UPDATE_ANNOTATIONS_FAILED, type: AnnotationActionTypes.UPDATE_ANNOTATIONS_FAILED,
payload: { payload: {
@ -862,9 +899,10 @@ export function createAnnotationsAsync(sessionInstance: any, frame: number, stat
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
await sessionInstance.annotations.put(statesToCreate); await sessionInstance.annotations.put(statesToCreate);
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -889,9 +927,10 @@ export function mergeAnnotationsAsync(sessionInstance: any, frame: number, state
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
await sessionInstance.annotations.merge(statesToMerge); await sessionInstance.annotations.merge(statesToMerge);
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -916,9 +955,10 @@ export function groupAnnotationsAsync(sessionInstance: any, frame: number, state
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
await sessionInstance.annotations.group(statesToGroup); await sessionInstance.annotations.group(statesToGroup);
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -942,10 +982,11 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
try { try {
await sessionInstance.annotations.split(stateToSplit, frame); await sessionInstance.annotations.split(stateToSplit, frame);
const states = await sessionInstance.annotations.get(frame, false, filters); const states = await sessionInstance.annotations
.get(frame, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({
@ -974,10 +1015,11 @@ export function changeLabelColorAsync(
): ThunkAction<Promise<void>, {}, {}, AnyAction> { ): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
const updatedLabel = label; const updatedLabel = label;
updatedLabel.color = color; updatedLabel.color = color;
const states = await sessionInstance.annotations.get(frameNumber, false, filters); const states = await sessionInstance.annotations
.get(frameNumber, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get(); const history = await sessionInstance.actions.get();
dispatch({ dispatch({

@ -14,6 +14,16 @@ export enum SettingsActionTypes {
CHANGE_SELECTED_SHAPES_OPACITY = 'CHANGE_SELECTED_SHAPES_OPACITY', CHANGE_SELECTED_SHAPES_OPACITY = 'CHANGE_SELECTED_SHAPES_OPACITY',
CHANGE_SHAPES_COLOR_BY = 'CHANGE_SHAPES_COLOR_BY', CHANGE_SHAPES_COLOR_BY = 'CHANGE_SHAPES_COLOR_BY',
CHANGE_SHAPES_BLACK_BORDERS = 'CHANGE_SHAPES_BLACK_BORDERS', CHANGE_SHAPES_BLACK_BORDERS = 'CHANGE_SHAPES_BLACK_BORDERS',
CHANGE_FRAME_STEP = 'CHANGE_FRAME_STEP',
CHANGE_FRAME_SPEED = 'CHANGE_FRAME_SPEED',
SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM',
CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL',
CHANGE_CONTRAST_LEVEL = 'CHANGE_CONTRAST_LEVEL',
CHANGE_SATURATION_LEVEL = 'CHANGE_SATURATION_LEVEL',
SWITCH_AUTO_SAVE = 'SWITCH_AUTO_SAVE',
CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL',
CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN',
SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS',
} }
export function changeShapesOpacity(opacity: number): AnyAction { export function changeShapesOpacity(opacity: number): AnyAction {
@ -96,3 +106,93 @@ export function changeGridOpacity(gridOpacity: number): AnyAction {
}, },
}; };
} }
export function changeFrameStep(frameStep: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_FRAME_STEP,
payload: {
frameStep,
},
};
}
export function changeFrameSpeed(frameSpeed: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_FRAME_SPEED,
payload: {
frameSpeed,
},
};
}
export function switchResetZoom(resetZoom: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_RESET_ZOOM,
payload: {
resetZoom,
},
};
}
export function changeBrightnessLevel(level: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL,
payload: {
level,
},
};
}
export function changeContrastLevel(level: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_CONTRAST_LEVEL,
payload: {
level,
},
};
}
export function changeSaturationLevel(level: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_SATURATION_LEVEL,
payload: {
level,
},
};
}
export function switchAutoSave(autoSave: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_AUTO_SAVE,
payload: {
autoSave,
},
};
}
export function changeAutoSaveInterval(autoSaveInterval: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_AUTO_SAVE_INTERVAL,
payload: {
autoSaveInterval,
},
};
}
export function changeAAMZoomMargin(aamZoomMargin: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_AAM_ZOOM_MARGIN,
payload: {
aamZoomMargin,
},
};
}
export function switchShowingInterpolatedTracks(showAllInterpolationTracks: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_SHOWNIG_INTERPOLATED_TRACKS,
payload: {
showAllInterpolationTracks,
},
};
}

@ -17,6 +17,7 @@ interface Props {
getJob(): void; getJob(): void;
} }
export default function AnnotationPageComponent(props: Props): JSX.Element { export default function AnnotationPageComponent(props: Props): JSX.Element {
const { const {
job, job,
@ -24,6 +25,7 @@ export default function AnnotationPageComponent(props: Props): JSX.Element {
getJob, getJob,
} = props; } = props;
if (job === null) { if (job === null) {
if (!fetching) { if (!fetching) {
getJob(); getJob();

@ -47,6 +47,10 @@ interface Props {
curZLayer: number; curZLayer: number;
minZLayer: number; minZLayer: number;
maxZLayer: number; maxZLayer: number;
brightnessLevel: number;
contrastLevel: number;
saturationLevel: number;
resetZoom: boolean;
onSetupCanvas: () => void; onSetupCanvas: () => void;
onDragCanvas: (enabled: boolean) => void; onDragCanvas: (enabled: boolean) => void;
onZoomCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void;
@ -102,6 +106,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
sidebarCollapsed, sidebarCollapsed,
activatedStateID, activatedStateID,
curZLayer, curZLayer,
resetZoom,
} = this.props; } = this.props;
if (prevProps.sidebarCollapsed !== sidebarCollapsed) { if (prevProps.sidebarCollapsed !== sidebarCollapsed) {
@ -156,6 +161,12 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
this.updateShapesView(); this.updateShapesView();
} }
if (prevProps.frame !== frameData.number && resetZoom) {
canvasInstance.html().addEventListener('canvas.setup', () => {
canvasInstance.fit();
}, { once: true });
}
if (prevProps.curZLayer !== curZLayer) { if (prevProps.curZLayer !== curZLayer) {
canvasInstance.setZLayer(curZLayer); canvasInstance.setZLayer(curZLayer);
} }
@ -339,6 +350,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
onActivateObject, onActivateObject,
onUpdateContextMenu, onUpdateContextMenu,
onEditShape, onEditShape,
brightnessLevel,
contrastLevel,
saturationLevel,
} = this.props; } = this.props;
// Size // Size
@ -357,6 +371,12 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
canvasInstance.grid(gridSize, gridSize); canvasInstance.grid(gridSize, gridSize);
// Filters
const backgroundElement = window.document.getElementById('cvat_canvas_background');
if (backgroundElement) {
backgroundElement.style.filter = `brightness(${brightnessLevel / 100}) contrast(${contrastLevel / 100}) saturate(${saturationLevel / 100})`;
}
// Events // Events
canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => { canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => {
const { const {

@ -13,7 +13,7 @@ import {
notification, notification,
} from 'antd'; } from 'antd';
import SettingsPageComponent from 'components/settings-page/settings-page'; import SettingsPageContainer from 'containers/settings-page/settings-page';
import TasksPageContainer from 'containers/tasks-page/tasks-page'; import TasksPageContainer from 'containers/tasks-page/tasks-page';
import CreateTaskPageContainer from 'containers/create-task-page/create-task-page'; import CreateTaskPageContainer from 'containers/create-task-page/create-task-page';
import TaskPageContainer from 'containers/task-page/task-page'; import TaskPageContainer from 'containers/task-page/task-page';
@ -204,7 +204,7 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
<HeaderContainer> </HeaderContainer> <HeaderContainer> </HeaderContainer>
<Layout.Content> <Layout.Content>
<Switch> <Switch>
<Route exact path='/settings' component={SettingsPageComponent} /> <Route exact path='/settings' component={SettingsPageContainer} />
<Route exact path='/tasks' component={TasksPageContainer} /> <Route exact path='/tasks' component={TasksPageContainer} />
<Route exact path='/tasks/create' component={CreateTaskPageContainer} /> <Route exact path='/tasks/create' component={CreateTaskPageContainer} />
<Route exact path='/tasks/:id' component={TaskPageContainer} /> <Route exact path='/tasks/:id' component={TaskPageContainer} />

@ -61,11 +61,17 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
brightnessLevel, brightnessLevel,
contrastLevel, contrastLevel,
saturationLevel, saturationLevel,
onChangeFrameStep,
onChangeFrameSpeed,
onSwitchResetZoom,
onSwitchRotateAll, onSwitchRotateAll,
onSwitchGrid, onSwitchGrid,
onChangeGridSize, onChangeGridSize,
onChangeGridColor, onChangeGridColor,
onChangeGridOpacity, onChangeGridOpacity,
onChangeBrightnessLevel,
onChangeContrastLevel,
onChangeSaturationLevel,
} = props; } = props;
return ( return (
@ -73,7 +79,16 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
<Row type='flex' align='bottom' className='cvat-player-settings-step'> <Row type='flex' align='bottom' className='cvat-player-settings-step'>
<Col> <Col>
<Text className='cvat-text-color'> Player step </Text> <Text className='cvat-text-color'> Player step </Text>
<InputNumber min={2} max={1000} value={frameStep} /> <InputNumber
min={2}
max={1000}
value={frameStep}
onChange={(value: number | undefined): void => {
if (value) {
onChangeFrameStep(value);
}
}}
/>
</Col> </Col>
<Col offset={1}> <Col offset={1}>
<Text type='secondary'> <Text type='secondary'>
@ -87,7 +102,12 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
<Row type='flex' align='middle' className='cvat-player-settings-speed'> <Row type='flex' align='middle' className='cvat-player-settings-speed'>
<Col> <Col>
<Text className='cvat-text-color'> Player speed </Text> <Text className='cvat-text-color'> Player speed </Text>
<Select value={frameSpeed}> <Select
value={frameSpeed}
onChange={(speed: FrameSpeed): void => {
onChangeFrameSpeed(speed);
}}
>
<Select.Option key='fastest' value={FrameSpeed.Fastest}>Fastest</Select.Option> <Select.Option key='fastest' value={FrameSpeed.Fastest}>Fastest</Select.Option>
<Select.Option key='fast' value={FrameSpeed.Fast}>Fast</Select.Option> <Select.Option key='fast' value={FrameSpeed.Fast}>Fast</Select.Option>
<Select.Option key='usual' value={FrameSpeed.Usual}>Usual</Select.Option> <Select.Option key='usual' value={FrameSpeed.Usual}>Usual</Select.Option>
@ -118,6 +138,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
max={1000} max={1000}
step={1} step={1}
value={gridSize} value={gridSize}
disabled={!grid}
onChange={(value: number | undefined): void => { onChange={(value: number | undefined): void => {
if (value) { if (value) {
onChangeGridSize(value); onChangeGridSize(value);
@ -129,6 +150,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
<Text className='cvat-text-color'> Grid color </Text> <Text className='cvat-text-color'> Grid color </Text>
<Select <Select
value={gridColor} value={gridColor}
disabled={!grid}
onChange={(color: GridColor): void => { onChange={(color: GridColor): void => {
onChangeGridColor(color); onChangeGridColor(color);
}} }}
@ -146,6 +168,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
min={0} min={0}
max={100} max={100}
value={gridOpacity} value={gridOpacity}
disabled={!grid}
onChange={(value: number | [number, number]): void => { onChange={(value: number | [number, number]): void => {
onChangeGridOpacity(value as number); onChangeGridOpacity(value as number);
}} }}
@ -160,6 +183,9 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
<Checkbox <Checkbox
className='cvat-text-color' className='cvat-text-color'
checked={resetZoom} checked={resetZoom}
onChange={(event: CheckboxChangeEvent): void => {
onSwitchResetZoom(event.target.checked);
}}
> >
Reset zoom Reset zoom
</Checkbox> </Checkbox>
@ -193,7 +219,14 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
Brightness Brightness
</Col> </Col>
<Col> <Col>
<Slider min={0} max={100} value={brightnessLevel} /> <Slider
min={50}
max={200}
value={brightnessLevel}
onChange={(value: number | [number, number]): void => {
onChangeBrightnessLevel(value as number);
}}
/>
</Col> </Col>
</Row> </Row>
<Row className='cvat-player-settings-contrast'> <Row className='cvat-player-settings-contrast'>
@ -201,7 +234,14 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
Contrast Contrast
</Col> </Col>
<Col> <Col>
<Slider min={0} max={100} value={contrastLevel} /> <Slider
min={50}
max={200}
value={contrastLevel}
onChange={(value: number | [number, number]): void => {
onChangeContrastLevel(value as number);
}}
/>
</Col> </Col>
</Row> </Row>
<Row className='cvat-player-settings-saturation'> <Row className='cvat-player-settings-saturation'>
@ -209,7 +249,14 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
Saturation Saturation
</Col> </Col>
<Col> <Col>
<Slider min={0} max={100} value={saturationLevel} /> <Slider
min={0}
max={300}
value={saturationLevel}
onChange={(value: number | [number, number]): void => {
onChangeSaturationLevel(value as number);
}}
/>
</Col> </Col>
</Row> </Row>
</div> </div>

@ -11,7 +11,6 @@ import {
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import WorkspaceSettingsContainer from 'containers/settings-page/workspace-settings'; import WorkspaceSettingsContainer from 'containers/settings-page/workspace-settings';
import PlayerSettingsContainer from 'containers/settings-page/player-settings'; import PlayerSettingsContainer from 'containers/settings-page/player-settings';
@ -76,4 +75,4 @@ function SettingsPage(props: RouteComponentProps): JSX.Element {
); );
} }
export default withRouter(SettingsPage); export default SettingsPage;

@ -8,6 +8,7 @@ import {
} from 'antd'; } from 'antd';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
interface Props { interface Props {
autoSave: boolean; autoSave: boolean;
@ -26,6 +27,10 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
autoSaveInterval, autoSaveInterval,
aamZoomMargin, aamZoomMargin,
showAllInterpolationTracks, showAllInterpolationTracks,
onSwitchAutoSave,
onChangeAutoSaveInterval,
onChangeAAMZoomMargin,
onSwitchShowingInterpolatedTracks,
} = props; } = props;
return ( return (
@ -35,6 +40,9 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
<Checkbox <Checkbox
className='cvat-text-color cvat-workspace-settings-auto-save' className='cvat-text-color cvat-workspace-settings-auto-save'
checked={autoSave} checked={autoSave}
onChange={(event: CheckboxChangeEvent): void => {
onSwitchAutoSave(event.target.checked);
}}
> >
Enable auto save Enable auto save
</Checkbox> </Checkbox>
@ -48,6 +56,11 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
max={60} max={60}
step={1} step={1}
value={Math.round(autoSaveInterval / (60 * 1000))} value={Math.round(autoSaveInterval / (60 * 1000))}
onChange={(value: number | undefined): void => {
if (value) {
onChangeAutoSaveInterval(value * 60 * 1000);
}
}}
/> />
<Text type='secondary'> minutes </Text> <Text type='secondary'> minutes </Text>
</Col> </Col>
@ -57,6 +70,9 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
<Checkbox <Checkbox
className='cvat-text-color' className='cvat-text-color'
checked={showAllInterpolationTracks} checked={showAllInterpolationTracks}
onChange={(event: CheckboxChangeEvent): void => {
onSwitchShowingInterpolatedTracks(event.target.checked);
}}
> >
Show all interpolation tracks Show all interpolation tracks
</Checkbox> </Checkbox>
@ -68,7 +84,16 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
<Row className='cvat-workspace-settings-aam-zoom-margin'> <Row className='cvat-workspace-settings-aam-zoom-margin'>
<Col> <Col>
<Text className='cvat-text-color'> Attribute annotation mode (AAM) zoom margin </Text> <Text className='cvat-text-color'> Attribute annotation mode (AAM) zoom margin </Text>
<InputNumber min={0} max={1000} value={aamZoomMargin} /> <InputNumber
min={0}
max={1000}
value={aamZoomMargin}
onChange={(value: number | undefined): void => {
if (value) {
onChangeAAMZoomMargin(value);
}
}}
/>
</Col> </Col>
</Row> </Row>
</div> </div>

@ -1,5 +1,8 @@
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import { import {
Row, Row,
Col, Col,
@ -27,19 +30,40 @@ interface Props {
onJobUpdate(jobInstance: any): void; onJobUpdate(jobInstance: any): void;
} }
export default function JobListComponent(props: Props): JSX.Element { function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
const { const {
taskInstance, taskInstance,
registeredUsers, registeredUsers,
onJobUpdate, onJobUpdate,
history: {
push,
},
} = props; } = props;
const { jobs } = taskInstance; const { jobs, id: taskId } = taskInstance;
const columns = [{ const columns = [{
title: 'Job', title: 'Job',
dataIndex: 'job', dataIndex: 'job',
key: 'job', key: 'job',
render: (id: number): JSX.Element => (<a href={`${baseURL}/?id=${id}`}>{ `Job #${id}` }</a>), render: (id: number): JSX.Element => (
<div>
<Button type='link' href={`${baseURL}/?id=${id}`}>{`Job #${id}`}</Button>
|
<Tooltip title='Beta version of new UI written in React. It is to get
acquainted only, we do not recommend use it to annotations
process because it is lack of some features and can be unstable.'
>
<Button
type='link'
onClick={(): void => {
push(`/tasks/${taskId}/jobs/${id}`);
}}
>
Try new UI
</Button>
</Tooltip>
</div>
),
}, { }, {
title: 'Frames', title: 'Frames',
dataIndex: 'frames', dataIndex: 'frames',
@ -168,3 +192,5 @@ export default function JobListComponent(props: Props): JSX.Element {
</div> </div>
); );
} }
export default withRouter(JobListComponent);

@ -24,7 +24,9 @@ interface DispatchToProps {
getJob(): void; getJob(): void;
} }
function mapStateToProps(state: CombinedState): StateToProps { function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const { params } = own.match;
const jobID = +params.jid;
const { const {
annotation: { annotation: {
job: { job: {
@ -35,7 +37,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
} = state; } = state;
return { return {
job, job: !job || jobID === job.id ? job : null,
fetching, fetching,
}; };
} }

@ -52,6 +52,10 @@ interface StateToProps {
gridOpacity: number; gridOpacity: number;
activeLabelID: number; activeLabelID: number;
activeObjectType: ObjectType; activeObjectType: ObjectType;
brightnessLevel: number;
contrastLevel: number;
saturationLevel: number;
resetZoom: boolean;
minZLayer: number; minZLayer: number;
maxZLayer: number; maxZLayer: number;
curZLayer: number; curZLayer: number;
@ -116,6 +120,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
gridSize, gridSize,
gridColor, gridColor,
gridOpacity, gridOpacity,
brightnessLevel,
contrastLevel,
saturationLevel,
resetZoom,
}, },
shapes: { shapes: {
opacity, opacity,
@ -145,6 +153,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
gridOpacity, gridOpacity,
activeLabelID, activeLabelID,
activeObjectType, activeObjectType,
brightnessLevel,
contrastLevel,
saturationLevel,
resetZoom,
curZLayer, curZLayer,
minZLayer, minZLayer,
maxZLayer, maxZLayer,

@ -2,6 +2,9 @@ import React from 'react';
import copy from 'copy-to-clipboard'; import copy from 'copy-to-clipboard';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { RouteComponentProps } from 'react-router-dom';
import { SliderValue } from 'antd/lib/slider'; import { SliderValue } from 'antd/lib/slider';
import { import {
@ -15,18 +18,22 @@ import {
} from 'actions/annotation-actions'; } from 'actions/annotation-actions';
import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar';
import { CombinedState } from 'reducers/interfaces'; import { CombinedState, FrameSpeed } from 'reducers/interfaces';
interface StateToProps { interface StateToProps {
jobInstance: any; jobInstance: any;
frameNumber: number; frameNumber: number;
frameStep: number; frameStep: number;
frameSpeed: FrameSpeed;
frameDelay: number;
playing: boolean; playing: boolean;
saving: boolean; saving: boolean;
canvasIsReady: boolean; canvasIsReady: boolean;
savingStatuses: string[]; savingStatuses: string[];
undoAction?: string; undoAction?: string;
redoAction?: string; redoAction?: string;
autoSave: boolean;
autoSaveInterval: number;
} }
interface DispatchToProps { interface DispatchToProps {
@ -45,6 +52,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
playing, playing,
frame: { frame: {
number: frameNumber, number: frameNumber,
delay: frameDelay,
}, },
}, },
annotations: { annotations: {
@ -63,13 +71,20 @@ function mapStateToProps(state: CombinedState): StateToProps {
}, },
settings: { settings: {
player: { player: {
frameSpeed,
frameStep, frameStep,
}, },
workspace: {
autoSave,
autoSaveInterval,
},
}, },
} = state; } = state;
return { return {
frameStep, frameStep,
frameSpeed,
frameDelay,
playing, playing,
canvasIsReady, canvasIsReady,
saving, saving,
@ -78,6 +93,8 @@ function mapStateToProps(state: CombinedState): StateToProps {
jobInstance, jobInstance,
undoAction: history.undo[history.undo.length - 1], undoAction: history.undo[history.undo.length - 1],
redoAction: history.redo[history.redo.length - 1], redoAction: history.redo[history.redo.length - 1],
autoSave,
autoSaveInterval,
}; };
} }
@ -105,32 +122,80 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
}; };
} }
type Props = StateToProps & DispatchToProps; type Props = StateToProps & DispatchToProps & RouteComponentProps;
class AnnotationTopBarContainer extends React.PureComponent<Props> { class AnnotationTopBarContainer extends React.PureComponent<Props> {
private autoSaveInterval: number | undefined;
private unblock: any;
public componentDidMount(): void {
const {
autoSave,
autoSaveInterval,
saving,
history,
jobInstance,
} = this.props;
this.autoSaveInterval = window.setInterval((): void => {
if (autoSave && !saving) {
this.onSaveAnnotation();
}
}, autoSaveInterval);
this.unblock = history.block((location: any) => {
if (jobInstance.annotations.hasUnsavedChanges() && location.pathname !== '/settings'
&& location.pathname !== `/tasks/${jobInstance.task.id}/jobs/${jobInstance.id}`) {
return 'You have unsaved changes, please confirm leaving this page.';
}
return undefined;
});
this.beforeUnloadCallback = this.beforeUnloadCallback.bind(this);
window.addEventListener('beforeunload', this.beforeUnloadCallback);
}
public componentDidUpdate(): void { public componentDidUpdate(): void {
const { const {
jobInstance, jobInstance,
frameSpeed,
frameNumber, frameNumber,
frameDelay,
playing, playing,
canvasIsReady, canvasIsReady,
onChangeFrame,
onSwitchPlay, onSwitchPlay,
onChangeFrame,
} = this.props; } = this.props;
if (playing && canvasIsReady) { if (playing && canvasIsReady) {
if (frameNumber < jobInstance.stopFrame) { if (frameNumber < jobInstance.stopFrame) {
let framesSkiped = 0;
if (frameSpeed === FrameSpeed.Fast
&& (frameNumber + 1 < jobInstance.stopFrame)) {
framesSkiped = 1;
}
if (frameSpeed === FrameSpeed.Fastest
&& (frameNumber + 2 < jobInstance.stopFrame)) {
framesSkiped = 2;
}
setTimeout(() => { setTimeout(() => {
const { playing: stillPlaying } = this.props; const { playing: stillPlaying } = this.props;
if (stillPlaying) { if (stillPlaying) {
onChangeFrame(frameNumber + 1); onChangeFrame(frameNumber + 1 + framesSkiped);
} }
}); }, frameDelay);
} else { } else {
onSwitchPlay(false); onSwitchPlay(false);
} }
} }
} }
public componentWillUnmount(): void {
window.clearInterval(this.autoSaveInterval);
window.removeEventListener('beforeunload', this.beforeUnloadCallback);
this.unblock();
}
private undo = (): void => { private undo = (): void => {
const { const {
undo, undo,
@ -177,11 +242,11 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
private onFirstFrame = (): void => { private onFirstFrame = (): void => {
const { const {
onChangeFrame,
frameNumber, frameNumber,
jobInstance, jobInstance,
playing, playing,
onSwitchPlay, onSwitchPlay,
onChangeFrame,
} = this.props; } = this.props;
const newFrame = jobInstance.startFrame; const newFrame = jobInstance.startFrame;
@ -195,12 +260,12 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
private onBackward = (): void => { private onBackward = (): void => {
const { const {
onChangeFrame,
frameNumber, frameNumber,
frameStep, frameStep,
jobInstance, jobInstance,
playing, playing,
onSwitchPlay, onSwitchPlay,
onChangeFrame,
} = this.props; } = this.props;
const newFrame = Math const newFrame = Math
@ -215,11 +280,11 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
private onPrevFrame = (): void => { private onPrevFrame = (): void => {
const { const {
onChangeFrame,
frameNumber, frameNumber,
jobInstance, jobInstance,
playing, playing,
onSwitchPlay, onSwitchPlay,
onChangeFrame,
} = this.props; } = this.props;
const newFrame = Math const newFrame = Math
@ -234,11 +299,11 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
private onNextFrame = (): void => { private onNextFrame = (): void => {
const { const {
onChangeFrame,
frameNumber, frameNumber,
jobInstance, jobInstance,
playing, playing,
onSwitchPlay, onSwitchPlay,
onChangeFrame,
} = this.props; } = this.props;
const newFrame = Math const newFrame = Math
@ -253,12 +318,12 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
private onForward = (): void => { private onForward = (): void => {
const { const {
onChangeFrame,
frameNumber, frameNumber,
frameStep, frameStep,
jobInstance, jobInstance,
playing, playing,
onSwitchPlay, onSwitchPlay,
onChangeFrame,
} = this.props; } = this.props;
const newFrame = Math const newFrame = Math
@ -273,11 +338,11 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
private onLastFrame = (): void => { private onLastFrame = (): void => {
const { const {
onChangeFrame,
frameNumber, frameNumber,
jobInstance, jobInstance,
playing, playing,
onSwitchPlay, onSwitchPlay,
onChangeFrame,
} = this.props; } = this.props;
const newFrame = jobInstance.stopFrame; const newFrame = jobInstance.stopFrame;
@ -314,8 +379,8 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
private onChangePlayerInputValue = (value: number | undefined): void => { private onChangePlayerInputValue = (value: number | undefined): void => {
const { const {
onSwitchPlay, onSwitchPlay,
playing,
onChangeFrame, onChangeFrame,
playing,
} = this.props; } = this.props;
if (typeof (value) !== 'undefined') { if (typeof (value) !== 'undefined') {
@ -336,6 +401,17 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
copy(url); copy(url);
}; };
private beforeUnloadCallback(event: BeforeUnloadEvent): any {
const { jobInstance } = this.props;
if (jobInstance.annotations.hasUnsavedChanges()) {
const confirmationMessage = 'You have unsaved changes, please confirm leaving this page.';
// eslint-disable-next-line no-param-reassign
event.returnValue = confirmationMessage;
return confirmationMessage;
}
return undefined;
}
public render(): JSX.Element { public render(): JSX.Element {
const { const {
playing, playing,
@ -379,7 +455,9 @@ class AnnotationTopBarContainer extends React.PureComponent<Props> {
} }
} }
export default connect( export default withRouter(
mapStateToProps, connect(
mapDispatchToProps, mapStateToProps,
)(AnnotationTopBarContainer); mapDispatchToProps,
)(AnnotationTopBarContainer),
);

@ -4,11 +4,17 @@ import { connect } from 'react-redux';
import PlayerSettingsComponent from 'components/settings-page/player-settings'; import PlayerSettingsComponent from 'components/settings-page/player-settings';
import { import {
changeFrameStep,
changeFrameSpeed,
switchResetZoom,
switchRotateAll, switchRotateAll,
switchGrid, switchGrid,
changeGridSize, changeGridSize,
changeGridColor, changeGridColor,
changeGridOpacity, changeGridOpacity,
changeBrightnessLevel,
changeContrastLevel,
changeSaturationLevel,
} from 'actions/settings-actions'; } from 'actions/settings-actions';
import { import {
@ -54,20 +60,14 @@ function mapStateToProps(state: CombinedState): StateToProps {
function mapDispatchToProps(dispatch: any): DispatchToProps { function mapDispatchToProps(dispatch: any): DispatchToProps {
return { return {
// will be implemented
// eslint-disable-next-line
onChangeFrameStep(step: number): void { onChangeFrameStep(step: number): void {
dispatch(changeFrameStep(step));
}, },
// will be implemented
// eslint-disable-next-line
onChangeFrameSpeed(speed: FrameSpeed): void { onChangeFrameSpeed(speed: FrameSpeed): void {
dispatch(changeFrameSpeed(speed));
}, },
// will be implemented
// eslint-disable-next-line
onSwitchResetZoom(enabled: boolean): void { onSwitchResetZoom(enabled: boolean): void {
dispatch(switchResetZoom(enabled));
}, },
onSwitchRotateAll(rotateAll: boolean): void { onSwitchRotateAll(rotateAll: boolean): void {
dispatch(switchRotateAll(rotateAll)); dispatch(switchRotateAll(rotateAll));
@ -84,20 +84,14 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
onChangeGridOpacity(gridOpacity: number): void { onChangeGridOpacity(gridOpacity: number): void {
dispatch(changeGridOpacity(gridOpacity)); dispatch(changeGridOpacity(gridOpacity));
}, },
// will be implemented
// eslint-disable-next-line
onChangeBrightnessLevel(level: number): void { onChangeBrightnessLevel(level: number): void {
dispatch(changeBrightnessLevel(level));
}, },
// will be implemented
// eslint-disable-next-line
onChangeContrastLevel(level: number): void { onChangeContrastLevel(level: number): void {
dispatch(changeContrastLevel(level));
}, },
// will be implemented
// eslint-disable-next-line
onChangeSaturationLevel(level: number): void { onChangeSaturationLevel(level: number): void {
dispatch(changeSaturationLevel(level));
}, },
}; };
} }

@ -0,0 +1,79 @@
import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import SettingsPageComponent from 'components/settings-page/settings-page';
import {
CombinedState,
} from 'reducers/interfaces';
interface StateToProps {
jobInstance: any;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
job: {
instance: jobInstance,
},
},
} = state;
return {
jobInstance,
};
}
type Props = StateToProps & RouteComponentProps;
class SettingsPageContainer extends React.PureComponent<Props> {
private unblock: any;
public componentDidMount(): void {
const {
history,
jobInstance,
} = this.props;
this.unblock = history.block((location: any) => {
if (jobInstance && jobInstance.annotations.hasUnsavedChanges()
&& location.pathname !== `/tasks/${jobInstance.task.id}/jobs/${jobInstance.id}`) {
return 'You have unsaved changes, please confirm leaving this page.';
}
return undefined;
});
this.beforeUnloadCallback = this.beforeUnloadCallback.bind(this);
window.addEventListener('beforeunload', this.beforeUnloadCallback);
}
public componentWillUnmount(): void {
window.removeEventListener('beforeunload', this.beforeUnloadCallback);
this.unblock();
}
private beforeUnloadCallback(event: BeforeUnloadEvent): any {
const { jobInstance } = this.props;
if (jobInstance.annotations.hasUnsavedChanges()) {
const confirmationMessage = 'You have unsaved changes, please confirm leaving this page.';
// eslint-disable-next-line no-param-reassign
event.returnValue = confirmationMessage;
return confirmationMessage;
}
return undefined;
}
public render(): JSX.Element {
return (
<SettingsPageComponent {...this.props} />
);
}
}
export default withRouter(
connect(
mapStateToProps,
)(SettingsPageContainer),
);

@ -1,6 +1,13 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import {
switchAutoSave,
changeAutoSaveInterval,
changeAAMZoomMargin,
switchShowingInterpolatedTracks,
} from 'actions/settings-actions';
import { import {
CombinedState, CombinedState,
} from 'reducers/interfaces'; } from 'reducers/interfaces';
@ -38,27 +45,19 @@ function mapStateToProps(state: CombinedState): StateToProps {
}; };
} }
function mapDispatchToProps(): DispatchToProps { function mapDispatchToProps(dispatch: any): DispatchToProps {
return { return {
// will be implemented
// eslint-disable-next-line
onSwitchAutoSave(enabled: boolean): void { onSwitchAutoSave(enabled: boolean): void {
dispatch(switchAutoSave(enabled));
}, },
// will be implemented
// eslint-disable-next-line
onChangeAutoSaveInterval(interval: number): void { onChangeAutoSaveInterval(interval: number): void {
dispatch(changeAutoSaveInterval(interval));
}, },
// will be implemented
// eslint-disable-next-line
onChangeAAMZoomMargin(margin: number): void { onChangeAAMZoomMargin(margin: number): void {
dispatch(changeAAMZoomMargin(margin));
}, },
// will be implemented
// eslint-disable-next-line
onSwitchShowingInterpolatedTracks(enabled: boolean): void { onSwitchShowingInterpolatedTracks(enabled: boolean): void {
dispatch(switchShowingInterpolatedTracks(enabled));
}, },
}; };
} }

@ -36,6 +36,8 @@ const defaultState: AnnotationState = {
number: 0, number: 0,
data: null, data: null,
fetching: false, fetching: false,
delay: 0,
changeTime: null,
}, },
playing: false, playing: false,
}, },
@ -151,6 +153,15 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
}, },
}; };
} }
case AnnotationActionTypes.CLOSE_JOB: {
return {
...defaultState,
canvas: {
...defaultState.canvas,
instance: new Canvas(),
},
};
}
case AnnotationActionTypes.CHANGE_FRAME: { case AnnotationActionTypes.CHANGE_FRAME: {
return { return {
...state, ...state,
@ -174,6 +185,8 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
states, states,
minZ, minZ,
maxZ, maxZ,
delay,
changeTime,
} = action.payload; } = action.payload;
const activatedStateID = states const activatedStateID = states
@ -188,6 +201,8 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
data, data,
number, number,
fetching: false, fetching: false,
changeTime,
delay,
}, },
}, },
annotations: { annotations: {

@ -305,6 +305,8 @@ export interface AnnotationState {
number: number; number: number;
data: any | null; data: any | null;
fetching: boolean; fetching: boolean;
delay: number;
changeTime: number | null;
}; };
playing: boolean; playing: boolean;
}; };

@ -30,9 +30,9 @@ const defaultState: SettingsState = {
gridSize: 100, gridSize: 100,
gridColor: GridColor.White, gridColor: GridColor.White,
gridOpacity: 0, gridOpacity: 0,
brightnessLevel: 50, brightnessLevel: 100,
contrastLevel: 50, contrastLevel: 100,
saturationLevel: 50, saturationLevel: 100,
}, },
}; };
@ -119,6 +119,96 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
}, },
}; };
} }
case SettingsActionTypes.CHANGE_FRAME_STEP: {
return {
...state,
player: {
...state.player,
frameStep: action.payload.frameStep,
},
};
}
case SettingsActionTypes.CHANGE_FRAME_SPEED: {
return {
...state,
player: {
...state.player,
frameSpeed: action.payload.frameSpeed,
},
};
}
case SettingsActionTypes.SWITCH_RESET_ZOOM: {
return {
...state,
player: {
...state.player,
resetZoom: action.payload.resetZoom,
},
};
}
case SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL: {
return {
...state,
player: {
...state.player,
brightnessLevel: action.payload.level,
},
};
}
case SettingsActionTypes.CHANGE_CONTRAST_LEVEL: {
return {
...state,
player: {
...state.player,
contrastLevel: action.payload.level,
},
};
}
case SettingsActionTypes.CHANGE_SATURATION_LEVEL: {
return {
...state,
player: {
...state.player,
saturationLevel: action.payload.level,
},
};
}
case SettingsActionTypes.SWITCH_AUTO_SAVE: {
return {
...state,
workspace: {
...state.workspace,
autoSave: action.payload.autoSave,
},
};
}
case SettingsActionTypes.CHANGE_AUTO_SAVE_INTERVAL: {
return {
...state,
workspace: {
...state.workspace,
autoSaveInterval: action.payload.autoSaveInterval,
},
};
}
case SettingsActionTypes.CHANGE_AAM_ZOOM_MARGIN: {
return {
...state,
workspace: {
...state.workspace,
aamZoomMargin: action.payload.aamZoomMargin,
},
};
}
case SettingsActionTypes.SWITCH_SHOWNIG_INTERPOLATED_TRACKS: {
return {
...state,
workspace: {
...state.workspace,
showAllInterpolationTracks: action.payload.showAllInterpolationTracks,
},
};
}
default: { default: {
return state; return state;
} }

Loading…
Cancel
Save