From aa4c37541ac79c61cc5d45a56ccfe588de718500 Mon Sep 17 00:00:00 2001 From: mdb Date: Fri, 8 Nov 2002 09:14:21 +0000 Subject: [PATCH] Initial version of basic project task management application. git-svn-id: https://samskivert.googlecode.com/svn/trunk@897 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- runtime/twodue/.cvsignore | 1 + runtime/twodue/build.xml | 63 +++++ runtime/twodue/etc/.cvsignore | 1 + runtime/twodue/etc/exceptionmap.properties | 6 + runtime/twodue/etc/messages.properties | 37 +++ runtime/twodue/etc/velocity.properties | 19 ++ runtime/twodue/etc/web.xml | 47 ++++ runtime/twodue/lib/.cvsignore | 1 + runtime/twodue/lib/REQUIRED_LIBS | 6 + .../src/java/com/samskivert/twodue/Log.java | 42 +++ .../java/com/samskivert/twodue/TwoDueApp.java | 102 +++++++ .../java/com/samskivert/twodue/data/Task.java | 35 +++ .../twodue/data/TaskRepository.java | 252 ++++++++++++++++++ .../samskivert/twodue/logic/UserLogic.java | 38 +++ .../com/samskivert/twodue/logic/index.java | 164 ++++++++++++ runtime/twodue/src/sql/tasks-schema.sql | 72 +++++ runtime/twodue/web/app_footer.wm | 3 + runtime/twodue/web/edit.wm | 51 ++++ runtime/twodue/web/footer.wm | 16 ++ runtime/twodue/web/header.wm | 26 ++ runtime/twodue/web/index.wm | 156 +++++++++++ runtime/twodue/web/style.css | 17 ++ 22 files changed, 1155 insertions(+) create mode 100644 runtime/twodue/.cvsignore create mode 100644 runtime/twodue/build.xml create mode 100644 runtime/twodue/etc/.cvsignore create mode 100644 runtime/twodue/etc/exceptionmap.properties create mode 100644 runtime/twodue/etc/messages.properties create mode 100644 runtime/twodue/etc/velocity.properties create mode 100644 runtime/twodue/etc/web.xml create mode 100644 runtime/twodue/lib/.cvsignore create mode 100644 runtime/twodue/lib/REQUIRED_LIBS create mode 100644 runtime/twodue/src/java/com/samskivert/twodue/Log.java create mode 100644 runtime/twodue/src/java/com/samskivert/twodue/TwoDueApp.java create mode 100644 runtime/twodue/src/java/com/samskivert/twodue/data/Task.java create mode 100644 runtime/twodue/src/java/com/samskivert/twodue/data/TaskRepository.java create mode 100644 runtime/twodue/src/java/com/samskivert/twodue/logic/UserLogic.java create mode 100644 runtime/twodue/src/java/com/samskivert/twodue/logic/index.java create mode 100644 runtime/twodue/src/sql/tasks-schema.sql create mode 100644 runtime/twodue/web/app_footer.wm create mode 100644 runtime/twodue/web/edit.wm create mode 100644 runtime/twodue/web/footer.wm create mode 100644 runtime/twodue/web/header.wm create mode 100644 runtime/twodue/web/index.wm create mode 100644 runtime/twodue/web/style.css diff --git a/runtime/twodue/.cvsignore b/runtime/twodue/.cvsignore new file mode 100644 index 00000000..0ac996b7 --- /dev/null +++ b/runtime/twodue/.cvsignore @@ -0,0 +1 @@ +twodue.war diff --git a/runtime/twodue/build.xml b/runtime/twodue/build.xml new file mode 100644 index 00000000..7a173802 --- /dev/null +++ b/runtime/twodue/build.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/runtime/twodue/etc/.cvsignore b/runtime/twodue/etc/.cvsignore new file mode 100644 index 00000000..d887182c --- /dev/null +++ b/runtime/twodue/etc/.cvsignore @@ -0,0 +1 @@ +repository.properties diff --git a/runtime/twodue/etc/exceptionmap.properties b/runtime/twodue/etc/exceptionmap.properties new file mode 100644 index 00000000..7d7db4a4 --- /dev/null +++ b/runtime/twodue/etc/exceptionmap.properties @@ -0,0 +1,6 @@ +# +# $Id: exceptionmap.properties,v 1.1 2002/11/08 09:14:20 mdb Exp $ +# +# Maps exceptions to friendly error messages + +java.sql.SQLException: A database error has occurred: {m} diff --git a/runtime/twodue/etc/messages.properties b/runtime/twodue/etc/messages.properties new file mode 100644 index 00000000..0d9eb334 --- /dev/null +++ b/runtime/twodue/etc/messages.properties @@ -0,0 +1,37 @@ +# -*- mode: makefile -*- +# +# $Id: messages.properties,v 1.1 2002/11/08 09:14:20 mdb Exp $ +# +# Translation messages for Two Due application + +# +# common translations + +app_name=Two Due + +# +# header.wm + +header.combined_title={0}: {1} +header.title={0} + +# +# app_footer.wm + +app_footer.summary_link=Task Summary +app_footer.account_link=Login/out + +# +# index.wm + +index.title=Task Summary + +index.message.task_created = Task created. +index.message.task_claimed = Task claimed. +index.message.task_completed = Task marked as completed. + +index.error.missing_summary = No summary provided for new task. +index.error.missing_category = No category provided for new task. +index.error.missing_complexity = No complexity selection for new task. +index.error.invalid_priority = No priority selection for new task. +index.error.missing_taskid = Internal error. No task id provided. diff --git a/runtime/twodue/etc/velocity.properties b/runtime/twodue/etc/velocity.properties new file mode 100644 index 00000000..256183c6 --- /dev/null +++ b/runtime/twodue/etc/velocity.properties @@ -0,0 +1,19 @@ +# +# $Id: velocity.properties,v 1.1 2002/11/08 09:14:20 mdb Exp $ +# +# Velocity engine configuration overrides + +#---------------------------------------------------------------------------- +# F O R E A C H P R O P E R T I E S +#---------------------------------------------------------------------------- +# These properties control how the counter is accessed in the #foreach +# directive. By default the reference $velocityCount will be available +# in the body of the #foreach directive. The default starting value +# for this reference is 1. +#---------------------------------------------------------------------------- + +directive.foreach.counter.name = vidx +directive.foreach.counter.initial.value = 0 + +# temporarily make life easier +file.resource.loader.path = /home/mdb/projects/twodue/web diff --git a/runtime/twodue/etc/web.xml b/runtime/twodue/etc/web.xml new file mode 100644 index 00000000..ac0b89ab --- /dev/null +++ b/runtime/twodue/etc/web.xml @@ -0,0 +1,47 @@ + + + + + + + dispatcher + com.samskivert.velocity.DispatcherServlet + + org.apache.velocity.properties + /velocity.properties + + + app_class + com.samskivert.twodue.TwoDueApp + + + logic_package + com.samskivert.twodue.logic + + + messages_path + messages + + + + + + dispatcher + *.wm + + + + index.wm + + diff --git a/runtime/twodue/lib/.cvsignore b/runtime/twodue/lib/.cvsignore new file mode 100644 index 00000000..d392f0e8 --- /dev/null +++ b/runtime/twodue/lib/.cvsignore @@ -0,0 +1 @@ +*.jar diff --git a/runtime/twodue/lib/REQUIRED_LIBS b/runtime/twodue/lib/REQUIRED_LIBS new file mode 100644 index 00000000..9d7ab619 --- /dev/null +++ b/runtime/twodue/lib/REQUIRED_LIBS @@ -0,0 +1,6 @@ +commons-collections.jar +commons-lang.jar +jakarta-regexp-1.2.jar +mm.mysql-2.0.14-bin.jar +samskivert.jar +velocity-1.4-dev.jar diff --git a/runtime/twodue/src/java/com/samskivert/twodue/Log.java b/runtime/twodue/src/java/com/samskivert/twodue/Log.java new file mode 100644 index 00000000..27ee00df --- /dev/null +++ b/runtime/twodue/src/java/com/samskivert/twodue/Log.java @@ -0,0 +1,42 @@ +// +// $Id: Log.java,v 1.1 2002/11/08 09:14:21 mdb Exp $ + +package com.samskivert.twodue; + +/** + * A placeholder class that contains a reference to the log object used by + * this package. + */ +public class Log +{ + /** + * This is the log instance that will be used to log all messages for + * this package. + */ + public static com.samskivert.util.Log log = + new com.samskivert.util.Log("twodue"); + + /** Convenience function. */ + public static void debug (String message) + { + log.debug(message); + } + + /** Convenience function. */ + public static void info (String message) + { + log.info(message); + } + + /** Convenience function. */ + public static void warning (String message) + { + log.warning(message); + } + + /** Convenience function. */ + public static void logStackTrace (Throwable t) + { + log.logStackTrace(com.samskivert.util.Log.WARNING, t); + } +} diff --git a/runtime/twodue/src/java/com/samskivert/twodue/TwoDueApp.java b/runtime/twodue/src/java/com/samskivert/twodue/TwoDueApp.java new file mode 100644 index 00000000..4b92322e --- /dev/null +++ b/runtime/twodue/src/java/com/samskivert/twodue/TwoDueApp.java @@ -0,0 +1,102 @@ +// +// $Id: TwoDueApp.java,v 1.1 2002/11/08 09:14:21 mdb Exp $ + +package com.samskivert.twodue; + +import java.util.Properties; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.StaticConnectionProvider; + +import com.samskivert.servlet.JDBCTableSiteIdentifier; +import com.samskivert.servlet.SiteIdentifier; +import com.samskivert.servlet.user.UserManager; +import com.samskivert.util.ServiceUnavailableException; +import com.samskivert.velocity.Application; + +import com.samskivert.twodue.data.TaskRepository; + +/** + * Contains references to application-wide resources (like the database + * repository) and handles initialization and cleanup for those resources. + */ +public class TwoDueApp extends Application +{ + /** Returns the connection provider in use by this application. */ + public final ConnectionProvider getConnectionProvider () + { + return _conprov; + } + + /** Returns the task repository in use by the application. */ + public TaskRepository getRepository () + { + return _taskrep; + } + + /** Returns the user manager in use by the application. */ + public UserManager getUserManager () + { + return _usermgr; + } + + protected void willInit (ServletConfig config) + { + super.willInit(config); + + try { + // create a static connection provider + _conprov = new StaticConnectionProvider(CONN_CONFIG); + + // initialize the user manager + Properties props = new Properties(); + props.put("login_url", "/register/login.wm?from=%R"); + _usermgr = new UserManager(props, _conprov); + + // initialize the task repository + _taskrep = new TaskRepository(_conprov); + + Log.info("TwoDue application initialized."); + + } catch (Throwable t) { + Log.warning("Error initializing application: " + t); + } + } + + public void shutdown () + { + try { + _usermgr.shutdown(); + Log.info("TwoDue application shutdown."); + + } catch (Throwable t) { + Log.warning("Error shutting down repository: " + t); + } + } + + /** We want a special site identifier. */ + protected SiteIdentifier createSiteIdentifier (ServletContext ctx) + { + try { + return new JDBCTableSiteIdentifier(_conprov); + } catch (PersistenceException pe) { + throw new ServiceUnavailableException( + "Can't access site database.", pe); + } + } + + /** A reference to our connection provider. */ + protected ConnectionProvider _conprov; + + /** A reference to our user manager. */ + protected UserManager _usermgr; + + /** A reference to our task repository. */ + protected TaskRepository _taskrep; + + /** The path to our database configuration file. */ + protected static final String CONN_CONFIG = "repository.properties"; +} diff --git a/runtime/twodue/src/java/com/samskivert/twodue/data/Task.java b/runtime/twodue/src/java/com/samskivert/twodue/data/Task.java new file mode 100644 index 00000000..c67656e0 --- /dev/null +++ b/runtime/twodue/src/java/com/samskivert/twodue/data/Task.java @@ -0,0 +1,35 @@ +// +// $Id: Task.java,v 1.1 2002/11/08 09:14:21 mdb Exp $ + +package com.samskivert.twodue.data; + +import java.util.ArrayList; +import java.sql.Date; + +/** + * Contains the basic data associated with a task. + */ +public class Task +{ + public int taskId; + + public String summary; + + public String category; + + public String complexity; + + public int priority; + + public String creator; + + public Date creation; + + public String owner; + + public String completor; + + public Date completion; + + public transient ArrayList notes; +} diff --git a/runtime/twodue/src/java/com/samskivert/twodue/data/TaskRepository.java b/runtime/twodue/src/java/com/samskivert/twodue/data/TaskRepository.java new file mode 100644 index 00000000..07fdcb64 --- /dev/null +++ b/runtime/twodue/src/java/com/samskivert/twodue/data/TaskRepository.java @@ -0,0 +1,252 @@ +// +// $Id: TaskRepository.java,v 1.1 2002/11/08 09:14:21 mdb Exp $ + +package com.samskivert.twodue.data; + +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; + +import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.DatabaseLiaison; +import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.JORARepository; +import com.samskivert.jdbc.jora.Cursor; +import com.samskivert.jdbc.jora.Session; +import com.samskivert.jdbc.jora.Table; + +/** + * Provides access to the task table. + */ +public class TaskRepository extends JORARepository +{ + /** + * The database identifier that the repository will use when fetching + * a connection from the connection provider. The value is + * twodue which you'll probably need to know to properly + * configure your connection provider. + */ + public static final String REPOSITORY_DB_IDENT = "twodue"; + + /** + * Creates the repository and opens the task database. A connection + * provider should be supplied that will be used to obtain the + * necessary database connection. The database identifier used to + * obtain our connection is documented by {@link + * #REPOSITORY_DB_IDENT}. + * + * @param provider a connection provider via which the repository will + * get its database connection. + */ + public TaskRepository (ConnectionProvider provider) + throws PersistenceException + { + super(provider, REPOSITORY_DB_IDENT); + + // make sure we can get our database connection + Connection conn = provider.getConnection(REPOSITORY_DB_IDENT); + provider.releaseConnection(REPOSITORY_DB_IDENT, conn); + } + + // documented inherited + protected void createTables (Session session) + { + // create our table objects + _ttable = new Table(Task.class.getName(), + "TASKS", session, "TASK_ID", true); + } + + /** + * Creates a task and inserts it into the repository. + * + * @return the newly created task. + */ + public Task createTask (String summary, String category, String complexity, + int priority, String creator) + throws PersistenceException + { + // create and configure a task instance + Task task = new Task(); + task.summary = summary; + task.category = category; + task.complexity = complexity; + task.priority = priority; + task.creator = creator; + createTask(task); + return task; + } + + /** + * Inserts the supplied (properly populated) new task into the task + * repository. + */ + public void createTask (final Task task) + throws PersistenceException + { + task.creation = new Date(System.currentTimeMillis()); + execute(new Operation() { + public Object invoke (Connection conn, DatabaseLiaison liaison) + throws PersistenceException, SQLException + { + // insert the task into the entry table + _ttable.insert(task); + // update the taskId now that it's known + task.taskId = liaison.lastInsertedId(conn); + return null; + } + }); + } + + /** + * Loads up the specified task. + */ + public Task loadTask (int taskId) + throws PersistenceException + { + return (Task)execute(new Operation () { + public Object invoke (Connection conn, DatabaseLiaison liaison) + throws PersistenceException, SQLException + { + String query = "where COMPLETOR IS NULL AND OWNER IS NULL " + + "ORDER BY CATEGORY, PRIORITY"; + Cursor tc = _ttable.select(query); + return tc.toArrayList(); + } + }); + } + + /** + * Loads up and returns all unowned, uncompleted tasks, ordered by + * priority. + */ + public ArrayList loadTasks () + throws PersistenceException + { + return loadTasks("where COMPLETOR IS NULL AND OWNER IS NULL " + + "ORDER BY PRIORITY DESC"); + } + + /** + * Loads up all owned tasks, ordered by priority. + */ + public ArrayList loadOwnedTasks () + throws PersistenceException + { + return loadTasks("where COMPLETOR IS NULL AND OWNER IS NOT NULL " + + "ORDER BY PRIORITY DESC"); + } + + /** + * Loads up and returns all completed tasks, sorted by completion + * date. + * + * @param start the offset into the sorted list of completed tasks to + * start returning tasks. + * @param limit the limit to the number of tasks to be returned, or -1 + * if all completed should be returned. + */ + public ArrayList loadCompletedTasks (int start, int limit) + throws PersistenceException + { + String query = "where COMPLETOR IS NOT NULL " + + "ORDER BY COMPLETION DESC LIMIT " + start; + if (limit != -1) { + query += (", " + limit); + } + return loadTasks(query); + } + + /** Loads lists of tasks. */ + protected ArrayList loadTasks (final String query) + throws PersistenceException + { + return (ArrayList)execute(new Operation () { + public Object invoke (Connection conn, DatabaseLiaison liaison) + throws PersistenceException, SQLException + { + Cursor tc = _ttable.select(query); + return tc.toArrayList(); + } + }); + } + + /** + * Marks the specified task as completed by the specified user. + */ + public void completeTask (final int taskId, final String completor) + throws PersistenceException + { + execute(new Operation () { + public Object invoke (Connection conn, DatabaseLiaison liaison) + throws PersistenceException, SQLException + { + String query = "update TASKS " + + "set COMPLETOR = ?, COMPLETION = ? where TASK_ID = ?"; + PreparedStatement stmt = null; + + try { + stmt = conn.prepareStatement(query); + stmt.setString(1, completor); + stmt.setDate(2, new Date(System.currentTimeMillis())); + stmt.setInt(3, taskId); + JDBCUtil.checkedUpdate(stmt, 1); + + } finally { + JDBCUtil.close(stmt); + } + + return null; + } + }); + } + + /** + * Marks the specified task as owned by the specified user. + */ + public void claimTask (final int taskId, final String owner) + throws PersistenceException + { + execute(new Operation () { + public Object invoke (Connection conn, DatabaseLiaison liaison) + throws PersistenceException, SQLException + { + String query = "update TASKS " + + "set OWNER = ? where TASK_ID = ?"; + PreparedStatement stmt = null; + + try { + stmt = conn.prepareStatement(query); + stmt.setString(1, owner); + stmt.setInt(2, taskId); + JDBCUtil.checkedUpdate(stmt, 1); + + } finally { + JDBCUtil.close(stmt); + } + + return null; + } + }); + } + + /** + * Updates a task that was previously loaded from the repository. + */ + public void updateTask (final Task task) + throws PersistenceException + { + execute(new Operation() { + public Object invoke (Connection conn, DatabaseLiaison liaison) + throws PersistenceException, SQLException + { + _ttable.update(task); + return null; + } + }); + } + + protected Table _ttable; +} diff --git a/runtime/twodue/src/java/com/samskivert/twodue/logic/UserLogic.java b/runtime/twodue/src/java/com/samskivert/twodue/logic/UserLogic.java new file mode 100644 index 00000000..359a5119 --- /dev/null +++ b/runtime/twodue/src/java/com/samskivert/twodue/logic/UserLogic.java @@ -0,0 +1,38 @@ +// +// $Id: UserLogic.java,v 1.1 2002/11/08 09:14:21 mdb Exp $ + +package com.samskivert.twodue.logic; + +import com.samskivert.servlet.user.User; +import com.samskivert.velocity.Application; +import com.samskivert.velocity.InvocationContext; +import com.samskivert.velocity.Logic; + +import com.samskivert.twodue.TwoDueApp; + +/** + * A base logic class for pages that require an authenticated user. + */ +public abstract class UserLogic implements Logic +{ + /** + * Logic classes should implement this method to perform their normal + * duties. + * + * @param ctx the context in which the request is being invoked. + * @param app the web application. + * @param user the user record for the authenticated user. + */ + public abstract void invoke ( + InvocationContext ctx, TwoDueApp app, User user) + throws Exception; + + // documentation inherited from interface + public void invoke (Application app, InvocationContext ctx) + throws Exception + { + TwoDueApp tdapp = (TwoDueApp)app; + User user = tdapp.getUserManager().requireUser(ctx.getRequest()); + invoke(ctx, tdapp, user); + } +} diff --git a/runtime/twodue/src/java/com/samskivert/twodue/logic/index.java b/runtime/twodue/src/java/com/samskivert/twodue/logic/index.java new file mode 100644 index 00000000..ab0cef3e --- /dev/null +++ b/runtime/twodue/src/java/com/samskivert/twodue/logic/index.java @@ -0,0 +1,164 @@ +// +// $Id: index.java,v 1.1 2002/11/08 09:14:21 mdb Exp $ + +package com.samskivert.twodue.logic; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import javax.servlet.http.HttpServletRequest; + +import com.samskivert.util.QuickSort; +import com.samskivert.util.StringUtil; + +import com.samskivert.servlet.RedirectException; +import com.samskivert.servlet.user.User; +import com.samskivert.servlet.util.ParameterUtil; +import com.samskivert.velocity.InvocationContext; + +import com.samskivert.twodue.Log; +import com.samskivert.twodue.TwoDueApp; +import com.samskivert.twodue.data.Task; + +/** + * Displays a summary out outstanding and completed tasks. + */ +public class index extends UserLogic +{ + public void invoke (InvocationContext ctx, TwoDueApp app, User user) + throws Exception + { + HttpServletRequest req = ctx.getRequest(); + + // display any message we've been asked to display + String msg = ParameterUtil.getParameter(req, "message", true); + if (!StringUtil.blank(msg)) { + ctx.put("error", msg); + } + + // we use this to determine whether to show "complete" buttons for + // tasks + ctx.put("username", user.username); + + // if they've submitted the form, we create a new task and stick + // it into the dataabse + if (ParameterUtil.parameterEquals( + ctx.getRequest(), "action", "create")) { + // set the creator from the username of the calling user + Task task = new Task(); + task.creator = user.username; + + // parse our fields + task.summary = ParameterUtil.requireParameter( + ctx.getRequest(), "summary", "index.error.missing_summary"); + task.category = ParameterUtil.requireParameter( + ctx.getRequest(), "category", + "index.error.missing_category"); + task.complexity = ParameterUtil.requireParameter( + ctx.getRequest(), "complexity", + "index.error.missing_complexity"); + task.priority = ParameterUtil.requireIntParameter( + ctx.getRequest(), "priority", + "index.error.invalid_priority"); + + // insert the task into the repository + app.getRepository().createTask(task); + + // flip back to this same page minus our query parameters to + // clear out the creation form + throw new RedirectException( + "index.wm?msg=index.message.task_created"); + + } else if (ParameterUtil.parameterEquals( + ctx.getRequest(), "action", "complete")) { + int taskId = ParameterUtil.requireIntParameter( + ctx.getRequest(), "task", "index.error.missing_taskid"); + app.getRepository().completeTask(taskId, user.username); + + // let the user know we updated the database + ctx.put("error", "index.message.task_completed"); + + } else if (ParameterUtil.parameterEquals( + ctx.getRequest(), "action", "claim")) { + int taskId = ParameterUtil.requireIntParameter( + ctx.getRequest(), "task", "index.error.missing_taskid"); + app.getRepository().claimTask(taskId, user.username); + + // let the user know we updated the database + ctx.put("error", "index.message.task_claimed"); + } + + // load up outstanding tasks and break them down by complexity + String expand = ParameterUtil.getParameter(req, "expand", false); + ArrayList tasks = app.getRepository().loadTasks(); + CatList[] xtasks = categorize(tasks, expand, new Categorizer() { + public String category (Task task) { + return task.complexity; + } + }); + ctx.put("xtasks", xtasks); + + // load up owned tasks and break them down by owner + tasks = app.getRepository().loadOwnedTasks(); + CatList[] otasks = categorize(tasks, null, new Categorizer() { + public String category (Task task) { + return task.owner; + } + }); + ctx.put("otasks", otasks); + + // load up recently completed tasks + tasks = app.getRepository().loadCompletedTasks(0, 6); + ctx.put("dtasks", tasks); + } + + protected static class CatList + implements Comparable + { + public String name; + public ArrayList tasks; + public int pruned; + public int compareTo (Object other) + { + return name.compareTo(((CatList)other).name); + } + } + + protected static interface Categorizer + { + public String category (Task task); + } + + protected CatList[] categorize ( + ArrayList tasks, String expand, Categorizer catter) + { + HashMap cmap = new HashMap(); + int tcount = tasks.size(); + for (int ii = 0; ii < tcount; ii++) { + Task task = (Task)tasks.get(ii); + String category = catter.category(task); + CatList clist = (CatList)cmap.get(category); + if (clist == null) { + clist = new CatList(); + clist.name = category; + clist.tasks = new ArrayList(); + cmap.put(category, clist); + } + if (expand == null || clist.tasks.size() < 2 || + (expand.equals(category))) { + clist.tasks.add(task); + } else { + clist.pruned++; + } + } + + CatList[] ctasks = new CatList[cmap.size()]; + Iterator iter = cmap.values().iterator(); + for (int ii = 0; iter.hasNext(); ii++) { + ctasks[ii] = (CatList)iter.next(); + } + QuickSort.sort(ctasks); + + return ctasks; + } +} diff --git a/runtime/twodue/src/sql/tasks-schema.sql b/runtime/twodue/src/sql/tasks-schema.sql new file mode 100644 index 00000000..bf7ca300 --- /dev/null +++ b/runtime/twodue/src/sql/tasks-schema.sql @@ -0,0 +1,72 @@ +/** + * $Id: tasks-schema.sql,v 1.1 2002/11/08 09:14:21 mdb Exp $ + * + * Schema for the Two Due tasks and notes table. + */ + +drop table if exists TASKS; + +/** + * Contains basic data for every task in the system. + */ +CREATE TABLE TASKS +( + /** + * A unique identifier for this task. + */ + TASK_ID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + + /** + * The summary description of this task. + */ + SUMMARY VARCHAR(255) NOT NULL, + + /** + * The comma separated list of categories occupied by this task. + */ + CATEGORY VARCHAR(255) NOT NULL, + + /** + * The complexity identifier for this task. + */ + COMPLEXITY VARCHAR(32) NOT NULL, + + /** + * The priority of this task. + */ + PRIORITY INTEGER UNSIGNED NOT NULL, + + /** + * The user to which this task is currently assigned. + */ + OWNER VARCHAR(255), + + /** + * The name of the creator of this task. + */ + CREATOR VARCHAR(255) NOT NULL, + + /** + * The time of creation of this task. + */ + CREATION DATE NOT NULL, + + /** + * The name of the completor(s) of this task. + */ + COMPLETOR VARCHAR(255), + + /** + * The time of completion of this task. + */ + COMPLETION DATE, + + /** + * Defines our table keys. + */ + PRIMARY KEY (TASK_ID), + KEY (CATEGORY), + KEY (PRIORITY), + KEY (CREATION), + KEY (COMPLETION) +); diff --git a/runtime/twodue/web/app_footer.wm b/runtime/twodue/web/app_footer.wm new file mode 100644 index 00000000..ecd4c8b4 --- /dev/null +++ b/runtime/twodue/web/app_footer.wm @@ -0,0 +1,3 @@ +Two Due: +$i18n.xlate("app_footer.summary_link") | +$i18n.xlate("app_footer.account_link") diff --git a/runtime/twodue/web/edit.wm b/runtime/twodue/web/edit.wm new file mode 100644 index 00000000..8cbdef75 --- /dev/null +++ b/runtime/twodue/web/edit.wm @@ -0,0 +1,51 @@ +#set ($title = $i18n.xlate("edit.title")) +#import ("/header.wm") + +

