Skip to content

Commit

Permalink
#3817 - Support undo/redo for document metadata annotations
Browse files Browse the repository at this point in the history
- Refactor undo support into an extension point
  • Loading branch information
reckart committed Feb 15, 2023
1 parent 306b938 commit 1143096
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;

Expand All @@ -43,26 +41,11 @@
import de.tudarmstadt.ukp.clarin.webanno.support.lambda.LambdaAjaxLink;
import de.tudarmstadt.ukp.clarin.webanno.support.logging.LogMessage;
import de.tudarmstadt.ukp.clarin.webanno.support.wicket.input.InputBehavior;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.CreateChainLinkAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.CreateChainSpanAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.CreateRelationAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.CreateSpanAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.DeleteRelationAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.DeleteSpanAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.MoveSpanAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.RedoableAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.UndoableActionSupportRegistry;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.UndoableAnnotationAction;
import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions.UpdateFeatureValueAnnotationAction;
import de.tudarmstadt.ukp.inception.annotation.events.AnnotationEvent;
import de.tudarmstadt.ukp.inception.annotation.events.DocumentOpenedEvent;
import de.tudarmstadt.ukp.inception.annotation.events.FeatureValueUpdatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.chain.ChainLinkCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.chain.ChainSpanCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationDeletedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanDeletedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanMovedEvent;
import de.tudarmstadt.ukp.inception.schema.AnnotationSchemaService;
import de.tudarmstadt.ukp.inception.schema.adapter.AnnotationException;
import wicket.contrib.input.events.key.KeyType;
Expand All @@ -77,39 +60,19 @@ public class UndoPanel
private static final AtomicLong NEXT_REQUEST_ID = new AtomicLong(1);

private @SpringBean AnnotationSchemaService schemaService;

private final Map<Class<? extends AnnotationEvent>, ActionFactory<UndoableAnnotationAction, //
AnnotationEvent>> undoHandlers;
private @SpringBean UndoableActionSupportRegistry undoableActionSupportRegistry;

public UndoPanel(String aId, AnnotationPageBase aPage)
{
super(aId);

undoHandlers = new HashMap<>();

registerHandler(SpanCreatedEvent.class, CreateSpanAnnotationAction::new);
registerHandler(SpanDeletedEvent.class, DeleteSpanAnnotationAction::new);
registerHandler(SpanMovedEvent.class, MoveSpanAnnotationAction::new);
registerHandler(RelationCreatedEvent.class, CreateRelationAnnotationAction::new);
registerHandler(RelationDeletedEvent.class, DeleteRelationAnnotationAction::new);
registerHandler(ChainSpanCreatedEvent.class, CreateChainSpanAnnotationAction::new);
registerHandler(ChainLinkCreatedEvent.class, CreateChainLinkAnnotationAction::new);
registerHandler(FeatureValueUpdatedEvent.class, UpdateFeatureValueAnnotationAction::new);

queue(new LambdaAjaxLink("undo", this::actionUndo)
.add(new InputBehavior(new KeyType[] { Ctrl, KeyType.z }, click)));
queue(new LambdaAjaxLink("redo", this::actionRedo)
.add(new InputBehavior(new KeyType[] { Shift, Ctrl, KeyType.z }, click)));

}

@SuppressWarnings({ "rawtypes", "unchecked" })
private <T extends UndoableAnnotationAction, E extends AnnotationEvent> void registerHandler(
Class<E> aEventClass, ActionFactory<T, E> aHandler)
{
undoHandlers.put(aEventClass, (ActionFactory) aHandler);
}

private UndoRedoState getState()
{
// We keep the state in the page because the undo panel is part of the list view in the
Expand Down Expand Up @@ -255,12 +218,12 @@ public void onAnnotationEvent(AnnotationEvent aEvent)
return;
}

var handler = undoHandlers.get(aEvent.getClass());
var handler = undoableActionSupportRegistry.getExtension(aEvent);

