├── assets
├── popup.html
├── hacktime.gif
├── happyface.png
└── poppedoutwindow.png
├── .editorconfig
├── pages
├── README.md
└── home.js
├── package.json
├── README.md
├── lib
└── styles.js
├── .gitignore
└── index.js
/assets/popup.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/hacktime.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trainyard/choo-time/HEAD/assets/hacktime.gif
--------------------------------------------------------------------------------
/assets/happyface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trainyard/choo-time/HEAD/assets/happyface.png
--------------------------------------------------------------------------------
/assets/poppedoutwindow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trainyard/choo-time/HEAD/assets/poppedoutwindow.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/pages/README.md:
--------------------------------------------------------------------------------
1 | ## history-view pages.
2 |
3 | Views that are directly mounted on the router
4 |
5 | More information: https://github.com/yoshuawuyts/choo-handbook/blob/master/guides/designing-for-reusability.md
6 |
7 | ### Generate
8 |
9 | ```bash
10 | $ choo generate page my-page
11 | ```
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "choo-time",
3 | "version": "0.2.0",
4 | "main": "index",
5 | "description": "Created with choo-cli",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "budo ./index.js --live --pushstate --open -- -g es2040",
9 | "lint": "standard --verbose | snazzy",
10 | "test": "npm run lint"
11 | },
12 | "dependencies": {
13 | "choo": "^3.2.0",
14 | "filesaver.js": "^0.2.0"
15 | },
16 | "devDependencies": {
17 | "browserify": "^13.0.1",
18 | "budo": "8.3.0",
19 | "es2040": "1.2.2",
20 | "envify": "^3.4.1",
21 | "standard": "^7.1.2",
22 | "snazzy": "^4.0.0",
23 | "uglifyify": "^3.0.2",
24 | "unassertify": "^2.0.3",
25 | "yo-yoify": "^3.3.0"
26 | },
27 | "standard": {
28 | "ignore": [
29 | "scripts"
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # choo-time
2 |
3 | 
4 |
5 | ## Quickstart
6 |
7 | It is quick and easy to start hacking time. Add it to your project using npm, implement it as a choo middleware, and then add a line of code to your reducers hash.
8 |
9 | installation from npm:
10 |
11 | `npm install choo-time --save-dev`
12 |
13 | add as a middleware:
14 |
15 | ```
16 | const chooTime = require('choo-time')
17 |
18 | app.use(chooTime())
19 | ```
20 |
21 | add the "refresh" reducer
22 |
23 | ```
24 | var myModel = {
25 | reducers: {
26 | refresh: (data, state) => state,
27 | ...
28 | }
29 | }
30 | ```
31 |
32 | start your app and then you should see a red smiley face button
33 | in the button right of your app.
34 |
35 | 
36 |
37 | Start click through your app and watch the tool button enumerate.
38 | This is a count of your actions.
39 |
40 | Click on the button and check out the complete timeline of actions for your app
41 |
42 | 
43 |
--------------------------------------------------------------------------------
/pages/home.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 |
3 | const changeHistory = e => send('focus', idx)
4 |
5 | function readFile(file, done) {
6 | var reader = new FileReader();
7 | reader.onload = e => done(e.target.result);
8 | reader.readAsText(file);
9 | }
10 |
11 | module.exports = (state, prev, send) => html`
12 |
13 |
14 |
28 |
29 | ${state.history.map(({actionName, argsPassedToAction, resultingState}, idx) => {
30 | return html`
31 | -
32 |
35 |
`
36 | })}
37 |
38 |
39 |
40 |
41 |
42 |
43 | `
44 |
--------------------------------------------------------------------------------
/lib/styles.js:
--------------------------------------------------------------------------------
1 | const btnStyle = `
2 | padding: inherit;
3 | margin: inherit;
4 | display: inherit;
5 | background-color: #38818F;
6 | border-radius: 999em;
7 | border: inherit;
8 | min-width: 56px;
9 | height: 56px;
10 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
11 | line-height: 1;
12 | color: white;
13 | bottom: 8px;
14 | right: 8px;
15 | position: fixed;
16 | z-index: 2;
17 | font-size: 18px;
18 | cursor: pointer;
19 | display: inline-block;
20 | `
21 |
22 |
23 | const popupStyle = `
24 | /*
25 | #1d1f20
26 | #343436
27 | #666
28 | #ccc
29 | */
30 |
31 | body {
32 | margin: 0;
33 | font-size: 12px;
34 | font-family: 'Monaco', courier, monospace;
35 | }
36 |
37 | .list-header {
38 | font-weight: bold;
39 | border-bottom: thin solid #333;
40 | padding: 6px 12px;
41 | }
42 | .list {
43 | list-style-type: none;
44 | margin: 0;
45 | padding: 0;
46 | border-radius: 2px;
47 | overflow: hidden;
48 | position: relative;
49 | }
50 | .list li {
51 | border-bottom: thin solid #333;
52 | line-height: 1.2rem;
53 | margin: 0;
54 | }
55 | button {
56 | outline: none;
57 | background: inherit;
58 | font-size: inherit;
59 | line-height: inherit;
60 | text-align: inherit;
61 | color: inherit;
62 | border: none;
63 | display: inline-block;
64 | width: 100%;
65 | padding: 0;
66 | margin: 0;
67 | cursor: pointer;
68 | padding: 6px 12px;
69 | transition: 0.2s;
70 | }
71 |
72 |
73 | .list button:hover {
74 | background: #999;
75 | color: #333;
76 | }
77 | .list button:focus {
78 | font-style: oblique;
79 | text-decoration: underline;
80 | background: #444;
81 | }
82 | .list button:active {
83 | background: #eee;
84 | }
85 |
86 | .action-col {
87 | box-sizing: border-box;
88 | background: #1d1f20;
89 | color: #ccc;
90 | vertical-align: top;
91 | width: 33%;
92 | display: inline-block;
93 | overflow:hidden;
94 | padding: 5px;
95 | position:absolute;
96 | height: 100%;
97 | }
98 |
99 | .state-col {
100 | margin-left: 33%;
101 | box-sizing: border-box;
102 | vertical-align: top;
103 | width: 66%;
104 | display: inline-block;
105 | overflow:hidden;
106 | }
107 |
108 | .state-col textarea {
109 | padding: 0.5em;
110 | width: 100%;
111 | height: 100%;
112 | min-height: 420px;
113 | border: none;
114 | outline: none;
115 | background-color: #f6f6f6;
116 | font-size: 14px;
117 | font-family: 'Monaco', courier, monospace;
118 | overflow: hidden;
119 | }
120 |
121 | .btn-control {
122 | background: #8c8c8c;
123 | color: #ffffff;
124 | padding: 4px 8px;
125 | display: inline-block;
126 | width: auto;
127 | border-radius: 8px;
128 | margin-left: 4px;
129 | }
130 | .btn-control:hover {
131 | color: #777;
132 | background: #eee;
133 | }
134 | `
135 |
136 | module.exports = {
137 | popupStyle,
138 | btnStyle
139 | }
140 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # generated gitignore file
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # node-waf configuration
27 | .lock-wscript
28 |
29 | # Compiled binary addons (http://nodejs.org/api/addons.html)
30 | build/Release
31 | dist
32 |
33 | # Dependency directories
34 | node_modules
35 | jspm_packages
36 |
37 | # Optional npm cache directory
38 | .npm
39 |
40 | # Optional REPL history
41 | .node_repl_history
42 |
43 | # Mac OSX
44 | *.DS_Store
45 | .AppleDouble
46 | .LSOverride
47 |
48 | # Icon must end with two \r
49 | Icon
50 |
51 |
52 | # Thumbnails
53 | ._*
54 |
55 | # Files that might appear in the root of a volume
56 | .DocumentRevisions-V100
57 | .fseventsd
58 | .Spotlight-V100
59 | .TemporaryItems
60 | .Trashes
61 | .VolumeIcon.icns
62 | .com.apple.timemachine.donotpresent
63 |
64 | # Directories potentially created on remote AFP share
65 | .AppleDB
66 | .AppleDesktop
67 | Network Trash Folder
68 | Temporary Items
69 | .apdisk
70 |
71 |
72 | # Linux
73 |
74 | # temporary files which can be created if a process still has a handle open of a deleted file
75 | .fuse_hidden*
76 |
77 | # KDE directory preferences
78 | .directory
79 |
80 | # Linux trash folder which might appear on any partition or disk
81 | .Trash-*
82 |
83 | # Windows
84 |
85 | # Windows image file caches
86 | Thumbs.db
87 | ehthumbs.db
88 |
89 | # Folder config file
90 | Desktop.ini
91 |
92 | # Recycle Bin used on file shares
93 | $RECYCLE.BIN/
94 |
95 | # Windows Installer files
96 | *.cab
97 | *.msi
98 | *.msm
99 | *.msp
100 |
101 | # Windows shortcuts
102 | *.lnk
103 |
104 | # VS Code
105 | .vscode
106 |
107 | #IntelliJ
108 |
109 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
110 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
111 |
112 | # User-specific stuff:
113 | .idea/workspace.xml
114 | .idea/tasks.xml
115 | .idea/dictionaries
116 | .idea/vcs.xml
117 | .idea/jsLibraryMappings.xml
118 |
119 | # Sensitive or high-churn files:
120 | .idea/dataSources.ids
121 | .idea/dataSources.xml
122 | .idea/dataSources.local.xml
123 | .idea/sqlDataSources.xml
124 | .idea/dynamic.xml
125 | .idea/uiDesigner.xml
126 |
127 | # Gradle:
128 | .idea/gradle.xml
129 | .idea/libraries
130 |
131 | # Mongo Explorer plugin:
132 | .idea/mongoSettings.xml
133 |
134 | ## File-based project format:
135 | *.iws
136 |
137 | ## Plugin-specific files:
138 |
139 | # IntelliJ
140 | /out/
141 |
142 | # mpeltonen/sbt-idea plugin
143 | .idea_modules/
144 |
145 | # JIRA plugin
146 | atlassian-ide-plugin.xml
147 |
148 | # Crashlytics plugin (for Android Studio and IntelliJ)
149 | com_crashlytics_export_strings.xml
150 | crashlytics.properties
151 | crashlytics-build.properties
152 | fabric.properties
153 |
154 | # misc
155 |
156 | node_modules
157 | temp
158 | npm-debug.log
159 | .vscode
160 | .idea
161 | .history
162 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const choo = require('choo')
2 | const html = require('choo/html')
3 | const fileSaver = require('filesaver.js')
4 | const { btnStyle, popupStyle } = require('./lib/styles')
5 |
6 | const view = choo()
7 | const completeHistory = []
8 | const _isMinimized = true;
9 | const buttonContainer = document.createElement('div')
10 | let _focusPayload;
11 | const hostWindow = window
12 | let focusWasSet = false;
13 | let timelineWindow = null;
14 | buttonContainer.id = '__history'
15 | document.body.appendChild(buttonContainer)
16 |
17 | const initialState = {
18 | history: completeHistory,
19 | focusPayload: completeHistory.length && completeHistory.slice(-1)[0] || {},
20 | }
21 | const leModel = {
22 | state: initialState,
23 | reducers: {
24 | export: (_, state) => {
25 | let exportData = JSON.stringify({ history: state.history }, null, 2)
26 | let blob = new Blob([exportData], { type: "text/plain;charset=utf-8" })
27 | let x = new Date()
28 | let fileName = `${document.title}-${x.getFullYear()}-${x.getMonth()+1}-${x.getDate()}[${x.getHours()}.${x.getMinutes()}.${x.getSeconds()}]`
29 | fileSaver.saveAs(blob, `${fileName}.json`)
30 | return state
31 | },
32 | update: (data, state) => {
33 | return { history: data }
34 | },
35 | focus: (data, state) => {
36 | _focusPayload = state.history[data].resultingState
37 | let previousPayload = state.history[data -1]
38 | if (previousPayload) {
39 |
40 | }
41 | focusWasSet = false;
42 | hostWindow.dispatchEvent(new CustomEvent("UPDATE_HOST_STATE", { detail: true } ))
43 | return { focusPayload: state.history[data] }
44 | }
45 | },
46 | effects: {
47 | import: (data, state, send, done) => {
48 | const history = JSON.parse(data).history
49 | send('update', history, () => send('focus', history.length - 1, done))
50 | }
51 | },
52 | subscriptions: [
53 | (send, done) => {
54 | window.addEventListener('UPDATE_STATE', function (e) {
55 | if (e && e.detail && e.detail.completeHistory) {
56 | send('update', e.detail.completeHistory, done)
57 | } else {
58 | done()
59 | }
60 | }, false);
61 | }
62 | ]
63 | }
64 |
65 | view.model(leModel)
66 | view.router((route) => [
67 | route('/', require('./pages/home'))
68 | ])
69 |
70 | function tardis () {
71 | buttonContainer.appendChild(_isMinimized ? renderButton() : html``)
72 |
73 | return {
74 | wrapReducers: (reducer) => (data, state) => {
75 | if (_focusPayload && !focusWasSet) {
76 | focusWasSet = true;
77 | return reducer(null, _focusPayload)
78 | }
79 | return reducer(data, state)
80 | },
81 | wrapSubscriptions: (subs) => (send, done) => {
82 | window.addEventListener('UPDATE_HOST_STATE', () => send('refresh', {}, done), false);
83 | },
84 | onAction: (args, state, name, route) => {
85 | console.log(`OnAction (${name}):`, { args, state, name, route })
86 | },
87 | onError: (err, state, send) => {
88 | console.error({err, state})
89 | },
90 | onStateChange: (args, state, data, prev, send) => {
91 | if (prev !== 'refresh') {
92 | completeHistory.push({ actionName: prev, argsPassedToAction: args, resultingState: state })
93 | window.dispatchEvent(new CustomEvent("UPDATE_STATE", { detail: { completeHistory } } ))
94 | console.log('onStateChange:', { args, state, data, prev, completeHistory } )
95 | buttonContainer.innerHTML = ''
96 | buttonContainer.appendChild(_isMinimized ? renderButton() : html``)
97 | }
98 | }
99 | }
100 | }
101 |
102 |
103 | const renderButton = () => html`
104 |
105 | `
106 |
107 | function popWindowOut() {
108 | if (timelineWindow === null || timelineWindow.closed) {
109 | timelineWindow = window.open(``, "MsgWindow", "width=750,height=500");
110 | timelineWindow.document.write('-')
111 | timelineWindow.document.body.innerHTML = ''
112 | const styleElement = html``
113 | timelineWindow.document.body.appendChild(styleElement)
114 | timelineWindow.document.body.appendChild(view.start())
115 | timelineWindow.focus()
116 | } else {
117 | timelineWindow.focus()
118 | }
119 | }
120 |
121 |
122 | module.exports = tardis
123 |
--------------------------------------------------------------------------------