Author: vjrj_ Date: 2012-05-30 12:46:29 +0200 (Wed, 30 May 2012) New Revision: 1877 Added: trunk/src/main/java/cc/kune/wave/client/WaveUnsavedIndicator.java Modified: trunk/src/main/java/cc/kune/core/client/sn/actions/SessionAction.java trunk/src/main/java/cc/kune/gspace/client/viewers/ContentViewerPanel.java trunk/src/main/java/cc/kune/wave/client/KuneStagesProvider.java trunk/src/main/java/cc/kune/wave/client/WebClient.java Log: CLOSED - # 278: Implements a custom UnsavedDataListener http://kune.ourproject.org/issues/ticket/278 Modified: trunk/src/main/java/cc/kune/core/client/sn/actions/SessionAction.java =================================================================== --- trunk/src/main/java/cc/kune/core/client/sn/actions/SessionAction.java 2012-05-24 13:19:59 UTC (rev 1876) +++ trunk/src/main/java/cc/kune/core/client/sn/actions/SessionAction.java 2012-05-30 10:46:29 UTC (rev 1877) @@ -27,9 +27,22 @@ import com.google.inject.Inject; +/** + * The Class SessionAction is used to create actions that depends of current + * session auth status + */ public abstract class SessionAction extends AbstractExtendedAction { + protected final Session session; + /** + * Instantiates a new session action. + * + * @param session + * the kune session + * @param authNeed + * if user authentication is needed + */ @Inject public SessionAction(final Session session, final boolean authNeed) { this.session = session; @@ -42,6 +55,14 @@ }); } + /** + * Refresh status. + * + * @param authNeed + * the auth need + * @param isLogged + * the is logged + */ public void refreshStatus(final boolean authNeed, final boolean isLogged) { boolean visible = false; final boolean noLogged = !isLogged; @@ -54,6 +75,12 @@ setVisible(visible); } + /** + * Sets the visible. + * + * @param visible + * the new visible + */ public void setVisible(final boolean visible) { setEnabled(visible); putValue(GuiActionDescrip.VISIBLE, visible); Modified: trunk/src/main/java/cc/kune/gspace/client/viewers/ContentViewerPanel.java =================================================================== --- trunk/src/main/java/cc/kune/gspace/client/viewers/ContentViewerPanel.java 2012-05-24 13:19:59 UTC (rev 1876) +++ trunk/src/main/java/cc/kune/gspace/client/viewers/ContentViewerPanel.java 2012-05-30 10:46:29 UTC (rev 1877) @@ -48,6 +48,7 @@ import cc.kune.wave.client.WaveClientProvider; import cc.kune.wave.client.WaveClientUtils; import cc.kune.wave.client.WaveClientView; +import cc.kune.wave.client.WaveUnsavedIndicator; import cc.kune.wave.client.WebClientMock; import com.google.gwt.core.client.GWT; @@ -80,12 +81,14 @@ @UiField DeckPanel deck; private final ContentDropController dropController; + private FramedPanel dummyWaveFrame; private final GSpaceArmor gsArmor; private final I18nTranslationService i18n; private IdGenerator idGenerator; private Element loading; @UiField InlineHTML onlyViewPanel; + private ProfileManager profiles; private final StateManager stateManager; @@ -94,27 +97,28 @@ private KuneStagesProvider wave; private final WaveClientProvider waveClientProv; - private ImplPanel waveHolder; @UiField ImplPanel waveHolderParent; + private final WaveStore waveStore = new SimpleWaveStore(); + private final WaveUnsavedIndicator waveUnsavedIndicator; + private final Widget widget; - private FramedPanel dummyWaveFrame; - @Inject public ContentViewerPanel(final GSpaceArmor wsArmor, final WaveClientProvider waveClient, final ContentCapabilitiesRegistry capabilitiesRegistry, final I18nTranslationService i18n, final EventBus eventBus, final StateManager stateManager, - final ContentDropController dropController) { + final ContentDropController dropController, final WaveUnsavedIndicator waveUnsavedIndicator) { this.gsArmor = wsArmor; this.waveClientProv = waveClient; this.capabilitiesRegistry = capabilitiesRegistry; this.i18n = i18n; this.stateManager = stateManager; this.dropController = dropController; + this.waveUnsavedIndicator = waveUnsavedIndicator; widget = uiBinder.createAndBindUi(this); contentTitle = new ContentTitleWidget(i18n, gsArmor, capabilitiesRegistry.getIconsRegistry()); eventBus.addHandler(WaveClientClearEvent.getType(), @@ -228,9 +232,10 @@ // UIObject.setVisible(waveFrame.getElement(), true); waveHolder.getElement().appendChild(loading); final Element holder = waveHolder.getElement().appendChild(Document.get().createDivElement()); - final KuneStagesProvider wave = new KuneStagesProvider(holder, (com.google.gwt.dom.client.Element) new Label().getElement(), waveHolder, dummyWaveFrame, waveRef, channel, - idGenerator, profiles, waveStore, isNewWave, - org.waveprotocol.box.webclient.client.Session.get().getDomain(), true, i18n); + final KuneStagesProvider wave = new KuneStagesProvider(holder, new Label().getElement(), + waveHolder, dummyWaveFrame, waveRef, channel, idGenerator, profiles, waveStore, isNewWave, + org.waveprotocol.box.webclient.client.Session.get().getDomain(), true, i18n, + waveUnsavedIndicator); this.wave = wave; wave.load(new Command() { @Override Modified: trunk/src/main/java/cc/kune/wave/client/KuneStagesProvider.java =================================================================== --- trunk/src/main/java/cc/kune/wave/client/KuneStagesProvider.java 2012-05-24 13:19:59 UTC (rev 1876) +++ trunk/src/main/java/cc/kune/wave/client/KuneStagesProvider.java 2012-05-30 10:46:29 UTC (rev 1877) @@ -19,7 +19,6 @@ package cc.kune.wave.client; import org.waveprotocol.box.webclient.client.RemoteViewServiceMultiplexer; -import org.waveprotocol.box.webclient.client.SavedStateIndicator; import org.waveprotocol.box.webclient.client.StageTwoProvider; import org.waveprotocol.box.webclient.client.WindowTitleHandler; import org.waveprotocol.box.webclient.search.WaveContext; @@ -73,31 +72,46 @@ private final static AsyncHolder<Object> HALT = new AsyncHolder<Object>() { @Override - public void call(Accessor<Object> accessor) { + public void call(final Accessor<Object> accessor) { // Never ready, so never notify the accessor. } }; - private final Element wavePanelElement; - private final Element unsavedIndicatorElement; - private final FramedPanel waveFrame; - private final LogicalPanel rootPanel; - private final WaveRef waveRef; + /** + * Finds the blip that should receive the focus and selects it. + */ + private static void selectAndFocusOnBlip(final Reader reader, final ModelAsViewProvider views, + final ConversationView wave, final FocusFramePresenter focusFrame, final WaveRef waveRef) { + final FocusBlipSelector blipSelector = + FocusBlipSelector.create(wave, views, reader, new ViewTraverser()); + final BlipView blipUi = blipSelector.selectBlipByWaveRef(waveRef); + // Focus on the selected blip. + if (blipUi != null) { + focusFrame.focus(blipUi); + } + } private final RemoteViewServiceMultiplexer channel; + private boolean closed; + private final I18nTranslationService i18n; private final IdGenerator idGenerator; - private final ProfileManager profiles; - private final WaveStore waveStore; private final boolean isNewWave; private final String localDomain; + private StageOne one; + private final ProfileManager profiles; + private final LogicalPanel rootPanel; + private final boolean showParticipantsPanel; - private boolean closed; - private StageOne one; + private StageThree three; private StageTwo two; - private StageThree three; + private final Element unsavedIndicatorElement; private WaveContext wave; - private final boolean showParticipantsPanel; - private final I18nTranslationService i18n; + private final FramedPanel waveFrame; + private final Element wavePanelElement; + private final WaveRef waveRef; + private final WaveStore waveStore; + private final WaveUnsavedIndicator waveUnsavedIndicator; + /** * @param wavePanelElement the DOM element to become the wave panel. * @param unsavedIndicatorElement the element that displays the wave saved state. @@ -110,10 +124,10 @@ * @param isNewWave true if the wave is a new client-created wave * @param idGenerator */ - public KuneStagesProvider(Element wavePanelElement, Element unsavedIndicatorElement, - LogicalPanel rootPanel, FramedPanel waveFrame, WaveRef waveRef, RemoteViewServiceMultiplexer channel, - IdGenerator idGenerator, ProfileManager profiles, WaveStore store, boolean isNewWave, - String localDomain, boolean showParticipantsPanel, I18nTranslationService i18n) { + public KuneStagesProvider(final Element wavePanelElement, final Element unsavedIndicatorElement, + final LogicalPanel rootPanel, final FramedPanel waveFrame, final WaveRef waveRef, final RemoteViewServiceMultiplexer channel, + final IdGenerator idGenerator, final ProfileManager profiles, final WaveStore store, final boolean isNewWave, + final String localDomain, final boolean showParticipantsPanel, final I18nTranslationService i18n, final WaveUnsavedIndicator waveUnsavedIndicator) { this.wavePanelElement = wavePanelElement; this.unsavedIndicatorElement = unsavedIndicatorElement; this.waveFrame = waveFrame; @@ -127,49 +141,25 @@ this.localDomain = localDomain; this.showParticipantsPanel = showParticipantsPanel; this.i18n = i18n; + this.waveUnsavedIndicator = waveUnsavedIndicator; } @Override - protected AsyncHolder<StageZero> createStageZeroLoader() { - return haltIfClosed(super.createStageZeroLoader()); - } - - @Override - protected AsyncHolder<StageOne> createStageOneLoader(StageZero zero) { + protected AsyncHolder<StageOne> createStageOneLoader(final StageZero zero) { return haltIfClosed(new StageOne.DefaultProvider(zero) { @Override - protected Element createWaveHolder() { - return wavePanelElement; + protected LogicalPanel createWaveContainer() { + return rootPanel; } @Override - protected LogicalPanel createWaveContainer() { - return rootPanel; + protected Element createWaveHolder() { + return wavePanelElement; } }); } @Override - protected AsyncHolder<StageTwo> createStageTwoLoader(StageOne one) { - return haltIfClosed(new StageTwoProvider(this.one = one, waveRef, channel, isNewWave, - idGenerator, profiles, new SavedStateIndicator(unsavedIndicatorElement)) { - // Kune patch - @Override - protected DomRenderer createRenderer() { - return KuneFullDomWaveRendererImpl.create(getConversations(), getProfileManager(), - getBlipDetailer(), getViewIdMapper(), getBlipQueue(), getThreadReadStateMonitor(), - createViewFactories(), showParticipantsPanel); - - // Warning: this run into issue #73 - // @Override - // protected void installFeatures() { - // WavePanelResourceLoader.loadCss(); - // // KuneWavePanelResourceLoader.loadCss(); - // } - - }});} - - @Override protected AsyncHolder<StageThree> createStageThreeLoader(final StageTwo two) { return haltIfClosed(new StageThree.DefaultProvider(this.two = two) { @Override @@ -177,7 +167,7 @@ // Prepend an init wave flow onto the stage continuation. super.create(new Accessor<StageThree>() { @Override - public void use(StageThree x) { + public void use(final StageThree x) { onStageThreeLoaded(x, whenReady); } }); @@ -216,53 +206,32 @@ }); } - - private void onStageThreeLoaded(StageThree x, Accessor<StageThree> whenReady) { - if (closed) { - // Stop the loading process. - return; - } - three = x; - if (isNewWave) { - initNewWave(x); - } else { - handleExistingWave(x); - } - wave = new WaveContext( - two.getWave(), two.getConversations(), two.getSupplement(), two.getReadMonitor()); - waveStore.add(wave); - install(); - whenReady.use(x); - } - private void initNewWave(StageThree three) { - // Do the new-wave flow. - ModelAsViewProvider views = two.getModelAsViewProvider(); - BlipQueueRenderer blipQueue = two.getBlipQueue(); - ConversationView wave = two.getConversations(); + @Override + protected AsyncHolder<StageTwo> createStageTwoLoader(final StageOne one) { + return haltIfClosed(new StageTwoProvider(this.one = one, waveRef, channel, isNewWave, + idGenerator, profiles, waveUnsavedIndicator) { + // Kune patch + @Override + protected DomRenderer createRenderer() { + return KuneFullDomWaveRendererImpl.create(getConversations(), getProfileManager(), + getBlipDetailer(), getViewIdMapper(), getBlipQueue(), getThreadReadStateMonitor(), + createViewFactories(), showParticipantsPanel); + + // Warning: this run into issue #73 + // @Override + // protected void installFeatures() { + // WavePanelResourceLoader.loadCss(); + // // KuneWavePanelResourceLoader.loadCss(); + // } - // Force rendering to finish. - blipQueue.flush(); - BlipView blipUi = views.getBlipView(wave.getRoot().getRootThread().getFirstBlip()); - three.getEditActions().startEditing(blipUi); - } + }});} - private void handleExistingWave(StageThree three) { - if (waveRef.hasDocumentId()) { - BlipQueueRenderer blipQueue = two.getBlipQueue(); - blipQueue.flush(); - selectAndFocusOnBlip(two.getReader(), two.getModelAsViewProvider(), two.getConversations(), - one.getFocusFrame(), waveRef); + @Override + protected AsyncHolder<StageZero> createStageZeroLoader() { + return haltIfClosed(super.createStageZeroLoader()); } - } - - /** - * A hook to install features that are not dependent an a certain stage. - */ - protected void install() { - WindowTitleHandler.install(waveStore, waveFrame); - } - + public void destroy() { if (wave != null) { waveStore.remove(wave); @@ -294,26 +263,58 @@ } closed = true; } - /** - * Finds the blip that should receive the focus and selects it. - */ - private static void selectAndFocusOnBlip(Reader reader, ModelAsViewProvider views, - ConversationView wave, FocusFramePresenter focusFrame, WaveRef waveRef) { - FocusBlipSelector blipSelector = - FocusBlipSelector.create(wave, views, reader, new ViewTraverser()); - BlipView blipUi = blipSelector.selectBlipByWaveRef(waveRef); - // Focus on the selected blip. - if (blipUi != null) { - focusFrame.focus(blipUi); - } - } /** * @return a halting provider if this stage is closed. Otherwise, returns the * given provider. */ @SuppressWarnings("unchecked") // HALT is safe as a holder for any type - private <T> AsyncHolder<T> haltIfClosed(AsyncHolder<T> provider) { + private <T> AsyncHolder<T> haltIfClosed(final AsyncHolder<T> provider) { return closed ? (AsyncHolder<T>) HALT : provider; } + + private void handleExistingWave(final StageThree three) { + if (waveRef.hasDocumentId()) { + final BlipQueueRenderer blipQueue = two.getBlipQueue(); + blipQueue.flush(); + selectAndFocusOnBlip(two.getReader(), two.getModelAsViewProvider(), two.getConversations(), + one.getFocusFrame(), waveRef); + } + } + + private void initNewWave(final StageThree three) { + // Do the new-wave flow. + final ModelAsViewProvider views = two.getModelAsViewProvider(); + final BlipQueueRenderer blipQueue = two.getBlipQueue(); + final ConversationView wave = two.getConversations(); + + // Force rendering to finish. + blipQueue.flush(); + final BlipView blipUi = views.getBlipView(wave.getRoot().getRootThread().getFirstBlip()); + three.getEditActions().startEditing(blipUi); + } + /** + * A hook to install features that are not dependent an a certain stage. + */ + protected void install() { + WindowTitleHandler.install(waveStore, waveFrame); + } + + private void onStageThreeLoaded(final StageThree x, final Accessor<StageThree> whenReady) { + if (closed) { + // Stop the loading process. + return; + } + three = x; + if (isNewWave) { + initNewWave(x); + } else { + handleExistingWave(x); + } + wave = new WaveContext( + two.getWave(), two.getConversations(), two.getSupplement(), two.getReadMonitor()); + waveStore.add(wave); + install(); + whenReady.use(x); + } } Added: trunk/src/main/java/cc/kune/wave/client/WaveUnsavedIndicator.java =================================================================== --- trunk/src/main/java/cc/kune/wave/client/WaveUnsavedIndicator.java (rev 0) +++ trunk/src/main/java/cc/kune/wave/client/WaveUnsavedIndicator.java 2012-05-30 10:46:29 UTC (rev 1877) @@ -0,0 +1,126 @@ +/* + * Copyright 2011 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 cc.kune.wave.client; + +import org.waveprotocol.wave.client.scheduler.Scheduler; +import org.waveprotocol.wave.client.scheduler.SchedulerInstance; +import org.waveprotocol.wave.client.scheduler.TimerService; +import org.waveprotocol.wave.concurrencycontrol.common.UnsavedDataListener; + +import cc.kune.common.client.notify.NotifyUser; +import cc.kune.core.client.i18n.I18n; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +@Singleton +public class WaveUnsavedIndicator implements UnsavedDataListener { + + private enum SavedState { + SAVED, UNSAVED; + } + + private static final int UPDATE_DELAY_MS = 300; + + private SavedState currentSavedState = null; + + private final TimerService scheduler; + + private final Scheduler.Task updateTask = new Scheduler.Task() { + @Override + public void execute() { + updateDisplay(); + } + }; + + private SavedState visibleSavedState = SavedState.SAVED; + + /** + * Simple saved state indicator. + * + * @author [email protected] (Daniel Danilatos) + * @author [email protected] (Yuri Zelikov) + */ + @Inject + public WaveUnsavedIndicator() { + this.scheduler = SchedulerInstance.getLowPriorityTimer(); + } + + private void maybeUpdateDisplay() { + if (needsUpdating()) { + switch (currentSavedState) { + case SAVED: + scheduler.scheduleDelayed(updateTask, UPDATE_DELAY_MS); + break; + case UNSAVED: + updateDisplay(); + break; + default: + throw new AssertionError("unknown " + currentSavedState); + } + } else { + scheduler.cancel(updateTask); + } + } + + private boolean needsUpdating() { + return visibleSavedState != currentSavedState; + } + + @Override + public void onClose(final boolean everythingCommitted) { + if (everythingCommitted) { + saved(); + } else { + unsaved(); + } + } + + @Override + public void onUpdate(final UnsavedDataInfo unsavedDataInfo) { + if (unsavedDataInfo.estimateUnacknowledgedSize() != 0) { + currentSavedState = SavedState.UNSAVED; + unsaved(); + } else { + currentSavedState = SavedState.SAVED; + saved(); + } + } + + public void saved() { + maybeUpdateDisplay(); + } + + public void unsaved() { + maybeUpdateDisplay(); + } + + private void updateDisplay() { + visibleSavedState = currentSavedState; + switch (visibleSavedState) { + case SAVED: + NotifyUser.hideProgress(); + break; + case UNSAVED: + NotifyUser.showProgress(I18n.t("Saving")); + break; + default: + throw new AssertionError("unknown " + currentSavedState); + } + } + +} Modified: trunk/src/main/java/cc/kune/wave/client/WebClient.java =================================================================== --- trunk/src/main/java/cc/kune/wave/client/WebClient.java 2012-05-24 13:19:59 UTC (rev 1876) +++ trunk/src/main/java/cc/kune/wave/client/WebClient.java 2012-05-30 10:46:29 UTC (rev 1877) @@ -106,27 +106,126 @@ interface Binder extends UiBinder<DockLayoutPanel, WebClient> { } + /** + * An exception handler that reports exceptions using a <em>shiny banner</em> + * (an alert placed on the top of the screen). Once the stack trace is + * prepared, it is revealed in the banner via a link. + */ + public static class ErrorHandler implements UncaughtExceptionHandler { + public static void getStackTraceAsync(final Throwable t, final Accessor<SafeHtml> whenReady) { + // TODO: Request stack-trace de-obfuscation. For now, just use the + // javascript stack trace. + // + // Use minimal services here, in order to avoid the chance that reporting + // the error produces more errors. In particular, do not use WIAB's + // scheduler to run this command. + // Also, this code could potentially be put behind a runAsync boundary, to + // save whatever dependencies it uses from the initial download. + new Timer() { + @Override + public void run() { + final SafeHtmlBuilder stack = new SafeHtmlBuilder(); + + Throwable error = t; + while (error != null) { + final String token = String.valueOf((new Date()).getTime()); + stack.appendHtmlConstant("Token: " + token + "<br> "); + stack.appendEscaped(String.valueOf(error.getMessage())).appendHtmlConstant("<br>"); + for (final StackTraceElement elt : error.getStackTrace()) { + stack.appendHtmlConstant(" ") + .appendEscaped(maybe(elt.getClassName(), "??")).appendHtmlConstant(".") // + .appendEscaped(maybe(elt.getMethodName(), "??")).appendHtmlConstant(" (") // + .appendEscaped(maybe(elt.getFileName(), "??")).appendHtmlConstant(":") // + .appendEscaped(maybe(elt.getLineNumber(), "??")).appendHtmlConstant(")") // + .appendHtmlConstant("<br>"); + } + error = error.getCause(); + if (error != null) { + stack.appendHtmlConstant("Caused by: "); + } + } + + whenReady.use(stack.toSafeHtml()); + } + }.schedule(1); + } + + public static void install() { + GWT.setUncaughtExceptionHandler(new ErrorHandler(GWT.getUncaughtExceptionHandler())); + } + + private static String maybe(final int value, final String otherwise) { + return value != -1 ? String.valueOf(value) : otherwise; + } + + private static String maybe(final String value, final String otherwise) { + return value != null ? value : otherwise; + } + + /** + * Indicates whether an error has already been reported (at most one error + * is ever reported by this handler). + */ + private boolean hasFired; + + /** Next handler in the handler chain. */ + private final UncaughtExceptionHandler next; + + private ErrorHandler(final UncaughtExceptionHandler next) { + this.next = next; + } + + @Override + public void onUncaughtException(final Throwable e) { + if (!hasFired) { + hasFired = true; + // final ErrorIndicatorPresenter error = + // ErrorIndicatorPresenter.create(RootPanel.get("banner")); + getStackTraceAsync(e, new Accessor<SafeHtml>() { + @Override + public void use(final SafeHtml stack) { + // error.addDetail(stack, null); + // REMOTE_LOG.severe(stack.asString().replace("<br>", "\n")); + final String message = stack.asString().replace("<br>", "\n"); + REMOTE_LOG.severe(message); + NotifyUser.logError(message); + NotifyUser.showProgress("Error"); + new Timer() { + @Override + public void run() { + NotifyUser.hideProgress(); + }}.schedule(5000); + } + }); + } + + if (next != null) { + next.onUncaughtException(e); + } + } + } + interface Style extends CssResource { } private static final Binder BINDER = GWT.create(Binder.class); + static Log LOG = Log.get(WebClient.class); - static Log LOG = Log.get(WebClient.class); // Use of GWT logging is only intended for sending exception reports to the // server, nothing else in the client should use java.util.logging. // Please also see WebClientDemo.gwt.xml. private static final Logger REMOTE_LOG = Logger.getLogger("REMOTE_LOG"); - /** Creates a popup that warns about network disconnects. */ private static UniversalPopup createTurbulencePopup() { - PopupChrome chrome = PopupChromeFactory.createPopupChrome(); - UniversalPopup popup = + final PopupChrome chrome = PopupChromeFactory.createPopupChrome(); + final UniversalPopup popup = PopupFactory.createPopup(null, new CenterPopupPositioner(), chrome, true); popup.add(new HTML("<div style='color: red; padding: 5px; text-align: center;'>" + "<b>A turbulence detected!<br></br>" + " Please save your last changes to somewhere and reload the wave.</b></div>")); return popup; } + private RemoteViewServiceMultiplexer channel; private final EventBus eventBus; @@ -136,8 +235,8 @@ private IdGenerator idGenerator; private final InboxCountPresenter inboxCount; + private final Element loading = new LoadingIndicator().getElement(); - private final Element loading = new LoadingIndicator().getElement(); private ParticipantId loggedInUser; @UiField @@ -148,10 +247,10 @@ @UiField(provided = true) final SearchPanelWidget searchPanel; + @UiField SplitLayoutPanel splitPanel; - @UiField Style style; @@ -162,9 +261,11 @@ @UiField FramedPanel waveFrame; - ImplPanel waveHolder; private final WaveStore waveStore = new SimpleWaveStore(); + + private final WaveUnsavedIndicator waveUnsavedIndicator; + /** * Create a remote websocket to talk to the server-side FedOne service. */ @@ -174,11 +275,12 @@ * This is the entry point method. */ @Inject - public WebClient(final EventBus eventBus, final KuneWaveProfileManager profiles, final InboxCountPresenter inboxCount, final TokenMatcher tokenMatcher, final cc.kune.core.client.state.Session session, final I18nTranslationService i18n) { + public WebClient(final EventBus eventBus, final KuneWaveProfileManager profiles, final InboxCountPresenter inboxCount, final TokenMatcher tokenMatcher, final cc.kune.core.client.state.Session session, final I18nTranslationService i18n, final WaveUnsavedIndicator waveUnsavedIndicator) { this.eventBus = eventBus; this.profiles = profiles; this.inboxCount = inboxCount; this.i18n = i18n; + this.waveUnsavedIndicator = waveUnsavedIndicator; searchPanel = new SearchPanelWidget(new SearchPanelRenderer(profiles)); ErrorHandler.install(); eventBus.addHandler(StackErrorEvent.getType(), new StackErrorEvent.StackErrorHandler() { @@ -268,12 +370,27 @@ public void getStackTraceAsync(final Throwable caught, final Accessor<SafeHtml> accessor) { ErrorHandler.getStackTraceAsync(caught, accessor); } - @Override public ImplPanel getWaveHolder() { return waveHolder; } + @Override + public WaveWebSocketClient getWebSocket() { + return websocket; + } + + + /** + * Returns <code>ws(s)://yourhost[:port]/</code>. + */ + // XXX check formatting wrt GPE + private native String getWebSocketBaseUrl(String moduleBase) /*-{ + return ((window.location.protocol == "https:") ? "wss" : "ws") + + /:\/\/[^\/]+/.exec(moduleBase)[0] + "/"; + }-*/; + + @Override public void login() { loginImpl(); } @@ -287,6 +404,12 @@ } } + /** + */ + private void loginToServer() { + assert loggedInUser != null; + channel = new RemoteViewServiceMultiplexer(websocket, loggedInUser.getAddress()); + } @Override public void logout() { @@ -297,130 +420,13 @@ clear(); } - @Override - public void setMaximized(final boolean maximized) { - splitPanel.setWidgetSize(searchPanel, maximized ? 0 : 400); - } - - private void setupUi() { - // Set up UI - DockLayoutPanel self = BINDER.createAndBindUi(this); - // kune-patch - // RootPanel.get("app").add(self); - initWidget(self); - waveHolder = new ImplPanel(""); - waveHolder.addStyleName("k-waveHolder"); - waveFrame.add(waveHolder); - // DockLayoutPanel forcibly conflicts with sensible layout control, and - // sticks inline styles on elements without permission. They must be - // cleared. - self.getElement().getStyle().clearPosition(); - splitPanel.setWidgetMinSize(searchPanel, 300); - - if (LogLevel.showDebug()) { - logPanel.enable(); - } else { - logPanel.removeFromParent(); - } - - setupSearchPanel(); - setupWavePanel(); - } - - @Override - public WaveWebSocketClient getWebSocket() { - return websocket; - } - - private void setupSearchPanel() { - // On wave action fire an event. - SearchPresenter.WaveActionHandler actionHandler = - new SearchPresenter.WaveActionHandler() { - @Override - public void onCreateWave() { - ClientEvents.get().fireEvent(WaveCreationEvent.CREATE_NEW_WAVE); - } - - @Override - public void onWaveSelected(WaveId id) { - ClientEvents.get().fireEvent(new WaveSelectionEvent(WaveRef.of(id))); - } - }; - Search search = SimpleSearch.create(RemoteSearchService.create(), waveStore); - search.addListener(inboxCount.getSearchListener()); - SearchPresenter.create(search, searchPanel, actionHandler, profiles); - } - - private void setupWavePanel() { - // Hide the frame until waves start getting opened. - UIObject.setVisible(waveFrame.getElement(), false); - - // Handles opening waves. - ClientEvents.get().addWaveSelectionEventHandler(new WaveSelectionEventHandler() { - @Override - public void onSelection(WaveRef waveRef) { - openWave(waveRef, false); - } - }); - } - - private void setupConnectionIndicator() { - ClientEvents.get().addNetworkStatusEventHandler(new NetworkStatusEventHandler() { - - boolean isTurbulenceDetected = false; - - @Override - public void onNetworkStatus(NetworkStatusEvent event) { - Element element = Document.get().getElementById("netstatus"); - if (element != null) { - switch (event.getStatus()) { - case CONNECTED: - case RECONNECTED: - element.setInnerText("Online"); - element.setClassName("online"); - isTurbulenceDetected = false; - turbulencePopup.hide(); - break; - case DISCONNECTED: - element.setInnerText("Offline"); - element.setClassName("offline"); - if (!isTurbulenceDetected) { - isTurbulenceDetected = true; - turbulencePopup.show(); - } - break; - case RECONNECTING: - element.setInnerText("Connecting..."); - element.setClassName("connecting"); - break; - } - } - } - }); - } - /** - * Returns <code>ws(s)://yourhost[:port]/</code>. - */ - // XXX check formatting wrt GPE - private native String getWebSocketBaseUrl(String moduleBase) /*-{return ((window.location.protocol == "https:") ? "wss" : "ws") + /:\/\/[^\/]+/.exec(moduleBase)[0] + "/";}-*/; - - private native boolean useSocketIO() /*-{ return !window.WebSocket }-*/; - - /** - */ - private void loginToServer() { - assert loggedInUser != null; - channel = new RemoteViewServiceMultiplexer(websocket, loggedInUser.getAddress()); - } - - /** * Shows a wave in a wave panel. * * @param waveRef wave id to open * @param isNewWave whether the wave is being created by this client session. */ - private void openWave(WaveRef waveRef, boolean isNewWave) { + private void openWave(final WaveRef waveRef, final boolean isNewWave) { LOG.info("WebClient.openWave()"); WaveClientClearEvent.fire(eventBus); @@ -430,9 +436,9 @@ // Release the display:none. UIObject.setVisible(waveFrame.getElement(), true); waveHolder.getElement().appendChild(loading); - Element holder = waveHolder.getElement().appendChild(Document.get().createDivElement()); - KuneStagesProvider wave = new KuneStagesProvider( - holder, (com.google.gwt.dom.client.Element) new Label().getElement(), waveHolder, waveFrame, waveRef, channel, idGenerator, profiles, waveStore, isNewWave, Session.get().getDomain(), true, i18n); + final Element holder = waveHolder.getElement().appendChild(Document.get().createDivElement()); + final KuneStagesProvider wave = new KuneStagesProvider( + holder, new Label().getElement(), waveHolder, waveFrame, waveRef, channel, idGenerator, profiles, waveStore, isNewWave, Session.get().getDomain(), true, i18n, waveUnsavedIndicator); this.wave = wave; wave.load(new Command() { @Override @@ -446,7 +452,7 @@ WaveRef fromWaveRef; try { fromWaveRef = GwtWaverefEncoder.decodeWaveRefFromPath(encodedToken); - } catch (InvalidWaveRefException e) { + } catch (final InvalidWaveRefException e) { LOG.info("History token contains invalid path: " + encodedToken); return; } @@ -461,102 +467,104 @@ SpaceConfEvent.fire(eventBus, Space.userSpace, tokenFromWaveref); History.newItem(tokenFromWaveref, false); } - /** - * An exception handler that reports exceptions using a <em>shiny banner</em> - * (an alert placed on the top of the screen). Once the stack trace is - * prepared, it is revealed in the banner via a link. - */ - public static class ErrorHandler implements UncaughtExceptionHandler { - public static void getStackTraceAsync(final Throwable t, final Accessor<SafeHtml> whenReady) { - // TODO: Request stack-trace de-obfuscation. For now, just use the - // javascript stack trace. - // - // Use minimal services here, in order to avoid the chance that reporting - // the error produces more errors. In particular, do not use WIAB's - // scheduler to run this command. - // Also, this code could potentially be put behind a runAsync boundary, to - // save whatever dependencies it uses from the initial download. - new Timer() { - @Override - public void run() { - SafeHtmlBuilder stack = new SafeHtmlBuilder(); + + @Override + public void setMaximized(final boolean maximized) { + splitPanel.setWidgetSize(searchPanel, maximized ? 0 : 400); + } - Throwable error = t; - while (error != null) { - String token = String.valueOf((new Date()).getTime()); - stack.appendHtmlConstant("Token: " + token + "<br> "); - stack.appendEscaped(String.valueOf(error.getMessage())).appendHtmlConstant("<br>"); - for (StackTraceElement elt : error.getStackTrace()) { - stack.appendHtmlConstant(" ") - .appendEscaped(maybe(elt.getClassName(), "??")).appendHtmlConstant(".") // - .appendEscaped(maybe(elt.getMethodName(), "??")).appendHtmlConstant(" (") // - .appendEscaped(maybe(elt.getFileName(), "??")).appendHtmlConstant(":") // - .appendEscaped(maybe(elt.getLineNumber(), "??")).appendHtmlConstant(")") // - .appendHtmlConstant("<br>"); - } - error = error.getCause(); - if (error != null) { - stack.appendHtmlConstant("Caused by: "); - } + private void setupConnectionIndicator() { + ClientEvents.get().addNetworkStatusEventHandler(new NetworkStatusEventHandler() { + + boolean isTurbulenceDetected = false; + + @Override + public void onNetworkStatus(final NetworkStatusEvent event) { + final Element element = Document.get().getElementById("netstatus"); + if (element != null) { + switch (event.getStatus()) { + case CONNECTED: + case RECONNECTED: + element.setInnerText("Online"); + element.setClassName("online"); + isTurbulenceDetected = false; + turbulencePopup.hide(); + break; + case DISCONNECTED: + element.setInnerText("Offline"); + element.setClassName("offline"); + if (!isTurbulenceDetected) { + isTurbulenceDetected = true; + turbulencePopup.show(); + } + break; + case RECONNECTING: + element.setInnerText("Connecting..."); + element.setClassName("connecting"); + break; } - - whenReady.use(stack.toSafeHtml()); } - }.schedule(1); - } + } + }); + } + + private void setupSearchPanel() { + // On wave action fire an event. + final SearchPresenter.WaveActionHandler actionHandler = + new SearchPresenter.WaveActionHandler() { + @Override + public void onCreateWave() { + ClientEvents.get().fireEvent(WaveCreationEvent.CREATE_NEW_WAVE); + } - public static void install() { - GWT.setUncaughtExceptionHandler(new ErrorHandler(GWT.getUncaughtExceptionHandler())); + @Override + public void onWaveSelected(final WaveId id) { + ClientEvents.get().fireEvent(new WaveSelectionEvent(WaveRef.of(id))); + } + }; + final Search search = SimpleSearch.create(RemoteSearchService.create(), waveStore); + search.addListener(inboxCount.getSearchListener()); + SearchPresenter.create(search, searchPanel, actionHandler, profiles); + } + + private void setupUi() { + // Set up UI + final DockLayoutPanel self = BINDER.createAndBindUi(this); + // kune-patch + // RootPanel.get("app").add(self); + initWidget(self); + waveHolder = new ImplPanel(""); + waveHolder.addStyleName("k-waveHolder"); + waveFrame.add(waveHolder); + // DockLayoutPanel forcibly conflicts with sensible layout control, and + // sticks inline styles on elements without permission. They must be + // cleared. + self.getElement().getStyle().clearPosition(); + splitPanel.setWidgetMinSize(searchPanel, 300); + + if (LogLevel.showDebug()) { + logPanel.enable(); + } else { + logPanel.removeFromParent(); } + + setupSearchPanel(); + setupWavePanel(); + } - private static String maybe(final int value, final String otherwise) { - return value != -1 ? String.valueOf(value) : otherwise; - } + private void setupWavePanel() { + // Hide the frame until waves start getting opened. + UIObject.setVisible(waveFrame.getElement(), false); - private static String maybe(final String value, final String otherwise) { - return value != null ? value : otherwise; - } - - /** - * Indicates whether an error has already been reported (at most one error - * is ever reported by this handler). - */ - private boolean hasFired; - - /** Next handler in the handler chain. */ - private final UncaughtExceptionHandler next; - - private ErrorHandler(final UncaughtExceptionHandler next) { - this.next = next; - } - - @Override - public void onUncaughtException(final Throwable e) { - if (!hasFired) { - hasFired = true; - // final ErrorIndicatorPresenter error = - // ErrorIndicatorPresenter.create(RootPanel.get("banner")); - getStackTraceAsync(e, new Accessor<SafeHtml>() { - @Override - public void use(final SafeHtml stack) { - // error.addDetail(stack, null); - // REMOTE_LOG.severe(stack.asString().replace("<br>", "\n")); - final String message = stack.asString().replace("<br>", "\n"); - REMOTE_LOG.severe(message); - NotifyUser.logError(message); - NotifyUser.showProgress("Error"); - new Timer() { - @Override - public void run() { - NotifyUser.hideProgress(); - }}.schedule(5000); - } - }); + // Handles opening waves. + ClientEvents.get().addWaveSelectionEventHandler(new WaveSelectionEventHandler() { + @Override + public void onSelection(final WaveRef waveRef) { + openWave(waveRef, false); } - - if (next != null) { - next.onUncaughtException(e); - } - } + }); } + private native boolean useSocketIO() /*-{ + return !window.WebSocket + }-*/; }