+ +

+$form.fixedHidden("action", "update") + + + + + + + + + + + + + +
 
Summary:
+ +
Category:
+ +$form.text("category", "size='10'", "") +
Complexity:
+ + +
Priority:
+ + +
+$form.submit("submit", "Update")
+ +
+ +#import ("/footer.wm") diff --git a/runtime/twodue/web/footer.wm b/runtime/twodue/web/footer.wm new file mode 100644 index 00000000..989f5a1d --- /dev/null +++ b/runtime/twodue/web/footer.wm @@ -0,0 +1,16 @@ +

+ + + + +
+#import ("app_footer.wm") +
+ + + diff --git a/runtime/twodue/web/header.wm b/runtime/twodue/web/header.wm new file mode 100644 index 00000000..a4539a46 --- /dev/null +++ b/runtime/twodue/web/header.wm @@ -0,0 +1,26 @@ +## This is the header and footer color +#set ($hcolor = "#CCCCCC") +## These colors are used for common UI elements +#set ($tcolor = "#99CCFF") +#set ($scolor = "#CCCCCC") + + + +$i18n.xlate("header.combined_title", $i18n.xlate("app_name"), $title) + + + + + + + +
+$i18n.xlate("header.title", $i18n.xlate("app_name"))
+$title
+ +#if ($error) +

