Java tutorial
/** * 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 com.google.livingstories.client.start; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Document; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.livingstories.client.ContentItemType; import com.google.livingstories.client.BaseContentItem; import com.google.livingstories.client.ClientMessageHolder; import com.google.livingstories.client.EventContentItem; import com.google.livingstories.client.LivingStory; import com.google.livingstories.client.LivingStoryRpcService; import com.google.livingstories.client.LivingStoryRpcServiceAsync; import com.google.livingstories.client.NarrativeContentItem; import com.google.livingstories.client.StartPageBundle; import com.google.livingstories.client.lsp.views.ManagementLinks; import com.google.livingstories.client.lsp.views.Resources; import com.google.livingstories.client.util.DateUtil; import com.google.livingstories.client.util.GlobalUtil; import com.google.livingstories.client.util.HistoryManager; import java.util.Date; import java.util.List; import java.util.Map; /** * Living Story start page * * @author ericzhang@google.com (Eric Zhang), nehasingh@google.com (Neha Singh) */ public class StartPage implements EntryPoint { private final LivingStoryRpcServiceAsync livingStoryService = GWT.create(LivingStoryRpcService.class); private static final int ONE_MINUTE_MILLIS = 1000 * 60; private static final int ONE_HOUR_MILLIS = ONE_MINUTE_MILLIS * 60; private static final int ONE_DAY_MILLIS = ONE_HOUR_MILLIS * 24; private static final int ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7; private static final int MONTHS_PER_YEAR = 12; private static final int YEAR_CUTOFF = MONTHS_PER_YEAR; private VerticalPanel startPageWidget; @Override public void onModuleLoad() { // Inject the contents of the CSS file // TODO: extract start page styles into its own resource so this doesn't import // from the client/lsp/views package. Resources.INSTANCE.css().ensureInjected(); RootPanel.get("managementLinks").add(new ManagementLinks()); startPageWidget = new VerticalPanel(); startPageWidget.setWidth("100%"); RootPanel.get("storyList").add(startPageWidget); String title = ClientMessageHolder.consts.startPageTitle(); Document doc = Document.get(); doc.setTitle(title); doc.getElementById("logoImage").setAttribute("alt", title); livingStoryService.getStartPageBundle(new AsyncCallback<StartPageBundle>() { @Override public void onFailure(Throwable t) { startPageWidget.add(new Label(ClientMessageHolder.consts.startPageLoadFailed())); } @Override public void onSuccess(StartPageBundle bundle) { populate(bundle.getStories(), bundle.getStoryIdToUpdateMap()); } }); } private void populate(List<LivingStory> stories, Map<Long, List<BaseContentItem>> storyIdToUpdateMap) { for (LivingStory story : stories) { GlobalUtil.addIfNotNull(startPageWidget, createStoryWidget(story, storyIdToUpdateMap.get(story.getId()))); } } private Widget createStoryWidget(LivingStory story, List<BaseContentItem> updates) { if (updates.isEmpty()) { return null; } VerticalPanel panel = new VerticalPanel(); panel.setWidth("100%"); DOM.setStyleAttribute(panel.getElement(), "marginBottom", "15px"); Anchor storyName = new Anchor(story.getTitle(), getStoryUrl(story)); storyName.addStyleName("startPageStoryName"); Label updateCount = getUpdateRecencyLabel( DateUtil.laterDate(updates.get(0).getTimestamp(), story.getLastChangeTimestamp())); updateCount.setWordWrap(false); HorizontalPanel header = new HorizontalPanel(); header.setWidth("100%"); header.addStyleName("sectionHeader"); DOM.setStyleAttribute(header.getElement(), "marginBottom", "10px"); header.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT); header.add(storyName); header.add(updateCount); header.setCellHorizontalAlignment(updateCount, HorizontalPanel.ALIGN_RIGHT); panel.add(header); for (BaseContentItem update : updates) { Anchor headline = null; if (update.getContentItemType() == ContentItemType.NARRATIVE) { NarrativeContentItem narrative = (NarrativeContentItem) update; headline = new Anchor( narrative.getHeadline() + "<span class=\"greyFont\"> - " + narrative.getNarrativeType().toString(), true, getStoryUrl(story) + getUpdateUrl(narrative)); } else { EventContentItem event = (EventContentItem) update; headline = new Anchor(event.getEventUpdate(), true, getStoryUrl(story) + getUpdateUrl(event)); } DOM.setStyleAttribute(headline.getElement(), "paddingLeft", "5px"); panel.add(headline); Label dateLabel = new Label(DateUtil.formatDate(update.getDateSortKey())); dateLabel.addStyleName("greyFont"); DOM.setStyleAttribute(dateLabel.getElement(), "padding", "0 0 13px 5px"); panel.add(dateLabel); } Anchor viewAll = new Anchor(ClientMessageHolder.consts.startPageViewAll(), true, getStoryUrl(story)); viewAll.setStylePrimaryName("startPageViewAll"); panel.add(viewAll); return panel; } private String getStoryUrl(LivingStory story) { return "lsps/" + story.getUrl(); } private String getUpdateUrl(BaseContentItem update) { return HistoryManager.getTokenStringForFocusedContentItem(update.getId()); } private Label getUpdateRecencyLabel(Date lastUpdateTime) { Date now = new Date(); long millisPassed = now.getTime() - lastUpdateTime.getTime(); int monthsApart = fullMonthsApart(lastUpdateTime, now); String displayString; if (millisPassed < ONE_HOUR_MILLIS) { // "1 minute ago" is the most recent an update gets here. displayString = ClientMessageHolder.msgs .nMinutesAgo(Math.max(intDiv(millisPassed, ONE_MINUTE_MILLIS), 1)); } else if (millisPassed < ONE_DAY_MILLIS) { displayString = ClientMessageHolder.msgs.nHoursAgo(intDiv(millisPassed, ONE_HOUR_MILLIS)); } else if (millisPassed < ONE_WEEK_MILLIS) { displayString = ClientMessageHolder.msgs.nDaysAgo(intDiv(millisPassed, ONE_DAY_MILLIS)); } else if (monthsApart < 1) { displayString = ClientMessageHolder.msgs.nWeeksAgo(intDiv(millisPassed, ONE_WEEK_MILLIS)); } else if (monthsApart < YEAR_CUTOFF) { displayString = ClientMessageHolder.msgs.nMonthsAgo(monthsApart); } else { displayString = ClientMessageHolder.msgs.nYearsAgo(monthsApart / MONTHS_PER_YEAR); } Label label = new Label(displayString); label.addStyleName("normalFontWeight"); return label; } @SuppressWarnings("deprecation") /** returns the number of full months between d1 -- which should be the earlier date -- and d2 */ private int fullMonthsApart(Date d1, Date d2) { int yearsApart = d2.getYear() - d1.getYear(); int fullMonths = 12 * yearsApart; Date d1Modified = new Date(d1.getTime()); d1Modified.setYear(d2.getYear()); int monthsApart = d2.getMonth() - d1Modified.getMonth(); fullMonths += monthsApart; // this could, effectively, be a subtraction d1Modified.setMonth(d2.getMonth()); // if the day, hour, minute, etc.; components of d1Modified actually indicate it's after d2, // we've overcounted the number of full months by 1. if (d1Modified.compareTo(d2) > 0) { fullMonths--; } return fullMonths; } private int intDiv(long n, long d) { long ret = n / d; return ret > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) ret; } }