// 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.