/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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. 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.wave.client.wavepanel.render;
import org.waveprotocol.wave.client.account.Profile;
import org.waveprotocol.wave.client.account.ProfileManager;
import org.waveprotocol.wave.client.common.safehtml.EscapeUtils;
import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder;
import org.waveprotocol.wave.client.render.RenderingRules;
import org.waveprotocol.wave.client.state.ThreadReadStateMonitor;
import org.waveprotocol.wave.client.uibuilder.HtmlClosure;
import org.waveprotocol.wave.client.uibuilder.HtmlClosureCollection;
import org.waveprotocol.wave.client.uibuilder.UiBuilder;
import org.waveprotocol.wave.client.wavepanel.view.ViewIdMapper;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.AnchorViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipMetaViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ContinuationIndicatorViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.InlineThreadViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ParticipantAvatarViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ParticipantsViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ReplyBoxViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.RootThreadViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.ViewFactory;
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 FullDomRenderer implements RenderingRules<UiBuilder> {
public interface DocRefRenderer {
UiBuilder render(ConversationBlip blip,
IdentityMap<ConversationThread, UiBuilder> replies);
DocRefRenderer EMPTY = new DocRefRenderer() {
@Override
public UiBuilder render(ConversationBlip blip,
IdentityMap<ConversationThread, UiBuilder> replies) {
return UiBuilder.Constant.of(EscapeUtils.fromSafeConstant("<div></div>"));
}
};
}
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;
public FullDomRenderer(ShallowBlipRenderer blipPopulator, DocRefRenderer docRenderer,
ProfileManager profileManager, ViewIdMapper viewIdMapper, ViewFactory viewFactory,
ThreadReadStateMonitor readMonitor) {
this.blipPopulator = blipPopulator;
this.docRenderer = docRenderer;
this.profileManager = profileManager;
this.viewIdMapper = viewIdMapper;
this.viewFactory = viewFactory;
this.readMonitor = readMonitor;
}
@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(id, participantsUi);
}
@Override
public UiBuilder render(Conversation conversation, ParticipantId participant) {
Profile profile = profileManager.getProfile(participant);
String id = viewIdMapper.participantOf(conversation, participant);
ParticipantAvatarViewBuilder participantUi = ParticipantAvatarViewBuilder.create(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(replyIndicatorId);
builder = RootThreadViewBuilder.create(threadId, blipsUi, replyBoxBuilder);
} else {
ContinuationIndicatorViewBuilder indicatorBuilder = ContinuationIndicatorViewBuilder.create(
replyIndicatorId);
InlineThreadViewBuilder inlineBuilder =
InlineThreadViewBuilder.create(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 = BlipMetaViewBuilder.create(viewIdMapper.metaOf(blip), document);
blipPopulator.render(blip, metaUi);
return BlipViewBuilder.create(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);
}
}