Merge lp:~mttronchetti/novacut/history into lp:novacut
- history
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 271 |
Proposed branch: | lp:~mttronchetti/novacut/history |
Merge into: | lp:novacut |
Diff against target: |
356 lines (+198/-40) 5 files modified
novacut-gtk (+11/-4) novacut/schema.py (+1/-0) ui/bucket.js (+16/-0) ui/projects.html (+96/-3) ui/projects.js (+74/-33) |
To merge this branch: | bzr merge lp:~mttronchetti/novacut/history |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jason Gerard DeRose | Approve | ||
Review via email: mp+109928@code.launchpad.net |
Commit message
Description of the change
Added lazy delete for project with a cool drop down menu called history
- 272. By Matteo Ronchetti
-
added possibility to duplicate a slice with d button on keyboard
- 273. By Matteo Ronchetti
-
added possibility to duplicate a slice with d button on keyboard
- 274. By Matteo Ronchetti
-
changed the way projects are removed
Jason Gerard DeRose (jderose) wrote : | # |
Matteo,
Okay, this is a lot better now, thanks! I'm going to merge this because I want you to have code in this release, but fair warning... this still needs some more work, and I reserve the right to use my "executive powers" to potentially make same changes later :P
Things I really like:
1) Using the same visual representation for the project inside the History/Trash... makes it much clearer what the "thing" is in there, nice work
2) Dragging out of the History/Trash to undelete... simple, and fits with the interaction patterns we use elsewhere
Things I think need improvement:
1) As it stands, "History" is a confusing term for what this menu does, and I personally don't think this is the place to expose your project's history of snapshots (that should be inside the project).
2) I don't like using onmouseover to show the History menu. Despite it's popularity in a lot of web sites lately (Google+ and Vimeo are big offenders in my mind), I think using onmouseover to reveal something is fundamentally bad UX. It also making it difficult to keep our interactions consistent between touch and mouse (but I don't think that's the biggest reason to avoid it).
Anyway, thanks again and talk to you more once you get back from the UK!
Preview Diff
1 | === modified file 'novacut-gtk' | |||
2 | --- novacut-gtk 2012-06-08 16:59:15 +0000 | |||
3 | +++ novacut-gtk 2012-06-19 17:00:25 +0000 | |||
4 | @@ -126,6 +126,7 @@ | |||
5 | 126 | 'render_job': ['job_id'], | 126 | 'render_job': ['job_id'], |
6 | 127 | 'job_rendered': ['job_id', 'file_id', 'link'], | 127 | 'job_rendered': ['job_id', 'file_id', 'link'], |
7 | 128 | 'delete_project': ['project_id'], | 128 | 'delete_project': ['project_id'], |
8 | 129 | 'sos_project': ['project_id'], | ||
9 | 129 | } | 130 | } |
10 | 130 | 131 | ||
11 | 131 | __Renderer = None | 132 | __Renderer = None |
12 | @@ -161,6 +162,7 @@ | |||
13 | 161 | hub.connect('hash_job', self.on_hash_job) | 162 | hub.connect('hash_job', self.on_hash_job) |
14 | 162 | hub.connect('render_job', self.on_render_job) | 163 | hub.connect('render_job', self.on_render_job) |
15 | 163 | hub.connect('delete_project', self.on_delete_project) | 164 | hub.connect('delete_project', self.on_delete_project) |
16 | 165 | hub.connect('sos_project', self.on_sos_project) | ||
17 | 164 | 166 | ||
18 | 165 | def post_env_init(self): | 167 | def post_env_init(self): |
19 | 166 | views.init_views(self.db, views.novacut_main) | 168 | views.init_views(self.db, views.novacut_main) |
20 | @@ -181,12 +183,17 @@ | |||
21 | 181 | 183 | ||
22 | 182 | def on_delete_project(self, hub, project_id): | 184 | def on_delete_project(self, hub, project_id): |
23 | 183 | print('delete_project', project_id) | 185 | print('delete_project', project_id) |
24 | 186 | doc = self.db.get(project_id) | ||
25 | 187 | doc['isdeleted'] = True | ||
26 | 188 | self.db.post(doc) | ||
27 | 189 | |||
28 | 190 | def on_sos_project(self,hub,project_id): | ||
29 | 191 | print('sos_project', project_id) | ||
30 | 184 | doc = self.db.get(project_id) | 192 | doc = self.db.get(project_id) |
31 | 185 | print(doc) | 193 | print(doc) |
36 | 186 | db = self.server.database(schema.project_db_name(project_id)) | 194 | doc['isdeleted'] = False |
37 | 187 | db.delete() | 195 | self.db.post(doc) |
38 | 188 | db = self.server | 196 | |
35 | 189 | db.delete('novacut-0',project_id,rev=doc['_rev']) | ||
39 | 190 | 197 | ||
40 | 191 | def on_load_project(self, hub, project_id): | 198 | def on_load_project(self, hub, project_id): |
41 | 192 | print('load_project', project_id) | 199 | print('load_project', project_id) |
42 | 193 | 200 | ||
43 | === modified file 'novacut/schema.py' | |||
44 | --- novacut/schema.py 2012-03-18 07:38:42 +0000 | |||
45 | +++ novacut/schema.py 2012-06-19 17:00:25 +0000 | |||
46 | @@ -370,6 +370,7 @@ | |||
47 | 370 | 'atime': ts, | 370 | 'atime': ts, |
48 | 371 | 'db_name': project_db_name(_id), | 371 | 'db_name': project_db_name(_id), |
49 | 372 | 'title': title, | 372 | 'title': title, |
50 | 373 | 'isdeleted': False, | ||
51 | 373 | } | 374 | } |
52 | 374 | 375 | ||
53 | 375 | 376 | ||
54 | 376 | 377 | ||
55 | === modified file 'ui/bucket.js' | |||
56 | --- ui/bucket.js 2012-04-25 04:01:29 +0000 | |||
57 | +++ ui/bucket.js 2012-06-19 17:00:25 +0000 | |||
58 | @@ -1735,6 +1735,19 @@ | |||
59 | 1735 | } | 1735 | } |
60 | 1736 | }, | 1736 | }, |
61 | 1737 | 1737 | ||
62 | 1738 | duplicate_selected: function(dnd) { | ||
63 | 1739 | var element = $(UI.selected); | ||
64 | 1740 | var doc = UI.session.get_doc(element.id); | ||
65 | 1741 | var ndoc = create_slice(doc.node.src,doc.node.stop.frame); | ||
66 | 1742 | ndoc.node.start.frame = doc.node.start.frame; | ||
67 | 1743 | UI.session.save(ndoc); | ||
68 | 1744 | var slice = new Slice(UI.session, ndoc); | ||
69 | 1745 | slice.x = 64; | ||
70 | 1746 | slice.y = 36; | ||
71 | 1747 | UI.bucket.appendChild(slice.element); | ||
72 | 1748 | UI.sequence.do_reorder(); | ||
73 | 1749 | }, | ||
74 | 1750 | |||
75 | 1738 | first: function() { | 1751 | first: function() { |
76 | 1739 | var element = $(UI.selected); | 1752 | var element = $(UI.selected); |
77 | 1740 | if (element && element.parentNode) { | 1753 | if (element && element.parentNode) { |
78 | @@ -1850,6 +1863,9 @@ | |||
79 | 1850 | UI.player.hold_and_resume(); | 1863 | UI.player.hold_and_resume(); |
80 | 1851 | } | 1864 | } |
81 | 1852 | }, | 1865 | }, |
82 | 1866 | 'U+0044': function(event) { | ||
83 | 1867 | UI.duplicate_selected(event); | ||
84 | 1868 | }, | ||
85 | 1853 | 1869 | ||
86 | 1854 | // The David Fulde key | 1870 | // The David Fulde key |
87 | 1855 | // aka Backspace aka Big Delete on a mac keyboard | 1871 | // aka Backspace aka Big Delete on a mac keyboard |
88 | 1856 | 1872 | ||
89 | === modified file 'ui/projects.html' | |||
90 | --- ui/projects.html 2012-03-06 18:12:44 +0000 | |||
91 | +++ ui/projects.html 2012-06-19 17:00:25 +0000 | |||
92 | @@ -9,16 +9,109 @@ | |||
93 | 9 | <script src="/_apps/dmedia/common.js"></script> | 9 | <script src="/_apps/dmedia/common.js"></script> |
94 | 10 | <script src="novacut.js"></script> | 10 | <script src="novacut.js"></script> |
95 | 11 | <script src="projects.js"></script> | 11 | <script src="projects.js"></script> |
96 | 12 | <script> | ||
97 | 13 | function dragstart(ev){ | ||
98 | 14 | ev.dataTransfer.setData("Text", ev.target.id); | ||
99 | 15 | ev.dataTransfer.effectAllowed = 'move'; | ||
100 | 16 | } | ||
101 | 17 | function enter(ev){ | ||
102 | 18 | ev.target.setAttribute("style","background-color: rgba(255,255,255,0.15); height: 2000px;width = 2000px;"); | ||
103 | 19 | return false; | ||
104 | 20 | } | ||
105 | 21 | function leave(ev){ | ||
106 | 22 | ev.target.setAttribute("style","height: 2000px;width = 2000px"); | ||
107 | 23 | return false; | ||
108 | 24 | } | ||
109 | 25 | function d(ev){ | ||
110 | 26 | ev.preventDefault(); | ||
111 | 27 | ev.target.setAttribute("style","height: 2000px;width = 2000px;"); | ||
112 | 28 | var data=ev.dataTransfer.getData("Text"); | ||
113 | 29 | Hub.send('sos_project', data); | ||
114 | 30 | element = document.getElementById(data); | ||
115 | 31 | element.parentNode.removeChild(element); | ||
116 | 32 | var doc = novacut.get_sync(data) | ||
117 | 33 | UI.add_item(data,doc.title,doc.time,countFiles(data)); | ||
118 | 34 | return false; | ||
119 | 35 | } | ||
120 | 36 | function over(ev){ | ||
121 | 37 | ev.preventDefault(); | ||
122 | 38 | } | ||
123 | 39 | </script> | ||
124 | 40 | <style> | ||
125 | 41 | .el{ | ||
126 | 42 | cursor: move; | ||
127 | 43 | } | ||
128 | 44 | #menu{ | ||
129 | 45 | float: right; | ||
130 | 46 | padding: 3px 8px 0 0; | ||
131 | 47 | list-style: none; | ||
132 | 48 | cursor: default; | ||
133 | 49 | } | ||
134 | 50 | |||
135 | 51 | #menu li{ | ||
136 | 52 | float: left; | ||
137 | 53 | padding: 0 0 10px 0; | ||
138 | 54 | position: relative; | ||
139 | 55 | } | ||
140 | 56 | |||
141 | 57 | #menu li:hover > a{ | ||
142 | 58 | color: #fafafa; | ||
143 | 59 | } | ||
144 | 60 | #menu li:hover > ul{ | ||
145 | 61 | display: block; | ||
146 | 62 | } | ||
147 | 63 | |||
148 | 64 | #menu ul{ /*container*/ | ||
149 | 65 | min-width: 320px; | ||
150 | 66 | min-height: 26px; | ||
151 | 67 | list-style: none; | ||
152 | 68 | margin: 0px -269px; | ||
153 | 69 | padding: 4px; | ||
154 | 70 | display: none; | ||
155 | 71 | position: absolute; | ||
156 | 72 | top: 35px; | ||
157 | 73 | left: 0; | ||
158 | 74 | z-index: 99999; | ||
159 | 75 | background: #444; | ||
160 | 76 | border-radius: 10px; | ||
161 | 77 | box-shadow: 1px 1px 10px rgba(0,0,0,0.5); | ||
162 | 78 | } | ||
163 | 79 | #menu ul li{ /* projects*/ | ||
164 | 80 | float: none; | ||
165 | 81 | margin: 10px; | ||
166 | 82 | padding: 0; | ||
167 | 83 | display: block; | ||
168 | 84 | cursor: move; | ||
169 | 85 | } | ||
170 | 86 | #menu ul:after{ | ||
171 | 87 | content: ''; | ||
172 | 88 | position: absolute; | ||
173 | 89 | left: 284px; | ||
174 | 90 | top: -15px; | ||
175 | 91 | width: 0; | ||
176 | 92 | height: 0; | ||
177 | 93 | border-left: 10px solid transparent; | ||
178 | 94 | border-right: 10px solid transparent; | ||
179 | 95 | border-bottom: 15px solid; | ||
180 | 96 | } | ||
181 | 97 | |||
182 | 98 | #menu ul:after{ | ||
183 | 99 | border-bottom-color: #444; | ||
184 | 100 | } | ||
185 | 101 | |||
186 | 102 | </style> | ||
187 | 12 | </head> | 103 | </head> |
188 | 13 | <body> | 104 | <body> |
189 | 14 | <form id="new_project" class="head"> | 105 | <form id="new_project" class="head"> |
190 | 15 | <div class="grid_row"> | 106 | <div class="grid_row"> |
191 | 16 | <input placeholder="New project name" type="text" class="grid_4" autofocus> | 107 | <input placeholder="New project name" type="text" class="grid_4" autofocus> |
192 | 17 | <button class="grid_4" disabled>Create Project</button> | 108 | <button class="grid_4" disabled>Create Project</button> |
193 | 109 | <ul id="menu"><li> | ||
194 | 110 | <a>History</a> | ||
195 | 111 | <ul id="list"></ul> | ||
196 | 112 | </li></ul> | ||
197 | 18 | </div> | 113 | </div> |
198 | 19 | </form> | 114 | </form> |
202 | 20 | 115 | <ul style="height: 200px;width = 200px;" id="projects" ondragenter="enter(event)" ondragleave="leave(event)" ondrop="d(event)" ondragover="over(event);"></ul> | |
200 | 21 | <ul id="projects"></ul> | ||
201 | 22 | |||
203 | 23 | </body> | 116 | </body> |
204 | 24 | </html> | 117 | </html> |
205 | 25 | 118 | ||
206 | === modified file 'ui/projects.js' | |||
207 | --- ui/projects.js 2012-06-08 16:59:15 +0000 | |||
208 | +++ ui/projects.js 2012-06-19 17:00:25 +0000 | |||
209 | @@ -5,22 +5,35 @@ | |||
210 | 5 | window.location.assign('cutter.html#' + project_id); | 5 | window.location.assign('cutter.html#' + project_id); |
211 | 6 | } | 6 | } |
212 | 7 | 7 | ||
213 | 8 | function countFiles(project_id){ | ||
214 | 9 | var pdb = new couch.Database("novacut-0-" + project_id.toLowerCase()); | ||
215 | 10 | try{ | ||
216 | 11 | var filecount = pdb.view_sync('doc', 'type', {key: 'dmedia/file'}).rows[0].value; | ||
217 | 12 | } | ||
218 | 13 | catch(e){ | ||
219 | 14 | var filecount = 0; | ||
220 | 15 | } | ||
221 | 16 | return filecount; | ||
222 | 17 | } | ||
223 | 8 | 18 | ||
224 | 9 | var UI = { | 19 | var UI = { |
225 | 10 | init: function() { | 20 | init: function() { |
226 | 11 | UI.form = $('new_project'); | 21 | UI.form = $('new_project'); |
227 | 12 | UI.input = UI.form.getElementsByTagName('input')[0]; | 22 | UI.input = UI.form.getElementsByTagName('input')[0]; |
228 | 13 | UI.button = UI.form.getElementsByTagName('button')[0]; | 23 | UI.button = UI.form.getElementsByTagName('button')[0]; |
231 | 14 | UI.project = new Project(novacut); | 24 | //UI.project = new Project(novacut); |
232 | 15 | UI.items = new Items('projects'); | 25 | //UI.items = new Items('projects'); |
233 | 16 | 26 | ||
234 | 17 | UI.form.onsubmit = UI.on_submit; | 27 | UI.form.onsubmit = UI.on_submit; |
235 | 18 | UI.input.oninput = UI.on_input; | 28 | UI.input.oninput = UI.on_input; |
236 | 19 | 29 | ||
237 | 30 | UI.hist = document.getElementById('list'); | ||
238 | 31 | UI.proj = document.getElementById('projects'); | ||
239 | 20 | UI.load_items(); | 32 | UI.load_items(); |
240 | 21 | }, | 33 | }, |
241 | 22 | 34 | ||
242 | 23 | load_items: function() { | 35 | load_items: function() { |
243 | 36 | //UI.add_history("qwe","nome",3333,3); | ||
244 | 24 | console.log('load_items'); | 37 | console.log('load_items'); |
245 | 25 | novacut.view(UI.on_items, 'project', 'title'); | 38 | novacut.view(UI.on_items, 'project', 'title'); |
246 | 26 | }, | 39 | }, |
247 | @@ -28,28 +41,58 @@ | |||
248 | 28 | on_items: function(req) { | 41 | on_items: function(req) { |
249 | 29 | var rows = req.read().rows; | 42 | var rows = req.read().rows; |
250 | 30 | console.log(rows.length); | 43 | console.log(rows.length); |
273 | 31 | UI.items.replace(rows, | 44 | for(var b in rows){ |
274 | 32 | function(row, items) { | 45 | var a = rows[b] |
275 | 33 | var pdb = new couch.Database("novacut-0-" + row.id.toLowerCase()); | 46 | var pdb = new couch.Database("novacut-0-" + a.id.toLowerCase()); |
276 | 34 | try{ | 47 | try{ |
277 | 35 | var filecount = pdb.view_sync('doc', 'type', {key: 'dmedia/file'}).rows[0].value; | 48 | var filecount = pdb.view_sync('doc', 'type', {key: 'dmedia/file'}).rows[0].value; |
278 | 36 | } | 49 | } |
279 | 37 | catch(e){ | 50 | catch(e){ |
280 | 38 | var filecount = 0; | 51 | var filecount = 0; |
281 | 39 | } | 52 | } |
282 | 40 | 53 | var doc = novacut.get_sync(a.id) | |
283 | 41 | var li = $el('li', {'class': 'project', 'id': row.id}); | 54 | if(!doc.isdeleted) UI.add_item(a.id,a.key,a.value,filecount); |
284 | 42 | 55 | else UI.add_history(a.id,a.key,a.value,filecount); | |
285 | 43 | var thumb = $el('div', {'class': 'thumbnail'}); | 56 | } |
286 | 44 | thumb.style.backgroundImage = "url(/_apps/dmedia/novacut-avatar-192.png)";//novacut.att_css_url(row.id); | 57 | }, |
287 | 45 | 58 | ||
288 | 46 | var info = $el('div', {'class': 'info'}); | 59 | add_history: function(id,name,date,filecount){ |
289 | 47 | info.appendChild( | 60 | var li = $el('li', {'class': 'project', 'id': id}); |
290 | 48 | $el('p', {'textContent': row.key, 'class': 'title'}) | 61 | li.setAttribute('draggable', 'true'); |
291 | 49 | ); | 62 | li.setAttribute('ondragstart', 'dragstart(event)'); |
292 | 50 | 63 | var thumb = $el('div', {'class': 'thumbnail'}); | |
293 | 51 | info.appendChild( | 64 | thumb.style.backgroundImage = "url(/_apps/dmedia/novacut-avatar-192.png)";//novacut.att_css_url(row.id); |
294 | 52 | $el('p', {'textContent': format_date(row.value)}) | 65 | |
295 | 66 | var info = $el('div', {'class': 'info'}); | ||
296 | 67 | info.appendChild( | ||
297 | 68 | $el('p', {'textContent': name, 'class': 'title'}) | ||
298 | 69 | ); | ||
299 | 70 | |||
300 | 71 | info.appendChild( | ||
301 | 72 | $el('p', {'textContent': format_date(date)}) | ||
302 | 73 | ); | ||
303 | 74 | |||
304 | 75 | info.appendChild( | ||
305 | 76 | $el('p', {'textContent': filecount + ' files'}) | ||
306 | 77 | ); | ||
307 | 78 | |||
308 | 79 | li.appendChild(thumb); | ||
309 | 80 | li.appendChild(info); | ||
310 | 81 | UI.hist.appendChild(li); | ||
311 | 82 | }, | ||
312 | 83 | |||
313 | 84 | add_item: function(id,name,date,filecount) { | ||
314 | 85 | var li = $el('li', {'class': 'project', 'id': id}); | ||
315 | 86 | var thumb = $el('div', {'class': 'thumbnail'}); | ||
316 | 87 | thumb.style.backgroundImage = "url(/_apps/dmedia/novacut-avatar-192.png)";//novacut.att_css_url(row.id); | ||
317 | 88 | |||
318 | 89 | var info = $el('div', {'class': 'info'}); | ||
319 | 90 | info.appendChild( | ||
320 | 91 | $el('p', {'textContent': name, 'class': 'title'}) | ||
321 | 92 | ); | ||
322 | 93 | |||
323 | 94 | info.appendChild( | ||
324 | 95 | $el('p', {'textContent': format_date(date)}) | ||
325 | 53 | ); | 96 | ); |
326 | 54 | 97 | ||
327 | 55 | info.appendChild( | 98 | info.appendChild( |
328 | @@ -62,21 +105,19 @@ | |||
329 | 62 | del.setAttribute('src', 'delete.png'); | 105 | del.setAttribute('src', 'delete.png'); |
330 | 63 | del.setAttribute('align', 'right'); | 106 | del.setAttribute('align', 'right'); |
331 | 64 | del.onclick = function(){ | 107 | del.onclick = function(){ |
334 | 65 | this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); | 108 | this.parentNode.parentNode.removeChild(this.parentNode); |
335 | 66 | Hub.send('delete_project', row.id) | 109 | Hub.send('delete_project', id) |
336 | 110 | var doc = novacut.get_sync(id) | ||
337 | 111 | UI.add_history(id,doc.title,doc.time,countFiles(id)); | ||
338 | 67 | } | 112 | } |
339 | 68 | li.appendChild(del); | 113 | li.appendChild(del); |
340 | 69 | thumb.onclick = function() { | 114 | thumb.onclick = function() { |
342 | 70 | Hub.send('load_project', row.id) | 115 | Hub.send('load_project', id) |
343 | 71 | } | 116 | } |
344 | 72 | info.onclick = function() { | 117 | info.onclick = function() { |
346 | 73 | Hub.send('load_project', row.id) | 118 | Hub.send('load_project', id) |
347 | 74 | } | 119 | } |
353 | 75 | 120 | UI.proj.appendChild(li); | |
349 | 76 | return li; | ||
350 | 77 | } | ||
351 | 78 | ); | ||
352 | 79 | UI.items.select(UI.project.id); | ||
354 | 80 | }, | 121 | }, |
355 | 81 | 122 | ||
356 | 82 | on_input: function(event) { | 123 | on_input: function(event) { |
Matteo,
First of all, thanks for jumping into Novacut full-force! Looks like you really have a handle on interacting with CouchDB, from both JavaScript and Python. And as lots of this lacks documentation still, that's no small feat!
First problem I encountered is the "history" DB isn't getting created if it doesn't already exist. Second, my gut feeling is we don't want to store this is a 2nd database, basically because it creates a lot of consistency and sync issues.
From what I've learned so far from working with CouchDB, you really don't want the state of a "thing" to span multiple documents, and you especially don't want it to span multiple databases. Updates to documents can arrive at one node in a different order than they were saved in another, and the problem gets much more complicated if you're talking about multiple databases.
I think instead we should have some flag attribute on the novacut/project documents. Say a hypothetical "isdeleted" attribute. And we'll make the view that lists project on the starting novacut page show all novacut/project documents such that `!doc.isdeleted` and make the History menu show all novacut/project documents such that `doc.isdeleted`.
Does that make sense? Lets discuss the schema when we're next both on IRC, okay?
Thanks again!