// Copyright 2012 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.collide.client.status; import com.google.collide.client.util.Elements; import com.google.collide.json.client.JsoArray; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import elemental.client.Browser; import elemental.html.AnchorElement; import elemental.html.SpanElement; /** * This is the base of all message types. * * All common message properties are set through this base class. Message * events are routed through double-dispatch in the protected do*() methods * while their public facing counterparts ensure that the base message state is * consistent (e.g. setting canceled). */ public class StatusMessage { /** * A loading message indicates that the application is waiting for a slow * operation. * * A confirmation message is an informational message to the user. * * An error message indicates that there has been a recoverable or transient * error. * * A fatal message indicates that the application has entered into an * unrecoverable state (for example, an uncaught exception). Once a fatal * message fires, no other subsequent status messages will fire. * * NOTE: These message types are in order of priority. */ public enum MessageType { LOADING, // CONFIRMATION, // ERROR, // FATAL } // The default delay to use for avoiding message flicker. public static final int DEFAULT_DELAY = 200; public static final StatusAction RELOAD_ACTION = new StatusAction() { @Override public void renderAction(SpanElement actionContainer) { actionContainer.setTextContent("Reload Collide"); } @Override public void onAction() { Browser.getWindow().getLocation().reload(); } }; public static final StatusAction FEEDBACK_ACTION = new StatusAction() { @Override public void renderAction(SpanElement actionContainer) { AnchorElement a = Elements.createAnchorElement(); a.setHref( "https://groups.google.com/forum/?domain=google.com#!newtopic/collide-discussions"); a.setTarget("_blank"); a.setTextContent("Tell us what happened!"); a.getStyle().setColor("yellow"); actionContainer.appendChild(a); } @Override public void onAction() { // Nothing. Let the native anchor do its thing. } }; private final JsoArray<StatusAction> actions = JsoArray.create(); private boolean canceled = false; private boolean dismissable = false; private long expiryTime = 0; private String longText; private final StatusManager statusManager; private String text; private final MessageType type; public StatusMessage(StatusManager statusManager, MessageType type, String text) { this.statusManager = statusManager; this.type = type; this.text = text; } /** * Cancel a message. Once a message is canceled, all subsequent fires are * no-ops. */ public final void cancel() { canceled = true; statusManager.cancel(this); } /** * Cancel an event in the future. * * @param milliseconds time to expiry. */ public final void expire(int milliseconds) { expiryTime = System.currentTimeMillis() + milliseconds; Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { @Override public boolean execute() { cancel(); return false; } }, milliseconds); } /** * Fires a message to the status manager. If the message has been canceled, * this is a no-op. If the message has already been fired, then update that * message. */ public final void fire() { if (!canceled) { statusManager.fire(this); } } /** * Fire a message with a delay. If the message is canceled before the delay, * this is a no-op. * * @param milliseconds time to delay firing this message. */ public final void fireDelayed(int milliseconds) { Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { @Override public boolean execute() { fire(); return false; } }, milliseconds); } /** * @return the actions associated with this message or null */ public JsoArray<StatusAction> getActions() { return actions; } public String getLongText() { return longText == null ? "" : longText; } /** * Concrete implementations can reference the {@link StatusManager} through * this getter. * * @return the @{link StatusManager} for this message. */ protected final StatusManager getStatusManager() { return statusManager; } public String getText() { return text == null ? "" : text; } public void setText(String text) { this.text = text; } /** * @return return the time in milliseconds left until expiration or 0 if there * is no pending expiration. */ public int getTimeToExpiry() { return (int) (expiryTime == 0 ? 0 : Math.max(0, expiryTime - System.currentTimeMillis())); } public MessageType getType() { return type; } /** * @return Can the user manually dismiss this message? */ public boolean isDismissable() { return dismissable; } public void addAction(StatusAction action) { this.actions.add(action); } /** * @param dismissable whether or not this message can be dismissed by the * user. */ public void setDismissable(boolean dismissable) { this.dismissable = dismissable; } /** * The status message must fit on one line, but the message can be expanded to * show more detailed information like a stack trace using long text. * * @param longText */ public void setLongText(String longText) { this.longText = longText; } }