/**
* Copyright 2010 Google Inc.
*
* 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 org.waveprotocol.box.server.rpc.render;
import org.waveprotocol.box.server.rpc.render.account.Profile;
import org.waveprotocol.box.server.rpc.render.account.ProfileManager;
import org.waveprotocol.box.server.rpc.render.common.safehtml.EscapeUtils;
import org.waveprotocol.box.server.rpc.render.common.safehtml.SafeHtmlBuilder;
import org.waveprotocol.box.server.rpc.render.renderer.RenderingRules;
import org.waveprotocol.box.server.rpc.render.renderer.ShallowBlipRenderer;
import org.waveprotocol.box.server.rpc.render.state.ThreadReadStateMonitor;
import org.waveprotocol.box.server.rpc.render.uibuilder.HtmlClosure;
import org.waveprotocol.box.server.rpc.render.uibuilder.HtmlClosureCollection;
import org.waveprotocol.box.server.rpc.render.uibuilder.UiBuilder;
import org.waveprotocol.box.server.rpc.render.view.ViewFactory;
import org.waveprotocol.box.server.rpc.render.view.ViewIdMapper;
import org.waveprotocol.box.server.rpc.render.view.builder.AnchorViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.BlipMetaViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.BlipViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.ContinuationIndicatorViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.InlineThreadViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.ParticipantNameViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.ParticipantsViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.ReplyBoxViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.RootThreadViewBuilder;
import org.waveprotocol.box.server.rpc.render.view.builder.WavePanelResources;
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.conversation.ConversationView;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.IdentityMap;
import org.waveprotocol.wave.model.util.IdentityMap.ProcV;
import org.waveprotocol.wave.model.util.IdentityMap.Reduce;
import org.waveprotocol.wave.model.util.StringMap;
import org.waveprotocol.wave.model.wave.ParticipantId;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Renders conversational objects with UiBuilders.
*
*/
public final class FullHtmlWaveRenderer implements RenderingRules<UiBuilder> {
public interface DocRefRenderer {
UiBuilder render(ConversationBlip blip,
IdentityMap<ConversationThread, UiBuilder> replies);
public static final DocRefRenderer EMPTY = new DocRefRenderer() {
@Override
public UiBuilder render(ConversationBlip blip,
IdentityMap<ConversationThread, UiBuilder> replies) {
return UiBuilder.Constant.of(EscapeUtils.fromSafeConstant(""));
}
};
}
public interface ParticipantsRenderer {
UiBuilder render(Conversation c);
ParticipantsRenderer EMPTY = new ParticipantsRenderer() {
@Override
public UiBuilder render(Conversation c) {
return UiBuilder.Constant.of(EscapeUtils.fromSafeConstant("<div></div>"));
}
};
}
private final ShallowBlipRenderer blipPopulator;
private final DocRefRenderer docRenderer;
private final ViewIdMapper viewIdMapper;
private final ViewFactory viewFactory;
private final ProfileManager profileManager;
private final ThreadReadStateMonitor readMonitor;
private final WavePanelResources resources;
private final String waveUri;
public FullHtmlWaveRenderer(ShallowBlipRenderer blipPopulator, DocRefRenderer docRenderer,
ProfileManager profileManager, ViewIdMapper viewIdMapper, ViewFactory viewFactory,
ThreadReadStateMonitor readMonitor, WavePanelResources resources, String waveletUri) {
this.blipPopulator = blipPopulator;
this.docRenderer = docRenderer;
this.profileManager = profileManager;
this.viewIdMapper = viewIdMapper;
this.viewFactory = viewFactory;
this.readMonitor = readMonitor;
this.resources = resources;
this.waveUri = waveletUri;
}
@Override
public UiBuilder render(ConversationView wave,
IdentityMap<Conversation, UiBuilder> conversations) {
// return the first conversation in the view.
// TODO(hearnden): select the 'best' conversation.
return conversations.isEmpty() ? null : getFirstConversation(conversations);
}
public UiBuilder getFirstConversation(IdentityMap<Conversation, UiBuilder> conversations) {
return conversations.reduce(null, new Reduce<Conversation, UiBuilder, UiBuilder>() {
@Override
public UiBuilder apply(UiBuilder soFar, Conversation key, UiBuilder item) {
// Pick the first rendering (any will do).
return soFar == null ? item : soFar;
}
});
}
@Override
public UiBuilder render(Conversation conversation, UiBuilder participantsUi, UiBuilder threadUi) {
String id = viewIdMapper.conversationOf(conversation);
boolean isTop = !conversation.hasAnchor();
return isTop ? viewFactory.createTopConversationView(id, threadUi, participantsUi)
: viewFactory.createInlineConversationView(id, threadUi, participantsUi);
}
@Override
public UiBuilder render(Conversation conversation, StringMap<UiBuilder> participantUis) {
HtmlClosureCollection participantsUi = new HtmlClosureCollection();
for (ParticipantId participant : conversation.getParticipantIds()) {
participantsUi.add(participantUis.get(participant.getAddress()));
}
String id = viewIdMapper.participantsOf(conversation);
return ParticipantsViewBuilder.create(resources, id, participantsUi);
}
@Override
public UiBuilder render(Conversation conversation, ParticipantId participant) {
Profile profile = profileManager.getProfile(participant);
String id = viewIdMapper.participantOf(conversation, participant);
// Use ParticipantAvatarViewBuilder for avatars.
ParticipantNameViewBuilder participantUi = null;
participantUi =
ParticipantNameViewBuilder.create(resources, id);
participantUi.setAvatar(profile.getImageUrl());
participantUi.setName(profile.getFullName());
return participantUi;
}
@Override
public UiBuilder render(final ConversationThread thread,
final IdentityMap<ConversationBlip, UiBuilder> blipUis) {
HtmlClosure blipsUi = new HtmlClosure() {
@Override
public void outputHtml(SafeHtmlBuilder out) {
for (ConversationBlip blip : thread.getBlips()) {
UiBuilder blipUi = blipUis.get(blip);
// Not all blips are rendered.
if (blipUi != null) {
blipUi.outputHtml(out);
}
}
}
};
String threadId = viewIdMapper.threadOf(thread);
String replyIndicatorId = viewIdMapper.replyIndicatorOf(thread);
UiBuilder builder = null;
if (thread.getConversation().getRootThread() == thread) {
ReplyBoxViewBuilder replyBoxBuilder =
ReplyBoxViewBuilder.create(resources, replyIndicatorId);
builder = RootThreadViewBuilder.create(resources, threadId, blipsUi, replyBoxBuilder);
} else {
ContinuationIndicatorViewBuilder indicatorBuilder = ContinuationIndicatorViewBuilder.create(
resources, replyIndicatorId);
InlineThreadViewBuilder inlineBuilder =
InlineThreadViewBuilder.create(resources, threadId, blipsUi, indicatorBuilder);
int read = readMonitor.getReadCount(thread);
int unread = readMonitor.getUnreadCount(thread);
inlineBuilder.setTotalBlipCount(read + unread);
inlineBuilder.setUnreadBlipCount(unread);
builder = inlineBuilder;
}
return builder;
}
@Override
public UiBuilder render(final ConversationBlip blip, UiBuilder document,
final IdentityMap<ConversationThread, UiBuilder> anchorUis,
final IdentityMap<Conversation, UiBuilder> nestedConversations) {
UiBuilder threadsUi = new UiBuilder() {
@Override
public void outputHtml(SafeHtmlBuilder out) {
for (ConversationThread thread : blip.getReplyThreads()) {
anchorUis.get(thread).outputHtml(out);
}
}
};
UiBuilder convsUi = new UiBuilder() {
@Override
public void outputHtml(SafeHtmlBuilder out) {
// Order by conversation id. Ideally, the sort key would be creation
// time, but that is not exposed in the conversation API.
final List<Conversation> ordered = CollectionUtils.newArrayList();
nestedConversations.each(new ProcV<Conversation, UiBuilder>() {
@Override
public void apply(Conversation conv, UiBuilder ui) {
ordered.add(conv);
}
});
Collections.sort(ordered, new Comparator<Conversation>() {
@Override
public int compare(Conversation o1, Conversation o2) {
return o1.getId().compareTo(o2.getId());
}
});
List<UiBuilder> orderedUis = CollectionUtils.newArrayList();
for (Conversation conv : ordered) {
nestedConversations.get(conv).outputHtml(out);
}
}
};
BlipMetaViewBuilder metaUi = null;
metaUi = BlipMetaViewBuilder.create(resources, viewIdMapper.metaOf(blip), document);
metaUi.setBlipUri(waveUri + "/" + blip.hackGetRaw().getId());
blipPopulator.render(blip, metaUi);
return BlipViewBuilder.create(resources, viewIdMapper.blipOf(blip), metaUi, threadsUi, convsUi);
}
/**
*/
@Override
public UiBuilder render(
ConversationBlip blip, IdentityMap<ConversationThread, UiBuilder> replies) {
return docRenderer.render(blip, replies);
}
@Override
public UiBuilder render(ConversationThread thread, UiBuilder threadR) {
String id = EscapeUtils.htmlEscape(viewIdMapper.defaultAnchorOf(thread));
return AnchorViewBuilder.create(id, threadR);
}
}