24 |
25 |
26 |
27 |
31 |
32 |
33 |
34 |
41 |
42 |
--------------------------------------------------------------------------------
/src/markup/templates/board.pug:
--------------------------------------------------------------------------------
1 | include ../mixins/menu.pug
2 |
3 | .tyto-board__options
4 | +actionMenu('tyto-board__menu', 'more_vert', 'tyto-board', '', 'bottom-right', 'Board options')
5 | li.tyto-board__wipe-board.mdl-menu__item Wipe board
6 | li.tyto-board__delete-board.mdl-menu__item Delete board
7 | li.tyto-board__email-board.mdl-menu__item Email board
8 | a.tyto-board__emailer(style="display: none;")
9 |
10 | .tyto-board__details
11 | h1.tyto-board__title.bg--white(contenteditable="true")
12 | |<%= tyto.title %>
13 | |<% if (tyto.boards.length > 1) { %>
14 | +actionMenu('tyto-board__selector', 'expand_more', 'tyto-board__selector', '', 'bottom-right', 'Select a board')
15 | |<% _.each(tyto.boards.models, function(i){ %>
16 | |<% if (i.attributes.id != tyto.id) { %>
17 | li.mdl-menu__item.tyto-board__selector-option(title="View board")
18 | a(href!="#board/<%= i.attributes.id %>") <%= i.attributes.title %>
19 | |<% } %>
20 | |<% }) %>
21 | |<% } %>
22 |
23 | .tyto-board__columns
24 |
25 | .tyto-board__actions.mdl-button--fab_flinger-container
26 | button.tyto-board__add-entity.mdl-button.mdl-js-button.mdl-button--fab.mdl-js-ripple-effect.mdl-button--colored
27 | i.material-icons add
28 | .mdl-button--fab_flinger-options
29 | button.tyto-board__super-add.mdl-button.mdl-js-button.mdl-button--fab.mdl-js-ripple-effect.mdl-button--colored(title="Add task")
30 | i.material-icons description
31 | i.material-icons.sub add
32 | button.tyto-board__add-column.mdl-button.mdl-js-button.mdl-button--fab.mdl-js-ripple-effect.mdl-button--colored(title="Add column")
33 | i.material-icons view_column
34 | i.material-icons.sub add
35 |
36 | .tyto-board__bloomer
37 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing to tyto 
2 | ===
3 | Open source is a great thing and I really appreciate that you may be interested in contributing to `tyto`!
4 |
5 | ##Issues
6 | Feel free to submit an issue. However. Be sure to make your contribution clear.
7 |
8 | ###Found a bug?
9 | Be clear about what the issue is that you've found and PLEASE provide clear steps to reproduce it :simple_smile:
10 |
11 | ###Want a new feature?
12 | If you would like to see a new feature in `tyto`, raise an issue for it. Give it an appropriate label and then clearly set out what you'd like to see. If you can, also provide a possible solution for this feature to help get the ball rolling.
13 |
14 | ##Making contributions
15 | For me personally. I prefer to work on forked instances of a repo to ensure I'm not stepping on anyones toes.
16 |
17 | For minor fixes and issues. Develop against develop and submit pull requests to be merged into `develop`.
18 |
19 | For new features and large scale changes, please create a new branch on the repo and work against this. Ensure the branch name is labelled appropriately with the correct issue number.
20 |
21 | The most important thing to me is that people aren't afraid of contributing. If you have any problems or get stuck, feel free to get in touch!
22 |
23 | ###Set up
24 | 1. Fork the repo and clone it.
25 |
26 | git clone https://github.com//tyto.git
27 |
28 | 2. Navigate into the repo and install the dependencies.
29 |
30 | cd tyto
31 | yarn/npm install
32 |
33 | 3. Run gulp to take care of preprocessing and running a local static webserver instance(the project uses BrowserSync).
34 |
35 | gulp
36 |
37 | 4. Develop!
38 |
--------------------------------------------------------------------------------
/src/style/modules/edit.styl:
--------------------------------------------------------------------------------
1 | .tyto-edit
2 | & > *
3 | animation-name fade__in
4 | animation-duration .25s
5 |
6 | &__content
7 | position fixed
8 | top 56px
9 | right 0
10 | bottom 0
11 | left 0
12 | padding 20px
13 | overflow visible
14 | @media(min-width 852px)
15 | top 64px
16 | @media(min-width 768px)
17 | width 600px
18 | margin 0 auto
19 |
20 | &__color-select__menu
21 | display flex
22 | flex-wrap wrap
23 | flex-direction row
24 | padding 8px
25 |
26 | &__color-select__menu-option
27 | width 50%
28 |
29 | &__task-title
30 | margin 0 32px 0 0
31 | padding 10px 24px
32 |
33 | &__task-description
34 | padding 24px
35 | width 100%
36 | outline 0
37 | background transparent
38 | border none
39 | min-height 100px
40 |
41 | &__edit-description
42 | outline 0
43 | border none
44 | background transparent
45 | padding 16px 0
46 | width 100%
47 | min-height 100px
48 | max-height 300px
49 | margin 0 16px
50 | font-size 16px
51 | line-height 16px
52 | resize none
53 |
54 | &__nav
55 | &__details-footer
56 | min-height 50px
57 |
58 | &__details-footer
59 | padding 0 8px
60 | opacity 0.4
61 |
62 | &__actions
63 | position absolute
64 | top 20px
65 | right 20px
66 | display flex
67 | flex-direction column
68 |
69 |
70 | &__nav
71 | position fixed
72 | display flex
73 | align-items center
74 | z-index 2
75 | top 0
76 | left 100px
77 | height 56px
78 | @media(min-width 852px)
79 | height 64px
80 |
--------------------------------------------------------------------------------
/src/style/modules/board.styl:
--------------------------------------------------------------------------------
1 | /***
2 |
3 | Board component.
4 |
5 | ***/
6 |
7 | .tyto-board
8 | display flex
9 | flex-direction column
10 |
11 | &__options
12 | position fixed
13 | display flex
14 | align-items center
15 | z-index 2
16 | top 0
17 | right 10px
18 | height 56px
19 | @media(min-width 852px)
20 | height 64px
21 |
22 | &__columns
23 | display flex
24 | flex-direction row
25 | overflow-x auto
26 | overflow-y hidden
27 | flex 1 1 auto
28 | padding 10px
29 |
30 |
31 | &__details
32 | position fixed
33 | display flex
34 | align-items center
35 | z-index 2
36 | top 0px
37 | left 75px
38 | right 75px
39 |
40 | &__selector__menu-btn
41 | margin-left 6px
42 |
43 | &__selector-option
44 | padding 0
45 | a
46 | padding 0 8px
47 | display block
48 | text-decoration none
49 |
50 | &__title
51 | font-size 24px
52 | height 56px
53 | line-height 56px
54 | margin 0
55 | outline 0
56 | min-width 100px
57 | overflow hidden
58 | white-space nowrap
59 | text-overflow ellipsis
60 | &:active
61 | &:focus
62 | overflow visible
63 | white-space normal
64 | @media(min-width 852px)
65 | line-height 64px
66 | height 64px
67 |
68 | &__bloomer
69 | position fixed
70 | height 100px
71 | width 100px
72 | margin-left -50px
73 | margin-top -50px
74 | border-radius 100%
75 | z-index 9999
76 | display none
77 | animation-fill-mode forwards
78 | animation-timing-function linear
79 | animation-duration .5s
80 | &.is--blooming
81 | display block
82 | animation-name bloom
83 |
--------------------------------------------------------------------------------
/src/style/theme/theme.styl:
--------------------------------------------------------------------------------
1 | /***
2 |
3 | THEME --
4 |
5 | Contains styling rules for various app wide styles.
6 |
7 | ***/
8 |
9 |
10 | /*
11 | Generates color palette specific styles for background, border, text and
12 | hover color
13 | */
14 | for key, value in colorPalette
15 | .bg--{key}
16 | background-color value
17 | .tx--{key}
18 | color value
19 | .bd--{key}
20 | border 4px solid value
21 | .hv--{key}
22 | &:hover
23 | background-color value
24 | /*
25 | NOTE: That for [contenteditable] elements with a background color we wish
26 | to show an accented color on :focus/:active.
27 | */
28 | for key, value in colorPalette
29 | .bg--{key} [contenteditable]
30 | [contenteditable].bg--{key}
31 | .bg--{key} textarea
32 | transition background .25s
33 | outline 0
34 | &:active
35 | &:focus
36 | background-color darken(value, 15%)
37 | /*
38 | Generates text alignment utility classes.
39 | */
40 | for dir in left right center
41 | .tx--{dir}
42 | text-align dir
43 |
44 | /*
45 | Position helper
46 | */
47 | position()
48 | position arguments
49 | z-index 1 unless @z-index
50 |
51 |
52 | /*
53 | tx--black wouldn't work on this set up because it's a generated el.
54 | */
55 | .mdl-layout__drawer-button
56 | i
57 | color black
58 |
59 | .github-stats
60 | margin-top 50px
61 |
62 | /*
63 | NOTE: MDL Override FIX to stop MDL menu clipped menus blocking UI
64 | interaction.
65 | */
66 | .tyto-board .mdl-menu__container
67 | height 0px !important
68 |
69 | /***
70 | Suggestions
71 | ***/
72 | .tyto-suggestions
73 | &__container
74 | position fixed
75 | background white
76 | border-radius 2px
77 | z-index 99999
78 | &__list
79 | list-style none
80 | padding 0
81 | margin 0
82 | font-size 14px
83 | &__item
84 | padding 4px 26px
85 | cursor pointer
86 | line-height 14px
87 | &:hover
88 | &.is--active
89 | color white
90 | background-color teal
91 |
--------------------------------------------------------------------------------
/src/style/modules/column.styl:
--------------------------------------------------------------------------------
1 | /***
2 |
3 | Columns
4 |
5 | ***/
6 | /* variables */
7 | columnWidth = 320px
8 |
9 | .tyto-column
10 | display flex
11 | width columnWidth
12 | max-width columnWidth
13 | flex-direction column
14 | flex 0 0 columnWidth
15 | max-height 100%
16 |
17 | &:hover &__action
18 | min-height 40px
19 | height 40px
20 |
21 | &:hover &__actions
22 | .does--fade
23 | &:hover
24 | opacity 1
25 | opacity 0.4
26 |
27 | &__content
28 | display flex
29 | flex-direction column
30 | flex 1 1 auto
31 | width 100%
32 | max-height 100%
33 | padding-right 10px
34 | overflow auto
35 |
36 | &__mover
37 | position absolute
38 | top 0
39 | left 14px
40 | flex 0 0 auto
41 | cursor move
42 |
43 | &__actions
44 | text-align center
45 | margin 20px 0
46 | flex 0 0 auto
47 | position relative
48 | align-items center
49 | .does--fade
50 | opacity 0
51 |
52 | &__title
53 | max-width 200px
54 | min-width 150px
55 | margin 0
56 | display inline-block
57 |
58 | &__menu-btn
59 | position absolute
60 | width 24px
61 | height 24px
62 | border-radius 50%
63 | min-width 24px
64 | padding 0
65 | right 5px
66 | top 0
67 |
68 | &__tasks
69 | padding 0 10px
70 | flex 1 1 auto
71 | overflow-y auto
72 | overflow-x hidden
73 |
74 | &__placeholder
75 | display inline-block
76 | min-width columnWidth
77 | height 100%
78 |
79 | &__action
80 | height 0
81 | min-height 0
82 | overflow hidden
83 | text-align center
84 | display flex
85 | align-items center
86 | align-content center
87 | transition height .25s linear, min-height .25s linear
88 | i
89 | flex 1
90 | font-size 32px
91 | cursor pointer
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tyto",
3 | "version": "3.0.4",
4 | "description": "tyto - manage and organise",
5 | "scripts": {
6 | "test": "./node_modules/mocha-phantomjs/bin/mocha-phantomjs -p ./node_modules/.bin/phantomjs test/index.html"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/jh3y/tyto"
11 | },
12 | "author": "jh3y ",
13 | "license": "MIT",
14 | "bugs": {
15 | "url": "https://github.com/jh3y/tyto/issues"
16 | },
17 | "homepage": "https://github.com/jh3y/tyto",
18 | "devDependencies": {
19 | "minimatch": "^3.0.4",
20 | "tough-cookie": "^2.3.3",
21 | "babel-preset-es2015": "^6.14.0",
22 | "babel-preset-react": "^6.11.1",
23 | "babelify": "^7.3.0",
24 | "backbone": "1.1.2",
25 | "backbone.localstorage": "^1.1.16",
26 | "backbone.marionette": "2.4.1",
27 | "backbone.wreqr": "^1.4.0",
28 | "browser-sync": "^2.6.4",
29 | "browserify": "^10.2.0",
30 | "chai": "^3.5.0",
31 | "gulp": "^3.8.11",
32 | "gulp-autoprefixer": "^2.2.0",
33 | "gulp-concat": "^2.5.2",
34 | "gulp-filter": "^2.0.2",
35 | "gulp-gh-pages": "^0.5.1",
36 | "gulp-header": "^1.2.2",
37 | "gulp-load-plugins": "^0.10.0",
38 | "gulp-minify-css": "^1.2.1",
39 | "gulp-order": "^1.1.1",
40 | "gulp-plumber": "^1.0.0",
41 | "gulp-pug": "^3.0.4",
42 | "gulp-rename": "^1.2.2",
43 | "gulp-size": "^1.2.1",
44 | "gulp-sourcemaps": "^1.5.2",
45 | "gulp-stylus": "^2.0.1",
46 | "gulp-template-store": "^1.1.1",
47 | "gulp-uglify": "^1.4.1",
48 | "gulp-util": "^3.0.4",
49 | "gulp-wrap": "^0.11.0",
50 | "jquery": "^3.1.1",
51 | "jquery-ui": "^1.12.1",
52 | "jquery-ui-touch-punch": "^0.2.3",
53 | "lodash": "3.9.3",
54 | "marked": "^0.3.9",
55 | "material-design-icons": "^3.0.1",
56 | "material-design-lite": "^1.2.1",
57 | "mocha": "^3.2.0",
58 | "mocha-phantomjs": "^4.1.0",
59 | "normalize.css": "^5.0.0",
60 | "phantomjs": "^2.1.1",
61 | "vinyl-buffer": "^1.0.0",
62 | "vinyl-file": "^2.0.0",
63 | "vinyl-source-stream": "^1.1.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/style/modules/task.styl:
--------------------------------------------------------------------------------
1 | /***
2 |
3 | Tasks.
4 |
5 | ***/
6 |
7 | taskMinHeight = 150px
8 |
9 | .tyto-task
10 | margin-bottom 30px
11 | padding 0 0 20px 0
12 | max-width 100%
13 | min-height taskMinHeight
14 | overflow visible
15 | z-index auto
16 | /* Adds extra drop shadow when task is being lifted/dragged */
17 | &.ui-sortable-helper
18 | box-shadow 0 4px 4px 0 rgba(0,0,0,.14),
19 | 0 6px 2px -4px rgba(0,0,0,.2),
20 | 0 2px 10px 0 rgba(0,0,0,.12)
21 | /* Manages show/hide of opaque element interaction icons */
22 | .does--fade
23 | opacity 0
24 | &:hover
25 | .does--fade
26 | &:hover
27 | opacity 1
28 | opacity 0.4
29 |
30 | &__header
31 | position relative
32 | margin 10px 0
33 |
34 | &__title
35 | display inline-block
36 | max-width 200px
37 | min-width 150px
38 | margin 0
39 | font-size 24px
40 | line-height 32px
41 |
42 | &__menu-btn
43 | position absolute
44 | top 5px
45 | right 5px
46 | width 24px
47 | height 24px
48 | border-radius 50%
49 | min-width 24px
50 | padding 0
51 |
52 | &__time
53 | padding 16px
54 | & > *
55 | opacity 0.4
56 |
57 | &__mover
58 | position absolute
59 | top 5px
60 | left 5px
61 | cursor pointer
62 |
63 | &__edit-wrapper
64 | position relative
65 |
66 | &__description
67 | width 100%
68 | a
69 | z-index 99999
70 | cursor pointer
71 | img
72 | display block
73 | margin 5px auto
74 | max-width 100%
75 |
76 | &__description-edit
77 | outline 0
78 | border none
79 | background transparent
80 | padding 16px 0
81 | width 258px
82 | max-height 100px
83 | margin 0 16px
84 | font-size 16px
85 | line-height 16px
86 | resize none
87 |
88 | &__placeholder
89 | height taskMinHeight - 50px
90 | width 90%
91 | margin 0em auto 1em auto
92 |
--------------------------------------------------------------------------------
/src/markup/templates/edit.pug:
--------------------------------------------------------------------------------
1 | include ../mixins/menu.pug
2 | include ../mixins/timeLabel.pug
3 |
4 | .tyto-edit__nav
5 | |<% if (tyto.isNew) { %>
6 | button.tyto-edit__save.mdl-button.mdl-js-button.mdl-js-ripple-effect Done
7 | a.tyto-edit__cancel.mdl-button.mdl-js-button.mdl-js-ripple-effect(href!="#board/<%= tyto.board.id %>") Cancel
8 | |<% } else { %>
9 | a.tyto-edit__back.mdl-button.mdl-js-button.mdl-js-ripple-effect(href!="#board/<%= tyto.board.id %>") Return to board
10 | |<% } %>
11 | .tyto-edit__content
12 | .tyto-edit__details.has--bottom-margin
13 | h1.tyto-edit__task-title(contenteditable="true", data-model-prop="title", title="Task title") <%= tyto.title %>
14 | .tyto-edit__task-description(title="Task description") <%= tyto.description %>
15 | .tyto-task__edit-wrapper
16 | textarea.tyto-edit__edit-description.is--hidden(data-model-prop="description")
17 | .tyto-task__suggestions.tyto-suggestions__container.mdl-shadow--2dp.is--hidden
18 | .tyto-edit__details-footer.tx--right.has--bottom-margin
19 | +timeLabel('tyto-edit__task-time')
20 | .tyto-edit__task-column
21 | |<% if (tyto.selectedColumn) { %>
22 | |<%= tyto.selectedColumn.attributes.title %>
23 | |<% } %>
24 | .tyto-edit__actions
25 | |<% if (tyto.columns.length > 0 ) { %>
26 | +actionMenu('column-select', 'view_column', 'tyto-edit__column-select', '', 'bottom-right', 'Select column')
27 | |<% _.forEach(tyto.columns, function(column) { %>
28 | |<% if (!tyto.isNew) { var activeClass = (column.attributes.id === tyto.columnId) ? 'is--selected': '' } %>
29 | li.mdl-menu__item.tyto-edit__column-select__menu-option(data-column-id!="<%= column.id %>" class!="<%= activeClass %>") <%= column.attributes.title %>
30 | |<% }); %>
31 | |<% } %>
32 | +actionMenu('color-select' , 'color_lens', 'tyto-edit__color-select', '', 'bottom-right', 'Change color')
33 | |<% _.forEach(tyto.colors, function(col) { %>
34 | |<% var activeColor = (col === tyto.color) ? 'is--selected': '' %>
35 | li.mdl-menu__item.tyto-edit__color-select__menu-option(class!="bg--<%= col %> hv--<%= col %> <%= tyto.activeColor %>", data-color!="<%= col %>", title!="<%= col %>")
36 | |<% }); %>
37 | button.tyto-edit__track.mdl-button.mdl-js-button.mdl-button--icon.mdl-js-ripple-effect(title="Start tracking")
38 | i.material-icons schedule
39 |
--------------------------------------------------------------------------------
/src/script/app.js:
--------------------------------------------------------------------------------
1 | // Create app instance
2 | import TytoApp from './config/tyto';
3 | const Tyto = new TytoApp();
4 |
5 | window.Tyto = Tyto;
6 |
7 | // Hydrate template store for views
8 | import Templates from './templates/templates';
9 | Tyto.TemplateStore = Templates;
10 |
11 | // Import requirements
12 | import TytoCtrl from './controllers/tyto';
13 | import TytoViews from './views/tyto';
14 | import TytoModels from './models/tyto';
15 | import TytoUtils from './utils/utils';
16 | import TytoSuggestions from './utils/suggestions';
17 |
18 | Tyto.module('Models', TytoModels);
19 | Tyto.module('Ctrl', TytoCtrl);
20 | Tyto.module('Views', TytoViews);
21 | Tyto.module('Utils', TytoUtils);
22 | Tyto.module('Suggestions', TytoSuggestions);
23 |
24 | Tyto.Boards = new Tyto.Models.BoardCollection();
25 | Tyto.Columns = new Tyto.Models.ColumnCollection();
26 | Tyto.Tasks = new Tyto.Models.TaskCollection();
27 | Tyto.ActiveBoard = new Tyto.Models.Board();
28 | Tyto.ActiveCols = new Tyto.Models.ColumnCollection();
29 | Tyto.ActiveTasks = new Tyto.Models.TaskCollection();
30 |
31 | Tyto.on('before:start', function() {
32 | return Tyto.setRootLayout();
33 | });
34 |
35 | Tyto.on('start', function() {
36 | Tyto.__renderer = new marked.Renderer();
37 | Tyto.__renderer.link = function(href, title, text) {
38 | var e, out, prot;
39 | if (this.options.sanitize) {
40 | try {
41 | prot = decodeURIComponent(unescape(href)).replace(/[^\w:]/g, '').toLowerCase();
42 | } catch (_error) {
43 | e = _error;
44 | return '';
45 | }
46 | if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) {
47 | return '';
48 | }
49 | }
50 | out = '' + text + '';
55 | return out;
56 | };
57 | marked.setOptions({
58 | renderer: Tyto.__renderer
59 | });
60 | Tyto.Controller = new Tyto.Ctrl.Controller();
61 | Tyto.Controller.Router = new Tyto.Ctrl.Router({
62 | controller: Tyto.Controller
63 | });
64 | Tyto.Controller.start();
65 | return Backbone.history.start();
66 | });
67 |
68 |
69 | /*
70 | In a scenario where we are interacting with a live backend, expect to use
71 | something similar to;
72 |
73 | Tyto.boardList.fetch().done (data) ->
74 | Tyto.start()
75 |
76 | However, as we are only loading from localStorage, we can reset collections
77 | based on what is stored in localStorage.
78 |
79 | For this we use a utility function implementing in the Utils module.
80 | */
81 |
82 | Tyto.Utils.load(window.localStorage);
83 | Tyto.start();
84 |
--------------------------------------------------------------------------------
/src/script/controllers/tyto.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Global App Controller
3 | */
4 | const AppCtrl = function(AppCtrl, App, Backbone, Marionette) {
5 | AppCtrl.Router = Marionette.AppRouter.extend({
6 | appRoutes: {
7 | 'board/:board' : 'showBoardView',
8 | 'board/:board/task/:task' : 'showEditView',
9 | 'board/:board/task/:task?:params': 'showEditView',
10 | '*path' : 'showSelectView'
11 | }
12 | });
13 | AppCtrl.Controller = Marionette.Controller.extend({
14 | start: function() {
15 | this.showMenu();
16 | if (window.localStorage && !window.localStorage.tyto) {
17 | this.showCookieBanner();
18 | }
19 | },
20 | showSelectView: function() {
21 | Tyto.SelectView = new App.Views.Select({
22 | collection: Tyto.Boards
23 | });
24 | Tyto.RootView.showChildView('Content', Tyto.SelectView);
25 | },
26 | showMenu: function() {
27 | Tyto.MenuView = new App.Views.Menu();
28 | Tyto.RootView.showChildView('Menu', Tyto.MenuView);
29 | },
30 | showCookieBanner: function() {
31 | /*
32 | Show cookie banner by creating a temporary region and showing
33 | the view.
34 | */
35 | Tyto.RootView.$el.prepend($(''));
36 | Tyto.RootView.addRegion('Cookie', '#cookie-banner');
37 | Tyto.CookieBannerView = new App.Views.CookieBanner();
38 | Tyto.RootView.showChildView('Cookie', Tyto.CookieBannerView);
39 | },
40 | showBoardView: function(id) {
41 | let cols, model, tasks;
42 | Tyto.ActiveBoard = model = Tyto.Boards.get(id);
43 | if (model) {
44 | cols = Tyto.Columns.where({
45 | boardId: model.id
46 | });
47 | tasks = Tyto.Tasks.where({
48 | boardId: model.id
49 | });
50 | Tyto.ActiveTasks.reset(tasks);
51 | Tyto.ActiveCols.reset(cols);
52 | Tyto.BoardView = new App.Views.Board({
53 | model: model,
54 | collection: Tyto.ActiveCols,
55 | options: {
56 | tasks: Tyto.ActiveTasks
57 | }
58 | });
59 | App.RootView.showChildView('Content', Tyto.BoardView);
60 | } else {
61 | App.navigate('/', true);
62 | }
63 | },
64 | showEditView: function(bId, tId, params) {
65 | let taskToEdit;
66 | const board = Tyto.Boards.get(bId);
67 | const columns = Tyto.Columns.where({
68 | boardId: bId
69 | });
70 | let parentColumn;
71 | let isNew = false;
72 | if (params) {
73 | let qS = Tyto.Utils.processQueryString(params);
74 | if (qS.isFresh === 'true') {
75 | isNew = true;
76 | taskToEdit = Tyto.TempTask = new Tyto.Models.Task({
77 | boardId: bId,
78 | id : tId
79 | });
80 | }
81 | } else {
82 | taskToEdit = Tyto.Tasks.get(tId);
83 | }
84 | if (taskToEdit && board) {
85 | Tyto.EditView = new App.Views.Edit({
86 | model : taskToEdit,
87 | board : board,
88 | columns: columns,
89 | isNew : isNew
90 | });
91 | App.RootView.showChildView('Content', Tyto.EditView);
92 | } else if (board) {
93 | Tyto.navigate('/board/' + board.id, true);
94 | } else {
95 | Tyto.navigate('/', true);
96 | }
97 | }
98 | });
99 | };
100 |
101 | export default AppCtrl;
102 |
--------------------------------------------------------------------------------
/src/script/views/time.js:
--------------------------------------------------------------------------------
1 | const TimeModal = Backbone.Marionette.ItemView.extend({
2 | template: function(args) {
3 | return Tyto.TemplateStore.timeModal(args);
4 | },
5 | className: function() {
6 | return this.domAttributes.VIEW_CLASS;
7 | },
8 | domAttributes: {
9 | VIEW_CLASS: 'tyto-time-modal',
10 | PLAY_ICON : 'play_arrow',
11 | PAUSE_ICON: 'pause'
12 | },
13 | ui: {
14 | timerBtn : '.tyto-time-modal__timer',
15 | taskDescription: '.tyto-time-modal__content-description',
16 | timerIcon : '.tyto-time-modal__timer-icon',
17 | resetBtn : '.tyto-time-modal__timer-reset',
18 | closeBtn : '.tyto-time-modal__close',
19 | timeLbl : '.tyto-time-modal__timer-lbl',
20 | hours : '.tyto-time-modal__timer-lbl-hours',
21 | minutes : '.tyto-time-modal__timer-lbl-minutes',
22 | seconds : '.tyto-time-modal__timer-lbl-seconds'
23 | },
24 | events: {
25 | 'click @ui.closeBtn': 'closeModal',
26 | 'click @ui.timerBtn': 'toggleTimer',
27 | 'click @ui.resetBtn': 'resetTimer'
28 | },
29 | startTimer: function() {
30 | const view = this;
31 | view.isTiming = true;
32 | view.ui.timerIcon.text(view.domAttributes.PAUSE_ICON);
33 | view.ui.resetBtn.attr('disabled', true);
34 | view.ui.resetBtn.removeClass('mdl-button--accent');
35 | view.ui.closeBtn.attr('disabled', true);
36 | view.ui.closeBtn.removeClass('mdl-button--accent');
37 | view.timingInterval = setInterval(function() {
38 | view.incrementTime();
39 | view.renderTime();
40 | }, 1000);
41 | },
42 | incrementTime: function() {
43 | const view = this;
44 | const time = view.model.get('timeSpent');
45 | time.seconds++;
46 | if (time.seconds >= 60) {
47 | time.seconds = 0;
48 | time.minutes++;
49 | if (time.minutes >= 60) {
50 | time.minutes = 0;
51 | return time.hours++;
52 | }
53 | }
54 | },
55 | renderTime: function() {
56 | const view = this;
57 | const newTime = Tyto.Utils.getRenderFriendlyTime(view.model.get('timeSpent'));
58 | for(let measure of ['hours', 'minutes', 'seconds']) {
59 | if (view.ui[measure].text() !== newTime[measure])
60 | view.ui[measure].text(newTime[measure]);
61 | }
62 | },
63 | onRender: function() {
64 | const view = this;
65 | view.ui.taskDescription.html(marked(view.model.get('description')));
66 | view.renderTime();
67 | },
68 | stopTimer: function() {
69 | const view = this;
70 | view.isTiming = false;
71 | view.ui.timerIcon.text(view.domAttributes.PLAY_ICON);
72 | view.ui.resetBtn.removeAttr('disabled');
73 | view.ui.resetBtn.addClass('mdl-button--accent');
74 | view.ui.closeBtn.removeAttr('disabled');
75 | view.ui.closeBtn.addClass('mdl-button--accent');
76 | clearInterval(view.timingInterval);
77 | },
78 | resetTimer: function() {
79 | const view = this;
80 | view.model.set('timeSpent', {
81 | hours: 0,
82 | minutes: 0,
83 | seconds: 0
84 | });
85 | view.renderTime();
86 | },
87 | toggleTimer: function() {
88 | const view = this;
89 | if (view.isTiming) {
90 | view.stopTimer();
91 | } else {
92 | view.startTimer();
93 | }
94 | },
95 | closeModal: function() {
96 | const view = this;
97 | view.model.save({
98 | timeSpent: view.model.get('timeSpent')
99 | });
100 | Tyto.RootView.getRegion('TimeModal').$el.remove();
101 | Tyto.RootView.removeRegion('TimeModal');
102 | Tyto.Utils.renderTime(view.options.modelView);
103 | view.destroy();
104 | }
105 | });
106 |
107 | export default TimeModal;
108 |
--------------------------------------------------------------------------------
/src/script/views/menu.js:
--------------------------------------------------------------------------------
1 | const MenuView = Backbone.Marionette.ItemView.extend({
2 | template: function(args) {
3 | return Tyto.TemplateStore.menu(args);
4 | },
5 | tagName: 'div',
6 | className: function() {
7 | return this.domAttributes.VIEW_CLASS;
8 | },
9 | ui: {
10 | addBoardBtn: '.tyto-menu__add-board',
11 | exportBtn : '.tyto-menu__export',
12 | loadBtn : '.tyto-menu__load',
13 | importBtn : '.tyto-menu__import',
14 | deleteBtn : '.tyto-menu__delete-save',
15 | exporter : '.tyto-menu__exporter',
16 | importer : '.tyto-menu__importer',
17 | action : 'button'
18 | },
19 | events: {
20 | 'click @ui.addBoardBtn': 'addBoard',
21 | 'click @ui.exportBtn' : 'exportData',
22 | 'click @ui.deleteBtn' : 'deleteAppData',
23 | 'click @ui.loadBtn' : 'initLoad',
24 | 'click @ui.importBtn' : 'initLoad',
25 | 'click @ui.action' : 'restoreContent',
26 | 'change @ui.importer' : 'handleFile'
27 | },
28 | props: {
29 | DOWNLOAD_FILE_NAME: 'barn.json'
30 | },
31 | domAttributes: {
32 | VIEW_CLASS : 'tyto-menu',
33 | MENU_VISIBLE_CLASS: 'is-visible'
34 | },
35 | onShow: function() {
36 | const view = this;
37 | /**
38 | * The MenuView of Tyto handles the JSON import and export for the
39 | * application making use of the 'Utils' modules' 'load' function.
40 | */
41 | view.reader = new FileReader();
42 | view.reader.onloadend = function(e) {
43 | const data = JSON.parse(e.target.result);
44 | if (view.activeImporter.id === view.ui.loadBtn.attr('id')) {
45 | Tyto.Utils.load(data, false, true);
46 | } else {
47 | Tyto.Utils.load(data, true, false);
48 | }
49 | Tyto.navigate('/', true);
50 | };
51 | },
52 | restoreContent: function() {
53 | const props = this.domAttributes;
54 | const $visibles = Tyto.RootView.getRegion('Menu').$el.parent().find(`.${props.MENU_VISIBLE_CLASS}`);
55 | $visibles.removeClass(props.MENU_VISIBLE_CLASS);
56 | },
57 | handleFile: function(e) {
58 | const view = this;
59 | const file = e.target.files[0];
60 | if ((file.type.match('application/json')) || (file.name.indexOf('.json' !== -1))) {
61 | view.reader.readAsText(file);
62 | this.ui.importer[0].value = null;
63 | } else {
64 | alert('[tyto] only valid json files allowed');
65 | }
66 | },
67 | initLoad: function(e) {
68 | this.activeImporter = e.currentTarget;
69 | const anchor = this.ui.importer[0];
70 | if (window.File && window.FileReader && window.FileList && window.Blob) {
71 | anchor.click();
72 | } else {
73 | alert('[tyto] Unfortunately the file APIs are not fully supported in your browser');
74 | }
75 | },
76 | exportData: function() {
77 | const view = this;
78 | const anchor = view.ui.exporter[0];
79 | const exportable = {};
80 | _.forOwn(window.localStorage, function(val, key) {
81 | if (key.indexOf('tyto') !== -1) {
82 | return exportable[key] = val;
83 | }
84 | });
85 | const filename = view.props.DOWNLOAD_FILE_NAME;
86 | const content = `data:text/plain,${JSON.stringify(exportable)}`;
87 | anchor.setAttribute('download', filename);
88 | anchor.setAttribute('href', content);
89 | anchor.click();
90 | },
91 | deleteAppData: function() {
92 | _.forOwn(window.localStorage, function(val, key) {
93 | if (key.indexOf('tyto') !== -1 && key !== 'tyto') {
94 | return window.localStorage.removeItem(key);
95 | }
96 | });
97 | Tyto.Boards.reset();
98 | Tyto.Columns.reset();
99 | Tyto.Tasks.reset();
100 | Tyto.navigate('/', true);
101 | },
102 | addBoard: function() {
103 | Tyto.navigate('board/' + Tyto.Boards.create().id, true);
104 | }
105 | });
106 |
107 | export default MenuView;
108 |
--------------------------------------------------------------------------------
/gulp-config.js:
--------------------------------------------------------------------------------
1 | var env = 'public/',
2 | vendorDir = 'node_modules/',
3 | pkg = require('./package.json');
4 | module.exports = {
5 | pkg: {
6 | name: pkg.name
7 | },
8 | pluginOpts: {
9 | pug: {
10 | data : {
11 | name : pkg.name,
12 | description: pkg.description
13 | }
14 | },
15 | coffee: {
16 | bare: true
17 | },
18 | minify: {
19 | keepSpecialComments: 1
20 | },
21 | gSize: {
22 | showFiles: true
23 | },
24 | browserSync: {
25 | port : 1987,
26 | startPath: '/public',
27 | server : {
28 | baseDir: './'
29 | }
30 | },
31 | uglify: {
32 | preserveComments: 'license'
33 | },
34 | rename: {
35 | suffix: '.min'
36 | },
37 | order: {
38 | stylus: [
39 | '_var.stylus',
40 | '_typography.stylus',
41 | '_functions.stylus',
42 | 'base.stylus',
43 | '**/*.stylus'
44 | ]
45 | },
46 | prefix: [
47 | 'last 3 versions',
48 | 'Blackberry 10',
49 | 'Android 3',
50 | 'Android 4'
51 | ],
52 | wrap: '(function() { <%= contents %> }());',
53 | load: {
54 | rename: {
55 | 'gulp-gh-pages' : 'deploy',
56 | 'gulp-util' : 'gUtil',
57 | 'gulp-minify-css' : 'minify',
58 | 'gulp-autoprefixer' : 'prefix',
59 | 'gulp-template-store' : 'template'
60 | }
61 | }
62 | },
63 | paths: {
64 | base : env,
65 | sources: {
66 | script: [
67 | 'src/script/**/*.js'
68 | ],
69 | img: [
70 | 'src/img/**/*.*'
71 | ],
72 | json: [
73 | 'src/json/**/*.json'
74 | ],
75 | vendor: {
76 | js: [
77 | vendorDir + 'jquery/dist/jquery.js',
78 | vendorDir + 'lodash/index.js',
79 | vendorDir + 'backbone/backbone.js',
80 | vendorDir + 'backbone.wreqr/lib/backbone.wreqr.js',
81 | vendorDir + 'backbone.localstorage/backbone.localStorage.js',
82 | vendorDir + 'backbone.marionette/lib/backbone.marionette.js',
83 | /**
84 | * In order to use jquery/jquery-ui, need to require specific
85 | * modules in order to get sortable working.
86 | */
87 | vendorDir + 'jquery-ui/ui/data.js',
88 | vendorDir + 'jquery-ui/ui/widget.js',
89 | vendorDir + 'jquery-ui/ui/widgets/mouse.js',
90 | vendorDir + 'jquery-ui/ui/widgets/sortable.js',
91 | vendorDir + 'jquery-ui/ui/scroll-parent.js',
92 | vendorDir + 'jquery-ui/ui/version.js',
93 | vendorDir + 'jquery-ui/ui/ie.js',
94 | vendorDir + 'jquery-ui-touch-punch/jquery.ui.touch-punch.min.js',
95 | vendorDir + 'material-design-lite/material.js',
96 | vendorDir + 'marked/marked.min.js'
97 | ],
98 | css: [
99 | vendorDir + 'normalize.css/normalize.css',
100 | vendorDir + 'material-design-lite/material.css'
101 | ],
102 | fonts: [
103 | vendorDir + 'material-design-icons/iconfont/**/*.{eot,ttf,woff,woff2}'
104 | ]
105 | },
106 | markup: [
107 | 'src/markup/*.pug',
108 | 'src/markup/layout-blocks/**/*.pug'
109 | ],
110 | docs : 'src/markup/*.pug',
111 | templates: 'src/markup/templates/**/*.pug',
112 | style : 'src/style/**/*.styl',
113 | overwatch: env + '**/*.*'
114 | },
115 | destinations: {
116 | html : env,
117 | js : env + 'js/',
118 | css : env + 'css/',
119 | img : env + 'img/',
120 | fonts : env + 'fonts/',
121 | templates: 'src/script/templates/',
122 | build : '',
123 | dist : './dist',
124 | test : 'testEnv/'
125 | }
126 | }
127 | };
128 |
--------------------------------------------------------------------------------
/src/script/views/task.js:
--------------------------------------------------------------------------------
1 | const TaskView = Backbone.Marionette.ItemView.extend({
2 | tagName: 'div',
3 | className: function() {
4 | return this.domAttributes.VIEW_CLASS + this.model.attributes.color;
5 | },
6 | attributes: function() {
7 | const attr = {};
8 | attr[this.domAttributes.VIEW_ATTR] = this.model.get('id');
9 | return attr;
10 | },
11 | template: function(args) {
12 | return Tyto.TemplateStore.task(args);
13 | },
14 | ui: {
15 | deleteTask : '.tyto-task__delete-task',
16 | editTask : '.tyto-task__edit-task',
17 | trackTask : '.tyto-task__track-task',
18 | description : '.tyto-task__description',
19 | title : '.tyto-task__title',
20 | menu : '.tyto-task__menu',
21 | hours : '.tyto-task__time__hours',
22 | minutes : '.tyto-task__time__minutes',
23 | time : '.tyto-task__time',
24 | editDescription: '.tyto-task__description-edit',
25 | suggestions : '.tyto-task__suggestions'
26 | },
27 | events: {
28 | 'click @ui.deleteTask' : 'deleteTask',
29 | 'click @ui.editTask' : 'editTask',
30 | 'click @ui.trackTask' : 'trackTask',
31 | 'blur @ui.title' : 'saveTaskTitle',
32 | 'keydown @ui.title' : 'saveTaskTitle',
33 | 'blur @ui.editDescription': 'saveTaskDescription',
34 | 'click @ui.description' : 'showEditMode',
35 |
36 | /**
37 | * NOTE:: These are functions that are bootstrapped in from
38 | * the 'Suggestions' module.
39 | */
40 | 'keypress @ui.editDescription': 'handleKeyInteraction',
41 | 'keydown @ui.editDescription' : 'handleKeyInteraction',
42 | 'keyup @ui.editDescription' : 'handleKeyInteraction',
43 | 'click @ui.suggestions' : 'selectSuggestion'
44 | },
45 | domAttributes: {
46 | VIEW_CLASS : 'tyto-task mdl-card mdl-shadow--2dp bg--',
47 | VIEW_ATTR : 'data-task-id',
48 | IS_BEING_ADDED_CLASS: 'is--adding-task',
49 | COLUMN_CLASS : '.tyto-column',
50 | TASK_CONTAINER_CLASS: '.tyto-column__tasks',
51 | HIDDEN_UTIL_CLASS : 'is--hidden',
52 | INDICATOR : '.indicator'
53 | },
54 | getMDLMap: function() {
55 | const view = this;
56 | return [
57 | {
58 | el: view.ui.menu[0],
59 | component: 'MaterialMenu'
60 | }
61 | ];
62 | },
63 | handleKeyInteraction: function(e) {
64 | if (e.which === 27) this.saveTaskDescription();
65 | this.filterItems(e);
66 | },
67 | initialize: function() {
68 | const view = this;
69 | const attr = view.domAttributes;
70 | Tyto.Suggestions.bootstrapView(view);
71 | view.$el.on(Tyto.ANIMATION_EVENT, function() {
72 | $(this).parents(attr.COLUMN_CLASS).removeClass(attr.IS_BEING_ADDED_CLASS);
73 | });
74 | },
75 | deleteTask: function() {
76 | if (confirm(Tyto.CONFIRM_MESSAGE)) {
77 | this.model.destroy();
78 | }
79 | },
80 | onShow: function() {
81 | const view = this;
82 | const attr = view.domAttributes;
83 | const container = view.$el.parents(attr.TASK_CONTAINER_CLASS)[0];
84 | const column = view.$el.parents(attr.COLUMN_CLASS);
85 | if (container.scrollHeight > container.offsetHeight) {
86 | container.scrollTop = container.scrollHeight;
87 | }
88 | Tyto.Utils.upgradeMDL(view.getMDLMap());
89 | },
90 | onRender: function() {
91 | const view = this;
92 | view.ui.description.html(marked(view.model.get('description')));
93 | Tyto.Utils.autoSize(view.ui.editDescription[0]);
94 | Tyto.Utils.renderTime(view);
95 | },
96 | trackTask: function(e) {
97 | Tyto.Utils.showTimeModal(this.model, this);
98 | },
99 | editTask: function(e) {
100 | const view = this;
101 | const boardId = view.model.get('boardId');
102 | const taskId = view.model.id;
103 | const editUrl = `#board/${boardId}/task/${taskId}`;
104 | Tyto.Utils.bloom(view.ui.editTask[0], view.model.get('color'), editUrl);
105 | },
106 | showEditMode: function() {
107 | const domAttributes = this.domAttributes;
108 | const model = this.model;
109 | const desc = this.ui.description;
110 | const edit = this.ui.editDescription;
111 | desc.addClass(domAttributes.HIDDEN_UTIL_CLASS);
112 | edit.removeClass(domAttributes.HIDDEN_UTIL_CLASS)
113 | .val(model.get('description'))
114 | .focus();
115 | },
116 | saveTaskDescription: function(e) {
117 | const domAttributes = this.domAttributes;
118 | const edit = this.ui.editDescription;
119 | const desc = this.ui.description;
120 | edit.addClass(domAttributes.HIDDEN_UTIL_CLASS);
121 | desc.removeClass(domAttributes.HIDDEN_UTIL_CLASS);
122 | const content = edit.val();
123 | this.model.save({
124 | description: content
125 | });
126 | desc.html(marked(content));
127 | this.hideSuggestions();
128 | },
129 | saveTaskTitle: function(e) {
130 | this.model.save({
131 | title: this.ui.title.text().trim()
132 | });
133 | if (e.type === 'keydown' && e.which === 27)
134 | this.ui.title.blur();
135 | }
136 | });
137 |
138 | export default TaskView;
139 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/jh3y/tyto)
2 | tyto 
3 | ===
4 | __tyto__ is an extensible and customizable management and organisation tool
5 |
6 | just visit [jh3y.github.io/tyto](http://jh3y.github.io/tyto)!
7 |
8 | 
9 |
10 | ### Features
11 | * minimal UI
12 | * no accounts necessary
13 | * intuitive
14 | * extensible
15 | * localStorage persistence
16 | * time tracking
17 | * sortable UI
18 | * task linking
19 | * Markdown support
20 | * etc.
21 |
22 | 
23 |
24 | ### Why tyto? What's it for?
25 |
26 | Tyto arose from the want for an electronic post-it board without the need for accounts. Something simple and intuitive that could be easily shared.
27 |
28 | It's also the product of my own curiosity being used as an opportunity to pick up new tech stacks. It started as a vanilla JS app utilising one file and experimenting with HTML5 drag and drop. It then grew a little more, and a little more after that. Now it uses Backbone w/ Marionette. The next step? Most likely Angular 2.0 or React.
29 |
30 | 
31 |
32 | In truth, most organisations have some form of tool for what Tyto is doing. In my experience though, they can be cumbersome, clunky and just a bit noisy. Some employees tend to dislike internal tools. You still see whiteboards and walls plastered in sticky notes.
33 |
34 | This is where Tyto came from, It's my personal intuitive and minimal TodoMVC. No accounts necessary and the source isn't too hard to grasp making it rather easy to extend and customise.
35 |
36 | 
37 |
38 | ### Who's it for?
39 | Developer and project managers were the original target audience. A means to share project progression on a more _personal_ level. As opposed to publicly through an internal system. Almost like a complimentary attachment to an email.
40 |
41 | 
42 |
43 | There are no restrictions though, it's open source. Not quite right out of the box? Change it :smile:
44 |
45 | Extensibility provides a means to create a bespoke version based on theme or functionality.
46 |
47 | Tyto is a personal pet of mine and if it can help others, that's great!
48 |
49 |
50 |
51 | ###Using tyto
52 | Just want to use it? Do that by visiting [jh3y.github.io/tyto](http://jh3y.github.io/tyto).
53 |
54 | GitHub flavored markdown is supported thanks to `marked`.
55 |
56 | 
57 |
58 | This also enables you to link to boards, columns and other tasks by using the `#` character
59 |
60 | 
61 |
62 | Changes are persistent thanks to `localStorage`.
63 |
64 | Want to move to a different browser or machine though? Use the export utility to export a json file. Load this using the import utility.
65 |
66 | A persistent workflow across devices? I'm afraid I haven't implemented that. Accounts is _not_ something I am keen on implementing/hosting right now. I think it diverts from my original intention with Tyto.
67 |
68 | #### Your own environment
69 | ##### Prerequisites
70 | If you're cloning the repo and setting up the codebase you are going to need __node__(_preferably __yarn___) and __gulp__ installed.
71 |
72 | ##### Set up
73 | 1. Clone the repo.
74 |
75 | git clone https://github.com/jh3y/tyto.git
76 |
77 | 2. Navigate into the repo and install the dependencies.
78 |
79 | cd tyto
80 | yarn (alternatively, npm install)
81 |
82 | 3. Run gulp to take care of preprocessing and running a local static server instance(project utilises BrowserSync).
83 |
84 | gulp
85 |
86 | #### Hosting
87 | I would suggest just taking a snapshot of the `gh-pages` branch and ftp'ing this onto your desired server or web space. Alternatively, follow the set up procedure and FTP the contents of the `public` directory.
88 |
89 | If you wish to host on Github. Follow the set up procedure first(ideally, with a fork). When happy with your version, use the `deploy` task. This will require familiarity with `gulp-gh-pages` in order to publish to the correct location if other than `gh-pages`.
90 |
91 | 
92 |
93 | ### Development
94 | A strength of tyto is extensibility. Making changes whether it be functional or aesthetic is straightforward once familiar with the codebase.
95 |
96 | Any queries as to how things work in the codebase? Feel free to raise an issue with a question!
97 |
98 | 
99 |
100 | ####Under the hood
101 | There are a range of technologies being used under the hood.
102 | * jQuery
103 | * jQuery UI
104 | * Material Design Lite
105 | * Lodash
106 | * Backbone
107 | * Marionette
108 | * Marked
109 | * Jade
110 | * Stylus
111 | * Babel
112 | * Gulp
113 |
114 | 
115 |
116 | ### License
117 |
118 | MIT
119 |
120 | ---------------------------
121 |
122 | Made with :sparkles: [@jh3y](https://twitter.com/@_jh3y) 2017
123 |
--------------------------------------------------------------------------------
/src/json/intro.json:
--------------------------------------------------------------------------------
1 | {"tyto":"true","tyto--board":"b2206024-5879-3471-86e5-a2d3cdc6bbdf","tyto--board-b2206024-5879-3471-86e5-a2d3cdc6bbdf":"{\"title\":\"Intro Board\",\"id\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\"}","tyto--column":"8023a94e-36c4-4ecc-b2b9-772b27a547af,a6fb9c29-7d8e-f1fa-77f6-12461f151e0d,d993b375-5621-398d-6048-1ec53123b781,ec6edef3-7f75-ff9c-a018-a0a27da7d3f3,f881472f-b490-0975-3ff3-ef04dd4ff3b3","tyto--column-8023a94e-36c4-4ecc-b2b9-772b27a547af":"{\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":3,\"title\":\"Boards\",\"id\":\"8023a94e-36c4-4ecc-b2b9-772b27a547af\"}","tyto--column-a6fb9c29-7d8e-f1fa-77f6-12461f151e0d":"{\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":4,\"title\":\"Columns\",\"id\":\"a6fb9c29-7d8e-f1fa-77f6-12461f151e0d\"}","tyto--column-d993b375-5621-398d-6048-1ec53123b781":"{\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":2,\"title\":\"Menu\",\"id\":\"d993b375-5621-398d-6048-1ec53123b781\"}","tyto--column-ec6edef3-7f75-ff9c-a018-a0a27da7d3f3":"{\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":5,\"title\":\"Tasks\",\"id\":\"ec6edef3-7f75-ff9c-a018-a0a27da7d3f3\"}","tyto--column-f881472f-b490-0975-3ff3-ef04dd4ff3b3":"{\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":1,\"title\":\"Interacting with the UI\",\"id\":\"f881472f-b490-0975-3ff3-ef04dd4ff3b3\"}","tyto--task":"25370d57-8f9f-33fd-ac39-a4cb809bd1f0,2cf2ae78-d3ed-0dd5-69d4-74e63bf1c7b7,9a31bc5c-41f3-fc3c-dbec-4ccc042914bf,b21ab6e4-1ef7-0a73-f7ef-e5918e834d72,e5ccc8b3-6e22-d5d3-b0f6-8887c4b10f9d","tyto--task-25370d57-8f9f-33fd-ac39-a4cb809bd1f0":"{\"columnId\":\"ec6edef3-7f75-ff9c-a018-a0a27da7d3f3\",\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":1,\"title\":\"Handling Tasks\",\"description\":\"Tasks are created via three separate ways.
You have the option to add tasks via a separate page or use a 'quick add' style.
To quickly add tasks, use the plus icon at the bottom of a column or use the option from a columns menu.
Otherwise, use the option from the primary action button in the bottom right.
To delete a task, choose the delete option from it's menu (located top right).
To move a task within it's column or possibly to another column, drag it, using the move icon in the top left.
You can also edit a task on a separate page giving you some further options by using the edit option from the task menu.
\",\"id\":\"25370d57-8f9f-33fd-ac39-a4cb809bd1f0\",\"color\":\"purple\"}","tyto--task-2cf2ae78-d3ed-0dd5-69d4-74e63bf1c7b7":"{\"columnId\":\"d993b375-5621-398d-6048-1ec53123b781\",\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":1,\"title\":\"Menu options\",\"description\":\"Via the top level menu (accessed via top left) you can create new boards.
You are also able to import, export and load your data from tyto using options in the menu.
The difference between loading and importing is that a load will load over whatever is currently in place whereas an import will add to your current boards.
You can also wipe the localStorage for tyto, effectively wiping all of your data using the 'Delete Data' option.
\",\"id\":\"2cf2ae78-d3ed-0dd5-69d4-74e63bf1c7b7\",\"color\":\"orange\"}","tyto--task-9a31bc5c-41f3-fc3c-dbec-4ccc042914bf":"{\"columnId\":\"a6fb9c29-7d8e-f1fa-77f6-12461f151e0d\",\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":1,\"title\":\"Interacting with Columns\",\"description\":\"You can have multiple columns per board. The current maximum is 10.
Add a new column via the primary action button in the bottom right.
You can delete a column via each columns' menu in the top right and you can move a column by dragging the move icon to change the order and placement of columns.
Column titles are editable and you need just click the title, make changes, and they will be updated.
\",\"id\":\"9a31bc5c-41f3-fc3c-dbec-4ccc042914bf\",\"color\":\"red\"}","tyto--task-b21ab6e4-1ef7-0a73-f7ef-e5918e834d72":"{\"columnId\":\"8023a94e-36c4-4ecc-b2b9-772b27a547af\",\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":1,\"title\":\"What's a Board?\",\"description\":\"A board consists of a number of columns and tasks.
You can create multiple boards for different purposes.
You can then switch between boards using the dropdown next to the current board name.
To edit a board name, simply click it's name and make changes.
To add a new board, open the side menu(top left) and select 'Add a new board'.
To delete a board, use the menu on the top right of the page. Here you can also wipe the board clean or email the contents of a board.
\",\"id\":\"b21ab6e4-1ef7-0a73-f7ef-e5918e834d72\",\"color\":\"yellow\"}","tyto--task-e5ccc8b3-6e22-d5d3-b0f6-8887c4b10f9d":"{\"columnId\":\"f881472f-b490-0975-3ff3-ef04dd4ff3b3\",\"boardId\":\"b2206024-5879-3471-86e5-a2d3cdc6bbdf\",\"ordinal\":1,\"title\":\"Interaction\",\"description\":\"The aim for tyto's UI is to be minimal and clean.
Interaction points will disappear unless the user is hovering over relevant parts of the UI. This reduces clutter for the eye.
Editable items such as column name, board name, task title & description can be edited by simply clicking on the desired text.
Adding columns and tasks to a board is handled by the primary action button located in the bottom right of the screen with the plus icon.
tyto uses cookies that enable it to provide functionality and a better user experience. By using tyto and closing this message you agree to the use of cookies. Read more...