if (handler != null) {
if (handler.isPresent()) {
getState().clearRedoableActions();
long requestId = getRequestId();
getState().pushUndoable(handler.create(requestId, schemaService, aEvent));
getState().pushUndoable(handler.get().actionForEvent(requestId, aEvent));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions;

import org.springframework.context.ApplicationEvent;

import de.tudarmstadt.ukp.inception.annotation.layer.chain.ChainLinkCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.chain.ChainSpanCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.chain.ChainSpanEvent;
import de.tudarmstadt.ukp.inception.schema.AnnotationSchemaService;

public class ChainAnnotationActionUndoSupport
implements UndoableAnnotationActionSupport
{
private final AnnotationSchemaService schemaService;

public ChainAnnotationActionUndoSupport(AnnotationSchemaService aSchemaService)
{
schemaService = aSchemaService;
}

@Override
public boolean accepts(ApplicationEvent aContext)
{
return aContext instanceof ChainSpanEvent;
}

@Override
public UndoableAnnotationAction actionForEvent(long aRequestId, ApplicationEvent aEvent)
{
if (aEvent instanceof ChainSpanCreatedEvent) {
return new CreateChainSpanAnnotationAction(aRequestId, schemaService,
(ChainSpanCreatedEvent) aEvent);
}

if (aEvent instanceof ChainLinkCreatedEvent) {
return new CreateChainLinkAnnotationAction(aRequestId, schemaService,
(ChainLinkCreatedEvent) aEvent);
}

throw new IllegalArgumentException("Not an undoable action: [" + aEvent.getClass() + "]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions;

import org.springframework.context.ApplicationEvent;

import de.tudarmstadt.ukp.inception.annotation.events.FeatureValueUpdatedEvent;
import de.tudarmstadt.ukp.inception.schema.AnnotationSchemaService;

public class FeatureValueActionUndoSupport
implements UndoableAnnotationActionSupport
{
private final AnnotationSchemaService schemaService;

public FeatureValueActionUndoSupport(AnnotationSchemaService aSchemaService)
{
schemaService = aSchemaService;
}

@Override
public boolean accepts(ApplicationEvent aContext)
{
return aContext instanceof FeatureValueUpdatedEvent;
}

@Override
public UndoableAnnotationAction actionForEvent(long aRequestId, ApplicationEvent aEvent)
{
if (aEvent instanceof FeatureValueUpdatedEvent) {
return new UpdateFeatureValueAnnotationAction(aRequestId, schemaService,
(FeatureValueUpdatedEvent) aEvent);
}

throw new IllegalArgumentException("Not an undoable action: [" + aEvent.getClass() + "]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions;

import org.springframework.context.ApplicationEvent;

import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationDeletedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationEvent;
import de.tudarmstadt.ukp.inception.schema.AnnotationSchemaService;

public class RelationAnnotationActionUndoSupport
implements UndoableAnnotationActionSupport
{
private final AnnotationSchemaService schemaService;

public RelationAnnotationActionUndoSupport(AnnotationSchemaService aSchemaService)
{
schemaService = aSchemaService;
}

@Override
public boolean accepts(ApplicationEvent aContext)
{
return aContext instanceof RelationEvent;
}

@Override
public UndoableAnnotationAction actionForEvent(long aRequestId, ApplicationEvent aEvent)
{
if (aEvent instanceof RelationCreatedEvent) {
return new CreateRelationAnnotationAction(aRequestId, schemaService,
(RelationCreatedEvent) aEvent);
}

if (aEvent instanceof RelationDeletedEvent) {
return new DeleteRelationAnnotationAction(aRequestId, schemaService,
(RelationDeletedEvent) aEvent);
}

throw new IllegalArgumentException("Not an undoable action: [" + aEvent.getClass() + "]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions;

import org.springframework.context.ApplicationEvent;

import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanCreatedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanDeletedEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanEvent;
import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanMovedEvent;
import de.tudarmstadt.ukp.inception.schema.AnnotationSchemaService;

public class SpanAnnotationActionUndoSupport
implements UndoableAnnotationActionSupport
{
private final AnnotationSchemaService schemaService;

public SpanAnnotationActionUndoSupport(AnnotationSchemaService aSchemaService)
{
schemaService = aSchemaService;
}

@Override
public boolean accepts(ApplicationEvent aContext)
{
return aContext instanceof SpanEvent;
}

@Override
public UndoableAnnotationAction actionForEvent(long aRequestId, ApplicationEvent aEvent)
{
if (aEvent instanceof SpanCreatedEvent) {
return new CreateSpanAnnotationAction(aRequestId, schemaService,
(SpanCreatedEvent) aEvent);
}

if (aEvent instanceof SpanDeletedEvent) {
return new DeleteSpanAnnotationAction(aRequestId, schemaService,
(SpanDeletedEvent) aEvent);
}

if (aEvent instanceof SpanMovedEvent) {
return new MoveSpanAnnotationAction(aRequestId, schemaService, (SpanMovedEvent) aEvent);
}

throw new IllegalArgumentException("Not an undoable action: [" + aEvent.getClass() + "]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Licensed to the Technische Universität Darmstadt under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The Technische Universität Darmstadt
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.undo.actions;

import java.util.Optional;

import org.springframework.context.ApplicationEvent;

import de.tudarmstadt.ukp.clarin.webanno.support.extensionpoint.ExtensionPoint;

public interface UndoableActionSupportRegistry
extends ExtensionPoint<ApplicationEvent, UndoableAnnotationActionSupport>
{
default Optional<UndoableAnnotationActionSupport> getExtension(ApplicationEvent aEvent)
{
return getExtensions(aEvent).stream().findFirst();
}
}
Loading

0 comments on commit 1143096

Please sign in to comment.