// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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 com.google.collide.client.workspace;
import com.google.collide.client.AppContext;
import com.google.collide.client.code.CodePanelBundle;
import com.google.collide.client.code.NavigationAreaExpansionEvent;
import com.google.collide.client.code.ParticipantModel;
import com.google.collide.client.collaboration.CollaborationManager;
import com.google.collide.client.collaboration.DocOpsSavedNotifier;
import com.google.collide.client.collaboration.IncomingDocOpDemultiplexer;
import com.google.collide.client.communication.FrontendApi.ApiCallback;
import com.google.collide.client.document.DocumentManager;
import com.google.collide.client.history.PlaceNavigationHandler;
import com.google.collide.client.search.FileNameSearch;
import com.google.collide.client.search.TreeWalkFileNameSearchImpl;
import com.google.collide.client.util.Elements;
import com.google.collide.dto.GetWorkspaceMetaDataResponse;
import com.google.collide.dto.ServerError.FailureReason;
import com.google.collide.dto.client.DtoClientImpls.GetWorkspaceMetaDataImpl;
import com.google.collide.shared.util.ListenerRegistrar.Remover;
import com.google.collide.shared.util.ListenerRegistrar.RemoverManager;
import com.google.gwt.core.client.Scheduler;
import elemental.events.Event;
import elemental.events.EventListener;
import elemental.events.EventRemover;
import elemental.events.KeyboardEvent;
import elemental.events.KeyboardEvent.KeyCode;
/**
* Handler for the selection of a Workspace.
*/
// TODO: At some point we should try to make reEnter work on this thing.
public class WorkspacePlaceNavigationHandler
extends PlaceNavigationHandler<WorkspacePlace.NavigationEvent> {
private final AppContext appContext;
private final FileNameSearch searchIndex;
private final RemoverManager keyListenerRemoverManager;
// Presenters and Controllers that require cleanup.
private Header header;
private DocumentManager documentManager;
private CollaborationManager collaborationManager;
private WorkspacePlace workspacePlace;
private WorkspaceShell shell;
private FileTreeModelNetworkController fileNetworkController;
private KeepAliveTimer keepAliveTimer;
private ParticipantModel participantModel;
private CodePanelBundle codePanelBundle;
public WorkspacePlaceNavigationHandler(AppContext appContext) {
this.appContext = appContext;
this.keyListenerRemoverManager = new RemoverManager();
this.searchIndex = TreeWalkFileNameSearchImpl.create();
}
@Override
protected void cleanup() {
if (codePanelBundle != null) {
codePanelBundle.cleanup();
}
if (collaborationManager != null) {
collaborationManager.cleanup();
}
if (documentManager != null) {
documentManager.cleanup();
}
if (keyListenerRemoverManager != null) {
keyListenerRemoverManager.remove();
}
if (keepAliveTimer != null) {
keepAliveTimer.cancel();
}
}
@Override
protected void enterPlace(final WorkspacePlace.NavigationEvent navigationEvent) {
// Instantiate the Root View for the Workspace.
final WorkspaceShell.Resources res = appContext.getResources();
WorkspaceShell.View workspaceShellView = new WorkspaceShell.View(res);
workspacePlace = navigationEvent.getPlace();
FileTreeModelNetworkController.OutgoingController fileTreeModelOutgoingNetworkController =
new FileTreeModelNetworkController.OutgoingController(appContext);
FileTreeModel fileTreeModel =
new FileTreeModel(fileTreeModelOutgoingNetworkController);
documentManager = DocumentManager.create(fileTreeModel, appContext);
searchIndex.setFileTreeModel(fileTreeModel);
participantModel = ParticipantModel.create(
appContext.getFrontendApi(), appContext.getMessageFilter());
IncomingDocOpDemultiplexer docOpRecipient = IncomingDocOpDemultiplexer.create(
appContext.getMessageFilter());
collaborationManager = CollaborationManager.create(
appContext, documentManager, participantModel, docOpRecipient);
DocOpsSavedNotifier docOpSavedNotifier =
new DocOpsSavedNotifier(documentManager, collaborationManager);
fileNetworkController = FileTreeModelNetworkController.create(
fileTreeModel, appContext, navigationEvent.getPlace());
header = Header.create(workspaceShellView.getHeaderView(),
workspaceShellView,
workspacePlace,
appContext,
searchIndex,
fileTreeModel);
shell = WorkspaceShell.create(workspaceShellView, header);
// Attach to the DOM.
Elements.replaceContents(AppContext.GWT_ROOT, shell.getView().getElement());
// Reset the tab title
Elements.setCollideTitle("");
// Add a HotKey listener for to auto-focus the AwesomeBox.
/*
* The GlobalHotKey stuff utilizes the wave signal event stuff which filters alt+enter as an
* unimportant event. This prevents us from using the GlobalHotKey manager here.
*
* Note: This is capturing since the editor likes to nom-nom keys, in the dart re-write lets
* think about this sort of stuff ahead of time.
*/
final EventRemover eventRemover =
Elements.getBody().addEventListener(Event.KEYDOWN, new EventListener() {
@Override
public void handleEvent(Event evt) {
KeyboardEvent event = (KeyboardEvent) evt;
if (event.isAltKey() && event.getKeyCode() == KeyCode.ENTER) {
appContext.getAwesomeBoxComponentHostModel().revertToDefaultComponent();
header.getAwesomeBoxComponentHost().show();
}
}
}, true);
// Track this for removal in cleanup
keyListenerRemoverManager.track(new Remover() {
@Override
public void remove() {
eventRemover.remove();
}
});
codePanelBundle = new CodePanelBundle(appContext,
shell,
fileTreeModel,
searchIndex,
documentManager,
participantModel,
docOpRecipient,
navigationEvent.getPlace());
codePanelBundle.attach();
if (!navigationEvent.shouldNavExpand()) {
workspacePlace.fireEvent(new NavigationAreaExpansionEvent(false));
}
// Send a message to enter the workspace and initialize the workspace.
appContext.getFrontendApi().GET_WORKSPACE_META_DATA.send(
GetWorkspaceMetaDataImpl.make(), new ApiCallback<GetWorkspaceMetaDataResponse>() {
@Override
public void onMessageReceived(GetWorkspaceMetaDataResponse message) {
if (!navigationEvent.getPlace().isActive()) {
return;
}
// Start the keep-alive timer at 10 second intervals.
keepAliveTimer = new KeepAliveTimer(appContext, 5000);
keepAliveTimer.start();
codePanelBundle.enterWorkspace(navigationEvent, message);
}
@Override
public void onFail(FailureReason reason) {
if (FailureReason.UNAUTHORIZED == reason) {
/*
* User is not authorized to access this workspace.
*
* At this point, the components of the WorkspacePlace already sent multiple requests
* to the frontend that are bound to fail with the same reason. However, we don't want
* to gate loading the workspace to handle the rare case that a user accesses a branch
* that they do not have permission to access. Better to make the workspace load fast
* and log errors if the user is not authorized.
*/
UnauthorizedUser unauthorizedUser = UnauthorizedUser.create(res);
shell.setPerspective(unauthorizedUser.getView().getElement());
}
}
});
}
@Override
protected void reEnterPlace(
final WorkspacePlace.NavigationEvent navigationEvent, boolean hasNewState) {
if (hasNewState || navigationEvent.shouldForceReload()) {
// Simply do the default action which is to run cleanup/enter.
super.reEnterPlace(navigationEvent, hasNewState);
} else {
// we check to see if we end up being the active leaf and if so show the
// no file selected panel.
Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
@Override
public void execute() {
if (navigationEvent.isActiveLeaf()) {
codePanelBundle.showNoFileSelectedPanel();
}
}
});
}
}
}