/*
* Copyright 2011 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.walkaround.wave.client;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.walkaround.proto.ConnectResponse;
import com.google.walkaround.proto.WalkaroundWaveletSnapshot;
import com.google.walkaround.proto.WaveletDiffSnapshot;
import com.google.walkaround.proto.ClientVars.LiveClientVars;
import com.google.walkaround.proto.ClientVars.StaticClientVars;
import com.google.walkaround.proto.ClientVars.UdwLoadData;
import com.google.walkaround.proto.jso.ClientVarsJsoImpl;
import com.google.walkaround.proto.jso.DeltaJsoImpl;
import com.google.walkaround.slob.client.GenericOperationChannel.ReceiveOpChannel;
import com.google.walkaround.slob.client.GenericOperationChannel.SendOpService;
import com.google.walkaround.slob.shared.ChangeData;
import com.google.walkaround.slob.shared.MessageException;
import com.google.walkaround.slob.shared.SlobId;
import com.google.walkaround.util.client.log.ErrorReportingLogHandler;
import com.google.walkaround.util.client.log.LogPanel;
import com.google.walkaround.util.client.log.Logs;
import com.google.walkaround.util.client.log.Logs.Level;
import com.google.walkaround.util.client.log.Logs.Log;
import com.google.walkaround.util.shared.Assert;
import com.google.walkaround.util.shared.RandomBase64Generator;
import com.google.walkaround.util.shared.RandomBase64Generator.RandomProvider;
import com.google.walkaround.wave.client.MyFullDomRenderer.DocRefRenderer;
import com.google.walkaround.wave.client.attachment.DownloadThumbnailAction;
import com.google.walkaround.wave.client.attachment.UploadToolbarAction;
import com.google.walkaround.wave.client.attachment.WalkaroundAttachmentManager;
import com.google.walkaround.wave.client.profile.ContactsManager;
import com.google.walkaround.wave.client.rpc.AjaxRpc;
import com.google.walkaround.wave.client.rpc.AttachmentInfoService;
import com.google.walkaround.wave.client.rpc.ChannelConnectService;
import com.google.walkaround.wave.client.rpc.LoadWaveService;
import com.google.walkaround.wave.client.rpc.SubmitDeltaService;
import com.google.walkaround.wave.client.rpc.WaveletMap;
import com.google.walkaround.wave.client.rpc.LoadWaveService.ConnectCallback;
import com.google.walkaround.wave.client.rpc.WaveletMap.WaveletEntry;
import com.google.walkaround.wave.shared.IdHack;
import com.google.walkaround.wave.shared.Versions;
import com.google.walkaround.wave.shared.WaveSerializer;
import org.waveprotocol.wave.client.StageOne;
import org.waveprotocol.wave.client.StageThree;
import org.waveprotocol.wave.client.StageTwo;
import org.waveprotocol.wave.client.StageZero;
import org.waveprotocol.wave.client.Stages;
import org.waveprotocol.wave.client.account.Profile;
import org.waveprotocol.wave.client.account.ProfileManager;
import org.waveprotocol.wave.client.common.util.AsyncHolder;
import org.waveprotocol.wave.client.common.util.JsoView;
import org.waveprotocol.wave.client.concurrencycontrol.MuxConnector;
import org.waveprotocol.wave.client.concurrencycontrol.StaticChannelBinder;
import org.waveprotocol.wave.client.concurrencycontrol.WaveletOperationalizer;
import org.waveprotocol.wave.client.doodad.DoodadInstallers.ConversationInstaller;
import org.waveprotocol.wave.client.doodad.attachment.ImageThumbnail;
import org.waveprotocol.wave.client.editor.content.Registries;
import org.waveprotocol.wave.client.render.ReductionBasedRenderer;
import org.waveprotocol.wave.client.render.RenderingRules;
import org.waveprotocol.wave.client.scheduler.SchedulerInstance;
import org.waveprotocol.wave.client.scheduler.Scheduler.Task;
import org.waveprotocol.wave.client.uibuilder.UiBuilder;
import org.waveprotocol.wave.client.wave.LazyContentDocument;
import org.waveprotocol.wave.client.wave.RegistriesHolder;
import org.waveprotocol.wave.client.wave.SimpleDiffDoc;
import org.waveprotocol.wave.client.wave.WaveDocuments;
import org.waveprotocol.wave.client.wavepanel.render.HtmlDomRenderer;
import org.waveprotocol.wave.client.wavepanel.render.DocumentRegistries.Builder;
import org.waveprotocol.wave.client.wavepanel.view.BlipView;
import org.waveprotocol.wave.client.wavepanel.view.ParticipantView;
import org.waveprotocol.wave.client.wavepanel.view.dom.FullStructure;
import org.waveprotocol.wave.client.wavepanel.view.dom.ModelAsViewProvider;
import org.waveprotocol.wave.client.wavepanel.view.dom.ParticipantAvatarDomImpl;
import org.waveprotocol.wave.client.wavepanel.view.dom.UpgradeableDomAsViewProvider;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipQueueRenderer;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.DomRenderer;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ParticipantAvatarViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.impl.ParticipantViewImpl;
import org.waveprotocol.wave.client.widget.popup.AlignedPopupPositioner;
import org.waveprotocol.wave.client.widget.profile.ProfilePopupView;
import org.waveprotocol.wave.client.widget.profile.ProfilePopupWidget;
import org.waveprotocol.wave.concurrencycontrol.channel.WaveViewService;
import org.waveprotocol.wave.model.conversation.Conversation;
import org.waveprotocol.wave.model.conversation.ConversationBlip;
import org.waveprotocol.wave.model.conversation.ConversationThread;
import org.waveprotocol.wave.model.document.indexed.IndexedDocumentImpl;
import org.waveprotocol.wave.model.document.operation.DocInitialization;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.id.IdGenerator;
import org.waveprotocol.wave.model.id.ModernIdSerialiser;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.schema.SchemaProvider;
import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas;
import org.waveprotocol.wave.model.testing.RandomProviderImpl;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.IdentityMap;
import org.waveprotocol.wave.model.util.StringMap;
import org.waveprotocol.wave.model.wave.InvalidParticipantAddress;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.wave.Wavelet;
import org.waveprotocol.wave.model.wave.data.DocumentFactory;
import org.waveprotocol.wave.model.wave.data.ObservableWaveletData;
import org.waveprotocol.wave.model.wave.data.WaveViewData;
import org.waveprotocol.wave.model.wave.data.impl.ObservablePluggableMutableDocument;
import org.waveprotocol.wave.model.wave.data.impl.WaveViewDataImpl;
import org.waveprotocol.wave.model.wave.data.impl.WaveletDataImpl;
import javax.annotation.Nullable;
/**
* Walkaround configuration for Undercurrent client
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class Walkaround implements EntryPoint {
// TODO(danilatos): Enable this once Undercurrent does not crash. Or, delete if undercurrent
// ends up doing it by default:
//
// static {
// CollectionUtils.setDefaultCollectionFactory(new JsoCollectionFactory());
// }
static final Log logger = Logs.create("client");
private static final String WAVEPANEL_PLACEHOLDER = "initialHtml";
// TODO(danilatos): flag
private static final int VERSION_CHECK_INTERVAL_MS = 15 * 1000;
private static final String OOPHM_SUFFIX = "&gwt.codesvr=127.0.0.1:9997";
private final ClientVarsJsoImpl clientVars = nativeGetVars().cast();
private final WaveSerializer serializer = new WaveSerializer(new ClientMessageSerializer());
private final WaveletMap wavelets = new WaveletMap();
private SlobId convObjectId;
private SlobId udwObjectId;
private IdGenerator idGenerator;
private static native JsoView nativeGetVars() /*-{
return $wnd.__vars;
}-*/;
private static LogPanel domLogger;
private static boolean loaded;
private WaveletEntry makeEntry(SlobId objectId,
@Nullable ConnectResponse connectResponse, WaveletDataImpl wavelet) {
if (connectResponse != null) {
Preconditions.checkArgument(
objectId.getId().equals(connectResponse.getSignedSession().getSession().getObjectId()),
"Mismatched object ids: %s, %s", objectId, connectResponse);
}
return new WaveletEntry(objectId,
connectResponse == null ? null : connectResponse.getSignedSessionString(),
connectResponse == null ? null : connectResponse.getSignedSession().getSession(),
connectResponse == null ? null : connectResponse.getChannelToken(),
wavelet);
}
private WaveletEntry parseConvWaveletData(
@Nullable ConnectResponse connectResponse, WaveletDiffSnapshot diffSnapshot,
DocumentFactory<?> docFactory, StringMap<DocOp> diffMap) {
WaveSerializer waveSerializer = new WaveSerializer(new ClientMessageSerializer(), docFactory);
WaveletDataImpl wavelet;
try {
StringMap<DocOp> diffOps = waveSerializer.deserializeDocumentsDiffs(diffSnapshot);
diffMap.putAll(diffOps);
wavelet = waveSerializer.createWaveletData(
IdHack.convWaveletNameFromConvObjectId(convObjectId), diffSnapshot);
} catch (MessageException e) {
throw new RuntimeException(e);
}
return makeEntry(convObjectId, connectResponse, wavelet);
}
private WaveletEntry parseUdwData(
@Nullable ConnectResponse connectResponse, WalkaroundWaveletSnapshot snapshot,
DocumentFactory<?> docFactory) {
WaveSerializer serializer = new WaveSerializer(new ClientMessageSerializer(), docFactory);
WaveletDataImpl wavelet;
try {
wavelet = serializer.createWaveletData(
IdHack.udwWaveletNameFromConvObjectIdAndUdwObjectId(convObjectId, udwObjectId),
snapshot);
} catch (MessageException e) {
throw new RuntimeException(e);
}
return makeEntry(udwObjectId, connectResponse, wavelet);
}
/**
* Runs the harness script.
*/
@Override
public void onModuleLoad() {
if (loaded) {
return;
}
loaded = true;
setupLogging();
setupShell();
// Bail early if the server sent us an error message
if (clientVars.hasErrorVars()) {
Element placeHolder = Document.get().getElementById(WAVEPANEL_PLACEHOLDER);
String errorMessage = "Error: " + clientVars.getErrorVars().getErrorMessage();
if (placeHolder != null) {
placeHolder.setInnerText(errorMessage);
} else {
Window.alert(errorMessage);
}
return;
}
logger.log(Level.INFO, "Init");
final SavedStateIndicator indicator = new SavedStateIndicator(
Document.get().getElementById("savedStateContainer"));
final AjaxRpc rpc = new AjaxRpc("", indicator);
final boolean isLive;
final boolean useUdw;
@Nullable final UdwLoadData udwData;
final int randomSeed;
final String userIdString;
final boolean haveOAuthToken;
final String convObjectIdString;
final WaveletDiffSnapshot convSnapshot;
@Nullable final ConnectResponse convConnectResponse;
if (clientVars.hasStaticClientVars()) {
isLive = false;
StaticClientVars vars = clientVars.getStaticClientVars();
randomSeed = vars.getRandomSeed();
userIdString = vars.getUserEmail();
haveOAuthToken = vars.getHaveOauthToken();
convObjectIdString = vars.getConvObjectId();
convSnapshot = vars.getConvSnapshot();
convConnectResponse = null;
useUdw = false;
udwData = null;
} else {
isLive = true;
LiveClientVars vars = clientVars.getLiveClientVars();
randomSeed = vars.getRandomSeed();
userIdString = vars.getUserEmail();
haveOAuthToken = vars.getHaveOauthToken();
convObjectIdString =
vars.getConvConnectResponse().getSignedSession().getSession().getObjectId();
convSnapshot = vars.getConvSnapshot();
convConnectResponse = vars.getConvConnectResponse();
if (!vars.hasUdw()) {
useUdw = false;
udwData = null;
} else {
useUdw = true;
udwData = vars.getUdw();
udwObjectId = new SlobId(vars.getUdw().getConnectResponse()
.getSignedSession().getSession().getObjectId());
}
VersionChecker versionChecker = new VersionChecker(rpc, vars.getClientVersion());
// NOTE(danilatos): Use the highest priority timer, since we can't afford to
// let it be starved due to some bug with another non-terminating
// high-priority task. This task runs infrequently and is very minimal so
// the risk of impacting the UI is low.
SchedulerInstance.getHighPriorityTimer().scheduleRepeating(versionChecker,
VERSION_CHECK_INTERVAL_MS, VERSION_CHECK_INTERVAL_MS);
}
final RandomProviderImpl random =
// TODO(ohler): Get a stronger RandomProvider.
RandomProviderImpl.ofSeed(randomSeed);
final RandomBase64Generator random64 = new RandomBase64Generator(new RandomProvider() {
@Override public int nextInt(int upperBound) {
return random.nextInt(upperBound);
}});
final ParticipantId userId;
try {
userId = ParticipantId.of(userIdString);
} catch (InvalidParticipantAddress e1) {
Window.alert("Invalid user id received from server: " + userIdString);
return;
}
convObjectId = new SlobId(convObjectIdString);
idGenerator = new IdHack.MinimalIdGenerator(
IdHack.convWaveletIdFromObjectId(convObjectId),
useUdw ? IdHack.udwWaveletIdFromObjectId(udwObjectId)
// Some code depends on this not to return null, so let's return
// something.
: IdHack.DISABLED_UDW_ID,
random64);
// TODO(ohler): Make the server's response to the contacts RPC indicate
// whether an OAuth token is needed, and enable the button dynamically when
// appropriate, rather than statically.
UIObject.setVisible(Document.get().getElementById("enableAvatarsButton"),
!haveOAuthToken);
@Nullable final LoadWaveService loadWaveService = isLive ? new LoadWaveService(rpc) : null;
@Nullable final ChannelConnectService channelService =
isLive ? new ChannelConnectService(rpc) : null;
new Stages() {
@Override
protected AsyncHolder<StageZero> createStageZeroLoader() {
return new StageZero.DefaultProvider() {
@Override
protected UncaughtExceptionHandler createUncaughtExceptionHandler() {
return WalkaroundUncaughtExceptionHandler.INSTANCE;
}
};
}
@Override
protected AsyncHolder<StageOne> createStageOneLoader(StageZero zero) {
return new StageOne.DefaultProvider(zero) {
protected final ParticipantViewImpl.Helper<ParticipantAvatarDomImpl> participantHelper =
new ParticipantViewImpl.Helper<ParticipantAvatarDomImpl>() {
@Override public void remove(ParticipantAvatarDomImpl impl) {
impl.remove();
}
@Override public ProfilePopupView showParticipation(ParticipantAvatarDomImpl impl) {
return new ProfilePopupWidget(impl.getElement(),
AlignedPopupPositioner.BELOW_RIGHT);
}
};
@Override protected UpgradeableDomAsViewProvider createViewProvider() {
return new FullStructure(createCssProvider()) {
@Override public ParticipantView asParticipant(Element e) {
return e == null ? null : new ParticipantViewImpl<ParticipantAvatarDomImpl>(
participantHelper, ParticipantAvatarDomImpl.of(e));
}
};
}
};
}
@Override
protected AsyncHolder<StageTwo> createStageTwoLoader(final StageOne one) {
return new StageTwo.DefaultProvider(one, null) {
WaveViewData waveData;
StringMap<DocOp> diffMap = CollectionUtils.createStringMap();
@Override protected DomRenderer createRenderer() {
final BlipQueueRenderer pager = getBlipQueue();
DocRefRenderer docRenderer = new DocRefRenderer() {
@Override
public UiBuilder render(
ConversationBlip blip, IdentityMap<ConversationThread, UiBuilder> replies) {
// Documents are rendered blank, and filled in later when
// they get paged in.
pager.add(blip);
return DocRefRenderer.EMPTY.render(blip, replies);
}
};
RenderingRules<UiBuilder> rules = new MyFullDomRenderer(
getBlipDetailer(), docRenderer, getProfileManager(),
getViewIdMapper(), createViewFactories(), getThreadReadStateMonitor()) {
@Override
public UiBuilder render(Conversation conversation, ParticipantId participant) {
// Same as super class, but using avatars instead of names.
Profile profile = getProfileManager().getProfile(participant);
String id = getViewIdMapper().participantOf(conversation, participant);
ParticipantAvatarViewBuilder participantUi =
ParticipantAvatarViewBuilder.create(id);
participantUi.setAvatar(profile.getImageUrl());
participantUi.setName(profile.getFullName());
return participantUi;
}
};
return new HtmlDomRenderer(ReductionBasedRenderer.of(rules, getConversations()));
}
@Override
protected ProfileManager createProfileManager() {
return ContactsManager.create(rpc);
}
@Override
protected void create(final Accessor<StageTwo> whenReady) {
super.create(whenReady);
}
@Override
protected IdGenerator createIdGenerator() {
return idGenerator;
}
@Override
protected void fetchWave(final Accessor<WaveViewData> whenReady) {
wavelets.updateData(
parseConvWaveletData(
convConnectResponse, convSnapshot,
getDocumentRegistry(), diffMap));
if (useUdw) {
wavelets.updateData(
parseUdwData(
udwData.getConnectResponse(),
udwData.getSnapshot(),
getDocumentRegistry()));
}
Document.get().getElementById(WAVEPANEL_PLACEHOLDER).setInnerText("");
waveData = createWaveViewData();
whenReady.use(waveData);
}
@Override
protected WaveDocuments<LazyContentDocument> createDocumentRegistry() {
IndexedDocumentImpl.performValidation = false;
DocumentFactory<?> dataDocFactory =
ObservablePluggableMutableDocument.createFactory(createSchemas());
DocumentFactory<LazyContentDocument> blipDocFactory =
new DocumentFactory<LazyContentDocument>() {
private final Registries registries = RegistriesHolder.get();
@Override
public LazyContentDocument create(
WaveletId waveletId, String docId, DocInitialization content) {
SimpleDiffDoc diff = SimpleDiffDoc.create(content, diffMap.get(docId));
return LazyContentDocument.create(registries, diff);
}
};
return WaveDocuments.create(blipDocFactory, dataDocFactory);
}
@Override
protected ParticipantId createSignedInUser() {
return userId;
}
@Override
protected String createSessionId() {
// TODO(ohler): Write a note here about what this is and how it
// interacts with walkaround's session management.
return random64.next(6);
}
@Override
protected MuxConnector createConnector() {
return new MuxConnector() {
private void connectWavelet(StaticChannelBinder binder,
ObservableWaveletData wavelet) {
WaveletId waveletId = wavelet.getWaveletId();
SlobId objectId = IdHack.objectIdFromWaveletId(waveletId);
WaveletEntry data = wavelets.get(objectId);
Assert.check(data != null, "Unknown wavelet: %s", waveletId);
if (data.getChannelToken() == null) {
// TODO(danilatos): Handle with a nicer message, and maybe try to
// reconnect later.
Window.alert("Could not open a live connection to this wave. "
+ "It will be read-only, changes will not be saved!");
return;
}
String debugSuffix;
if (objectId.equals(convObjectId)) {
debugSuffix = "-c";
} else if (objectId.equals(udwObjectId)) {
debugSuffix = "-u";
} else {
debugSuffix = "-xxx";
}
ReceiveOpChannel<WaveletOperation> storeChannel =
new GaeReceiveOpChannel<WaveletOperation>(
objectId, data.getSignedSessionString(), data.getChannelToken(),
channelService, Logs.create("gaeroc" + debugSuffix)) {
@Override
protected WaveletOperation parse(ChangeData<JavaScriptObject> message)
throws MessageException {
return serializer.deserializeDelta(
message.getPayload().<DeltaJsoImpl>cast());
}
};
WalkaroundOperationChannel channel = new WalkaroundOperationChannel(
Logs.create("channel" + debugSuffix),
createSubmitService(objectId),
storeChannel, Versions.truncate(wavelet.getVersion()),
data.getSession().getClientId(),
indicator);
String id = ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveletId);
binder.bind(id, channel);
}
@Override
public void connect(Command onOpened) {
if (isLive) {
WaveletOperationalizer operationalizer = getWavelets();
StaticChannelBinder binder = new StaticChannelBinder(
operationalizer, getDocumentRegistry());
for (ObservableWaveletData wavelet : operationalizer.getWavelets()) {
if (useUdw || !IdHack.DISABLED_UDW_ID.equals(wavelet.getWaveletId())) {
connectWavelet(binder, wavelet);
}
}
// HACK(ohler): I haven't tried to understand what the semantics of the callback
// are; perhaps we should invoke it even if the wave is static. (It seems to be
// null though.)
if (onOpened != null) {
onOpened.execute();
}
}
}
@Override
public void close() {
throw new AssertionError("close not implemented");
}
};
}
private SubmitDeltaService createSubmitService(final SlobId objectId) {
return new SubmitDeltaService(rpc, wavelets, objectId) {
@Override
public void requestRevision(final SendOpService.Callback callback) {
loadWaveService.fetchWaveRevision(
wavelets.get(objectId).getSignedSessionString(),
new ConnectCallback() {
@Override public void onData(ConnectResponse data) {
// TODO(danilatos): Update session id etc in the operation channel
// in order for (something equivalent to) this to work.
// But don't have submit delta service keep a reference to this map.
// ALSO TODO: channelToken could be null, if a channel could not be opened.
// In this case the object should be opened as readonly.
//wavelets.updateData(
// new WaveletEntry(id, channelToken, sid, xsrfToken, revision, null));
callback.onSuccess(Versions.truncate(data.getObjectVersion()));
}
@Override public void onConnectionError(Throwable e) {
callback.onConnectionError(e);
}
});
}
};
}
@Override
protected WaveViewService createWaveViewService() {
// unecessary
throw new UnsupportedOperationException();
}
@Override
protected SchemaProvider createSchemas() {
return new ConversationSchemas();
}
@Override
protected Builder installDoodads(Builder doodads) {
doodads = super.installDoodads(doodads);
doodads.use(new ConversationInstaller() {
@Override
public void install(Wavelet w, Conversation c, Registries r) {
WalkaroundAttachmentManager mgr = new WalkaroundAttachmentManager(
new AttachmentInfoService(rpc), logger);
ImageThumbnail.register(r.getElementHandlerRegistry(), mgr,
new DownloadThumbnailAction());
}
});
return doodads;
}
@Override
protected void installFeatures() {
super.installFeatures();
ReadStateSynchronizer.create(FakeReadStateService.create(), getReadMonitor());
}
};
}
@Override
protected AsyncHolder<StageThree> createStageThreeLoader(final StageTwo two) {
return new StageThree.DefaultProvider(two) {
@Override protected void install() {
// Inhibit if not live; super.install() seems to enable editing in Undercurrent.
// Haven't studied this carefully though.
if (isLive) {
super.install();
}
}
@Override protected boolean getAttachmentButtonEnabled() {
return false;
}
@Override protected void create(final Accessor<StageThree> whenReady) {
// Prepend an init wave flow onto the stage continuation.
super.create(new Accessor<StageThree>() {
@Override
public void use(StageThree three) {
if (isLive) {
maybeNewWaveSetup(two, three);
UploadToolbarAction.install(three.getEditSession(), three.getEditToolbar());
// HACK(ohler): I haven't tried to understand what the semantics of the callback
// are; perhaps we should invoke it even if the wave is static.
whenReady.use(three);
}
}
});
}
};
}
private void maybeNewWaveSetup(StageTwo two, StageThree three) {
ModelAsViewProvider views = two.getModelAsViewProvider();
Conversation rootConv = two.getConversations().getRoot();
if (looksLikeANewWave(rootConv)) {
BlipView blipUi = views.getBlipView(rootConv.getRootThread().getFirstBlip());
// Needed because startEditing must have an editor already rendered.
two.getBlipQueue().flush();
three.getEditActions().startEditing(blipUi);
}
}
private boolean looksLikeANewWave(Conversation rootConv) {
return Iterables.size(rootConv.getRootThread().getBlips()) == 1
&& rootConv.getRootThread().getFirstBlip().getContent().size() == 4;
}
}.load(null);
}
private WaveViewDataImpl createWaveViewData() {
final WaveViewDataImpl waveData = WaveViewDataImpl.create(
IdHack.waveIdFromConvObjectId(convObjectId));
wavelets.each(new WaveletMap.Proc() {
@Override public void wavelet(WaveletEntry data) {
WaveletDataImpl wavelet = data.getWaveletState();
waveData.addWavelet(wavelet);
}
});
return waveData;
}
private void setupShell() {
DebugMenu menu = new DebugMenu();
boolean shouldShowDebug =
Window.Location.getParameter("debug") != null;
menu.setVisible(shouldShowDebug);
menu.addItem("OOPHM", navigateTaskOophm(Window.Location.getHref(), false));
menu.addItem("Show log", new Task() {
@Override public void execute() {
Walkaround.ensureDomLog();
}
});
menu.addItem("log level 'debug'", navigateTask(
Window.Location.getHref() + "&ll=debug", false));
menu.addItem("Frame: " + Window.Location.getHref(),
navigateTask(Window.Location.getHref(), true));
menu.addItem("Throw Exception", new Task() {
@Override public void execute() {
throw new RuntimeException("TEST EXCEPTION", a());
}
private Exception a() {
return b();
}
private Exception b() {
return new Exception("Nested test exception cause");
}
});
menu.install();
}
private void setupLogging() {
Logs.get().addHandler(new ErrorReportingLogHandler("/gwterr") {
@Override protected void onSevere(String stream) {
alertDomLog(stream);
}
});
}
/**
* If ths DOM log has not been created yet, creates it and draws attention to
* a stream.
*/
private static void alertDomLog(final String attentionStream) {
if (domLogger == null) {
domLogger = LogPanel.createOnStream(Logs.get(), attentionStream);
attachLogPanel();
}
}
/**
* If ths DOM log has not been created yet, creates it.
*/
private static void ensureDomLog() {
if (domLogger == null) {
domLogger = LogPanel.create(Logs.get());
attachLogPanel();
}
}
private static Task navigateTaskOophm(String url, boolean newPage) {
if (!url.endsWith(OOPHM_SUFFIX)) {
url += OOPHM_SUFFIX;
}
return navigateTask(url, newPage);
}
private static Task navigateTask(final String url, final boolean newPage) {
return new Task() {
@Override public void execute() {
if (newPage) {
Window.open(url, "_blank", null);
} else {
Window.Location.assign(url);
}
}
};
}
/** Reveals the log div, and executes a task when done. */
// The async API for this method is intended to support two things: a cool
// spew animation, and also the potential to runAsync the whole LogPanel code.
private static void attachLogPanel() {
Logs.get().addHandler(domLogger);
final Panel logHolder = RootPanel.get("logHolder");
logHolder.setVisible(true);
// Need one layout and paint cycle after revealing it to start animation.
// Use high priority to avoid potential starvation by other tasks if a
// problem is occurring.
SchedulerInstance.getHighPriorityTimer().scheduleDelayed(new Task() {
@Override
public void execute() {
logHolder.add(domLogger);
Style waveStyle = Document.get().getElementById(WAVEPANEL_PLACEHOLDER).getStyle();
Style logStyle = logHolder.getElement().getStyle();
logStyle.setHeight(250, Unit.PX);
waveStyle.setBottom(250, Unit.PX);
}
}, 50);
}
}