/** * 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.wave.client.wavepanel.view.dom.full; import static org.waveprotocol.wave.client.uibuilder.BuilderHelper.nonNull; import static org.waveprotocol.wave.client.uibuilder.OutputHelper.close; import static org.waveprotocol.wave.client.uibuilder.OutputHelper.closeSpan; import static org.waveprotocol.wave.client.uibuilder.OutputHelper.image; import static org.waveprotocol.wave.client.uibuilder.OutputHelper.open; import static org.waveprotocol.wave.client.uibuilder.OutputHelper.openSpanWith; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import org.waveprotocol.wave.client.common.safehtml.EscapeUtils; import org.waveprotocol.wave.client.common.safehtml.SafeHtml; import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder; import org.waveprotocol.wave.client.uibuilder.BuilderHelper.Component; import org.waveprotocol.wave.client.uibuilder.UiBuilder; import org.waveprotocol.wave.client.wavepanel.view.IntrinsicBlipMetaView; import org.waveprotocol.wave.client.wavepanel.view.View.Type; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.StringMap; import java.util.Arrays; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashSet; import java.util.Map; import java.util.Set; /** */ public final class BlipMetaViewBuilder implements UiBuilder, IntrinsicBlipMetaView { /** An enum for all the components of a blip view. */ public enum Components implements Component { /** The avatar element. */ AVATAR("A"), /** The text inside the information bar. */ METALINE("M"), /** The element for the information bar. */ METABAR("B"), /** The element containing the time text. */ TIME("T"), /** The element containing the document. */ CONTENT("C"), /** The element containing menu options. */ MENU("N"), ; private final String postfix; Components(String postfix) { this.postfix = postfix; } @Override public String getDomId(String baseId) { return baseId + postfix; } } // The consistent iterator ordering of EnumMap is relied upon, to ensure that // the same menu options are always rendered in the same order. private final static Map<MenuOption, SafeHtml> MENU_CODES = new EnumMap<MenuOption, SafeHtml>(MenuOption.class); private final static Map<MenuOption, SafeHtml> MENU_LABELS = new EnumMap<MenuOption, SafeHtml>(MenuOption.class); private final static StringMap<MenuOption> MENU_OPTIONS = CollectionUtils.createStringMap(); public static final String OPTION_ID_ATTRIBUTE = "o"; public static final String OPTION_SELECTED_ATTRIBUTE = "s"; static { MENU_CODES.put(MenuOption.EDIT, EscapeUtils.fromSafeConstant("e")); MENU_CODES.put(MenuOption.REPLY, EscapeUtils.fromSafeConstant("r")); MENU_CODES.put(MenuOption.DELETE, EscapeUtils.fromSafeConstant("d")); MENU_CODES.put(MenuOption.LINK, EscapeUtils.fromSafeConstant("l")); MENU_LABELS.put(MenuOption.EDIT, EscapeUtils.fromSafeConstant("Edit")); MENU_LABELS.put(MenuOption.REPLY, EscapeUtils.fromSafeConstant("Reply")); MENU_LABELS.put(MenuOption.DELETE, EscapeUtils.fromSafeConstant("Delete")); MENU_LABELS.put(MenuOption.LINK, EscapeUtils.fromSafeConstant("Link")); for (MenuOption option : MENU_CODES.keySet()) { MENU_OPTIONS.put(MENU_CODES.get(option).asString(), option); } assert MENU_CODES.keySet().equals(MENU_LABELS.keySet()); assert MENU_OPTIONS.countEntries() == MENU_CODES.size(); assert new HashSet<MenuOption>(Arrays.asList(MenuOption.values())).equals(MENU_LABELS.keySet()); } /** * A unique id for this builder. */ private final String id; private final BlipViewBuilder.Css css; // // Intrinsic state. // private String time; private String metaline; private String avatarUrl; private boolean read = true; private final Set<MenuOption> options = EnumSet.allOf(MenuOption.class); private final Set<MenuOption> selected = EnumSet.noneOf(MenuOption.class); // // Structural components. // private final UiBuilder content; /** * Creates a new blip view builder with the given id. * * @param id unique id for this builder, it must only contains alphanumeric * characters */ public static BlipMetaViewBuilder create(String id, UiBuilder content) { return new BlipMetaViewBuilder(WavePanelResourceLoader.getBlip().css(), id, nonNull(content)); } @VisibleForTesting BlipMetaViewBuilder(BlipViewBuilder.Css css, String id, UiBuilder content) { // must not contain ', it is especially troublesome because it cause // security issues. Preconditions.checkArgument(!id.contains("\'")); this.css = css; this.id = id; this.content = content; } @Override public void setAvatar(String avatarUrl) { this.avatarUrl = avatarUrl; } @Override public void setTime(String time) { this.time = time; } @Override public void setMetaline(String metaline) { this.metaline = metaline; } @Override public void setRead(boolean read) { this.read = read; } @Override public void enable(Set<MenuOption> options) { this.options.addAll(options); } @Override public void disable(Set<MenuOption> options) { this.options.removeAll(options); this.selected.removeAll(options); } public void select(MenuOption option) { this.selected.add(option); } public void deselect(MenuOption option) { this.selected.remove(option); } // // DomImpl nature. // @Override public void outputHtml(SafeHtmlBuilder output) { // HACK HACK HACK // This code should be automatically generated from UiBinder template, not // hand written. open(output, id, css.meta(), TypeCodes.kind(Type.META)); { // Author avatar. image(output, Components.AVATAR.getDomId(id), css.avatar(), EscapeUtils.fromString(avatarUrl), EscapeUtils.fromPlainText("author"), null); // Metabar. open(output, Components.METABAR.getDomId(id), css.metabar() + " " + (read ? css.read() : css.unread()), null); { open(output, Components.MENU.getDomId(id), css.menu(), null); menuBuilder(options, selected, css).outputHtml(output); close(output); // Time. open(output, Components.TIME.getDomId(id), css.time(), null); if (time != null) { output.appendEscaped(time); } close(output); // Metaline. open(output, Components.METALINE.getDomId(id), css.metaline(), null); if (metaline != null) { output.appendEscaped(metaline); } close(output); } close(output); // Content. open(output, Components.CONTENT.getDomId(id), css.contentContainer(), "document"); content.outputHtml(output); close(output); } close(output); } /** * Creates a builder for a blip menu. */ public static UiBuilder menuBuilder(final Set<MenuOption> options, final Set<MenuOption> selected, final BlipViewBuilder.Css css) { return new UiBuilder() { @Override public void outputHtml(SafeHtmlBuilder out) { for (MenuOption option : options) { out.append(EscapeUtils.fromSafeConstant("|")); String style = selected.contains(option) // ? css.menuOption() + css.menuOptionSelected() : css.menuOption(); String extra = OPTION_ID_ATTRIBUTE + "='" + MENU_CODES.get(option).asString() + "'" + (selected.contains(option) ? " " + OPTION_SELECTED_ATTRIBUTE + "='s'" : ""); openSpanWith(out, null, style, TypeCodes.kind(Type.MENU_ITEM), extra); out.append(MENU_LABELS.get(option)); closeSpan(out); } } }; } public static MenuOption getMenuOption(String id) { MenuOption option = MENU_OPTIONS.get(id); if (option == null) { throw new IllegalArgumentException("No such option: " + id); } return option; } public static SafeHtml getMenuOptionId(MenuOption option) { SafeHtml code = MENU_CODES.get(option); if (code == null) { throw new IllegalArgumentException("No such option: " + option); } return code; } }