+ + +
$i18n.xlate($error)
+#end diff --git a/runtime/twodue/web/index.wm b/runtime/twodue/web/index.wm new file mode 100644 index 00000000..83750a4c --- /dev/null +++ b/runtime/twodue/web/index.wm @@ -0,0 +1,156 @@ +#set ($title = $i18n.xlate("index.title")) +#import ("/header.wm") + +

+ + + + + + + + + + + + + + +
Owned tasks:Outstanding tasks:
+ + +#foreach ($otask in $otasks) +#if ($vidx > 0) + +#end + + +#foreach ($task in $otask.tasks) + +$form.fixedHidden("action", "complete") +$form.fixedHidden("task", "$task.taskId") +#if ($vidx%2 == 0) +#set ($rowcolor = "#FFFFFF") +#else +#set ($rowcolor = "#EEEEEE") +#end + + + + +#end +#end +
 
$otask.name
+#if ($username == $task.owner) +$form.imageSubmit("submit", "", "images/complete.png", "Mark as completed") +#else +  +#end +$task.summary$task.category
+ +

+

Click the check box to mark a task as completed.
+ +
+ + +#foreach ($xtask in $xtasks) +#if ($vidx > 0) + +#end + + + +#foreach ($task in $xtask.tasks) + +$form.fixedHidden("task", "$task.taskId") +#if ($vidx%2 == 0) +#set ($rowcolor = "#FFFFFF") +#else +#set ($rowcolor = "#EEEEEE") +#end + + + + + + +#end +#end +
 
$xtask.name +#if ($xtask.pruned > 0) +($xtask.pruned ...) +#else +  +#end +
$form.imageSubmit("action", "claim", "images/claim.png", "Claim this task")$task.summary   +[$task.creator]$task.category
+ +

+

Click the wrench to claim a task.
+ +
 
Recent completed tasks:Create new task:
+ +#foreach ($task in $dtasks) +#if ($vidx%2 == 0) +#set ($rowcolor = "#EEEEEE") +#else +#set ($rowcolor = "#FFFFFF") +#end + + + + +#end +
$task.completion
[$task.completor]
$task.summary$task.category
+ +
+ +
+$form.fixedHidden("action", "create") + + + + + + + + + + + + + +
 
Summary:
+ +
Category:
+ +$form.text("category", "size='10'", "") +
Complexity:
+ + +
Priority:
+ + +
+$form.submit("submit", "Create")
+ +
+ +
+ +#import ("/footer.wm") diff --git a/runtime/twodue/web/style.css b/runtime/twodue/web/style.css new file mode 100644 index 00000000..2abd9168 --- /dev/null +++ b/runtime/twodue/web/style.css @@ -0,0 +1,17 @@ +body { + font-family: Arial, Helvetica, sans-serif; + font-size: 12pt; +} + +th, td, center, div { /* ns 4 */ + font-family: Arial, Helvetica, sans-serif; + font-size: 12pt; +} + +pre { + font-family: Courier; + font-size: 12pt +} + +.big { font-size: 16pt } +.small { font-size: 10pt }