slices 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +3 -0
- data/README.md +51 -0
- data/Rakefile +9 -0
- data/app/assets/images/slices/ajax-loader.gif +0 -0
- data/app/assets/images/slices/asset-background.png +0 -0
- data/app/assets/images/slices/asset-spinner.gif +0 -0
- data/app/assets/images/slices/bg_header.gif +0 -0
- data/app/assets/images/slices/black-Linen.png +0 -0
- data/app/assets/images/slices/calendar.svg +68 -0
- data/app/assets/images/slices/chosen-sprite.png +0 -0
- data/app/assets/images/slices/drag-handle.svg +9 -0
- data/app/assets/images/slices/icon_admins.png +0 -0
- data/app/assets/images/slices/icon_app.png +0 -0
- data/app/assets/images/slices/icon_assets.png +0 -0
- data/app/assets/images/slices/icon_collapse.png +0 -0
- data/app/assets/images/slices/icon_drag.png +0 -0
- data/app/assets/images/slices/icon_files.png +0 -0
- data/app/assets/images/slices/icon_generic_file.png +0 -0
- data/app/assets/images/slices/icon_images.png +0 -0
- data/app/assets/images/slices/icon_padlock.png +0 -0
- data/app/assets/images/slices/icon_page.png +0 -0
- data/app/assets/images/slices/icon_search.png +0 -0
- data/app/assets/images/slices/icon_set-link.png +0 -0
- data/app/assets/images/slices/icon_set.png +0 -0
- data/app/assets/images/slices/icon_sitemap.png +0 -0
- data/app/assets/images/slices/icon_snippets.png +0 -0
- data/app/assets/images/slices/icon_template.jpg +0 -0
- data/app/assets/images/slices/icon_upload_happy.png +0 -0
- data/app/assets/images/slices/icon_upload_sad.png +0 -0
- data/app/assets/images/slices/icon_upload_thinking.png +0 -0
- data/app/assets/images/slices/noise.png +0 -0
- data/app/assets/images/slices/sitemap_icon_ghost.png +0 -0
- data/app/assets/images/slices/sitemap_icon_home.png +0 -0
- data/app/assets/images/slices/sitemap_icon_page.png +0 -0
- data/app/assets/images/slices/sitemap_icon_set_page.png +0 -0
- data/app/assets/images/slices/sitemap_icon_virtual_page.png +0 -0
- data/app/assets/images/slices/sitemap_overlay.png +0 -0
- data/app/assets/images/slices/spinner.gif +0 -0
- data/app/assets/images/slices/trash.png +0 -0
- data/app/assets/javascripts/admin.js.erb +18 -0
- data/app/assets/javascripts/slices/app/backbones/admins.js +114 -0
- data/app/assets/javascripts/slices/app/backbones/entries.js +172 -0
- data/app/assets/javascripts/slices/app/backbones/generic.js +101 -0
- data/app/assets/javascripts/slices/app/backbones/snippets.js +113 -0
- data/app/assets/javascripts/slices/app/helpers/assets.js +61 -0
- data/app/assets/javascripts/slices/app/helpers/breadcrumbs.js +30 -0
- data/app/assets/javascripts/slices/app/helpers/composer.js +26 -0
- data/app/assets/javascripts/slices/app/helpers/date_field.js +16 -0
- data/app/assets/javascripts/slices/app/helpers/get_value.js +31 -0
- data/app/assets/javascripts/slices/app/helpers/icon_upload_names.js.erb +5 -0
- data/app/assets/javascripts/slices/app/helpers/layout.js +20 -0
- data/app/assets/javascripts/slices/app/helpers/sitemap.js +150 -0
- data/app/assets/javascripts/slices/app/helpers/slice_preview.js +48 -0
- data/app/assets/javascripts/slices/app/helpers/tagging.js +73 -0
- data/app/assets/javascripts/slices/app/helpers/token_field.js +17 -0
- data/app/assets/javascripts/slices/app/helpers/upload_icons.js.erb +5 -0
- data/app/assets/javascripts/slices/app/helpers/uploader.js +127 -0
- data/app/assets/javascripts/slices/app/models/asset.js +29 -0
- data/app/assets/javascripts/slices/app/models/asset_collection.js +41 -0
- data/app/assets/javascripts/slices/app/models/attachment.js +29 -0
- data/app/assets/javascripts/slices/app/models/attachment_collection.js +7 -0
- data/app/assets/javascripts/slices/app/models/composer_item.js +1 -0
- data/app/assets/javascripts/slices/app/models/composer_item_collection.js +3 -0
- data/app/assets/javascripts/slices/app/models/file.js +103 -0
- data/app/assets/javascripts/slices/app/models/page.js +186 -0
- data/app/assets/javascripts/slices/app/models/s3_file.js +64 -0
- data/app/assets/javascripts/slices/app/slices.js +661 -0
- data/app/assets/javascripts/slices/app/views/asset_editor_view.js.erb +209 -0
- data/app/assets/javascripts/slices/app/views/asset_library_view.js +720 -0
- data/app/assets/javascripts/slices/app/views/asset_thumb_view.js.erb +191 -0
- data/app/assets/javascripts/slices/app/views/attachment_composer_view.js +350 -0
- data/app/assets/javascripts/slices/app/views/attachment_view.js +101 -0
- data/app/assets/javascripts/slices/app/views/calendar_view.js +198 -0
- data/app/assets/javascripts/slices/app/views/composer_item_view.js +54 -0
- data/app/assets/javascripts/slices/app/views/composer_view.js +130 -0
- data/app/assets/javascripts/slices/app/views/date_field_view.js +177 -0
- data/app/assets/javascripts/slices/app/views/file_view.js +142 -0
- data/app/assets/javascripts/slices/app/views/token_field_view.js +253 -0
- data/app/assets/javascripts/slices/lib/freeze.js +14 -0
- data/app/assets/javascripts/slices/lib/human_file_size.js +16 -0
- data/app/assets/javascripts/slices/lib/json_patch.js +9 -0
- data/app/assets/javascripts/slices/lib/moment.js +47 -0
- data/app/assets/javascripts/slices/lib/plugins.js +101 -0
- data/app/assets/javascripts/slices/lib/sortable.js +14 -0
- data/app/assets/javascripts/slices/slices.js +27 -0
- data/app/assets/javascripts/slices/vendor/autoscroll.js +188 -0
- data/app/assets/javascripts/slices/vendor/backbone.js +38 -0
- data/app/assets/javascripts/slices/vendor/handlebars.js +1920 -0
- data/app/assets/javascripts/slices/vendor/jqmodal.js +69 -0
- data/app/assets/javascripts/slices/vendor/jquery-ui.js +274 -0
- data/app/assets/javascripts/slices/vendor/jquery-ui_nested-sortable.js +357 -0
- data/app/assets/javascripts/slices/vendor/jquery.ajaxprogress.js +76 -0
- data/app/assets/javascripts/slices/vendor/jquery.js +2 -0
- data/app/assets/javascripts/slices/vendor/livefield.js +459 -0
- data/app/assets/javascripts/slices/vendor/moment.js +6 -0
- data/app/assets/javascripts/slices/vendor/rails.js +315 -0
- data/app/assets/javascripts/slices/vendor/underscore-string.js +1 -0
- data/app/assets/javascripts/slices/vendor/underscore.js +5 -0
- data/app/assets/stylesheets/admin.css +1 -0
- data/app/assets/stylesheets/slices/admin.css.erb +2237 -0
- data/app/assets/stylesheets/slices/reset_html5.css +106 -0
- data/app/assets/stylesheets/slices/slices.css +7 -0
- data/app/controllers/admin/admin_controller.rb +10 -0
- data/app/controllers/admin/admins_controller.rb +76 -0
- data/app/controllers/admin/assets_controller.rb +53 -0
- data/app/controllers/admin/auth/omniauth_callbacks_controller.rb +15 -0
- data/app/controllers/admin/auth/passwords_controller.rb +4 -0
- data/app/controllers/admin/auth/sessions_controller.rb +4 -0
- data/app/controllers/admin/entries_controller.rb +88 -0
- data/app/controllers/admin/page_search_controller.rb +12 -0
- data/app/controllers/admin/pages_controller.rb +103 -0
- data/app/controllers/admin/site_maps_controller.rb +15 -0
- data/app/controllers/admin/snippets_controller.rb +33 -0
- data/app/controllers/application_controller.rb +4 -0
- data/app/controllers/pages_controller.rb +45 -0
- data/app/controllers/slices_controller.rb +63 -0
- data/app/controllers/static_assets_controller.rb +52 -0
- data/app/helpers/admin/admin_helper.rb +63 -0
- data/app/helpers/admin/assets_helper.rb +36 -0
- data/app/helpers/admin/entries_helper.rb +13 -0
- data/app/helpers/admin/site_maps_helper.rb +104 -0
- data/app/helpers/assets_helper.rb +64 -0
- data/app/helpers/navigation_helper.rb +195 -0
- data/app/helpers/pages_helper.rb +119 -0
- data/app/models/admin.rb +34 -0
- data/app/models/asset.rb +211 -0
- data/app/models/attachment.rb +11 -0
- data/app/models/layout.rb +44 -0
- data/app/models/page.rb +214 -0
- data/app/models/placeholder_slice.rb +8 -0
- data/app/models/set_page.rb +12 -0
- data/app/models/set_slice.rb +57 -0
- data/app/models/site_map.rb +24 -0
- data/app/models/slice.rb +80 -0
- data/app/models/snippet.rb +21 -0
- data/app/observers/asset_observer.rb +6 -0
- data/app/observers/page_observer.rb +37 -0
- data/app/presenters/entry_presenter.rb +17 -0
- data/app/presenters/page_presenter.rb +67 -0
- data/app/presenters/presenter.rb +9 -0
- data/app/presenters/set_page_presenter.rb +2 -0
- data/app/views/admin/admins/index.html.erb +26 -0
- data/app/views/admin/admins/show.html.erb +27 -0
- data/app/views/admin/assets/index.html.erb +1 -0
- data/app/views/admin/auth/passwords/edit.html.erb +20 -0
- data/app/views/admin/auth/passwords/new.html.erb +14 -0
- data/app/views/admin/auth/sessions/_form.html.erb +35 -0
- data/app/views/admin/auth/sessions/new.html.erb +14 -0
- data/app/views/admin/entries/index.html.erb +32 -0
- data/app/views/admin/pages/_breadcrumbs.html.erb +32 -0
- data/app/views/admin/pages/_slices.html.erb +27 -0
- data/app/views/admin/pages/new.html.erb +14 -0
- data/app/views/admin/pages/show.html.erb +50 -0
- data/app/views/admin/shared/_asset_storage.html.erb +17 -0
- data/app/views/admin/shared/_custom_links.html.erb +1 -0
- data/app/views/admin/shared/_custom_navigation.html.erb +1 -0
- data/app/views/admin/shared/_navigation.html.erb +5 -0
- data/app/views/admin/site_maps/_page_li.html.erb +20 -0
- data/app/views/admin/site_maps/_set_page_li.html.erb +23 -0
- data/app/views/admin/site_maps/index.html.erb +29 -0
- data/app/views/admin/snippets/form.html.erb +12 -0
- data/app/views/admin/snippets/index.html.erb +20 -0
- data/app/views/admin/snippets/update.html.erb +0 -0
- data/app/views/layouts/admin.html.erb +72 -0
- data/lib/ext/file_store_cache.rb +18 -0
- data/lib/generators/humans/USAGE +8 -0
- data/lib/generators/humans/humans_generator.rb +10 -0
- data/lib/generators/humans/templates/humans.txt +6 -0
- data/lib/generators/slice/USAGE +28 -0
- data/lib/generators/slice/slice_generator.rb +123 -0
- data/lib/generators/slice/templates/main_fields.hbs +11 -0
- data/lib/generators/slice/templates/meta_fields.hbs +11 -0
- data/lib/generators/slice/templates/page.rb +19 -0
- data/lib/generators/slice/templates/presenter.rb +53 -0
- data/lib/generators/slice/templates/set.html.erb +8 -0
- data/lib/generators/slice/templates/set_slice.rb +14 -0
- data/lib/generators/slice/templates/set_slice_fields.hbs +5 -0
- data/lib/generators/slice/templates/show.html.erb +48 -0
- data/lib/generators/slice/templates/show_slice.rb +20 -0
- data/lib/generators/slice/templates/slice.rb +58 -0
- data/lib/generators/slice/templates/slice_fields.hbs +74 -0
- data/lib/generators/templates/slices.rb +211 -0
- data/lib/mongo_search.rb +84 -0
- data/lib/paperclip_validator.rb +5 -0
- data/lib/rack_utf8_fix.rb +10 -0
- data/lib/sRGB.icc +0 -0
- data/lib/set_link_renderer.rb +31 -0
- data/lib/slices.rb +68 -0
- data/lib/slices/asset/maker.rb +55 -0
- data/lib/slices/asset/rename.rb +67 -0
- data/lib/slices/available_slices.rb +43 -0
- data/lib/slices/cms_form_builder.rb +42 -0
- data/lib/slices/config.rb +93 -0
- data/lib/slices/container_parser.rb +70 -0
- data/lib/slices/generator_macros.rb +36 -0
- data/lib/slices/has_attachments.rb +111 -0
- data/lib/slices/has_slices.rb +88 -0
- data/lib/slices/i18n.rb +6 -0
- data/lib/slices/i18n/backend.rb +32 -0
- data/lib/slices/i18n_backend.rb +24 -0
- data/lib/slices/paperclip.rb +13 -0
- data/lib/slices/position_helper.rb +98 -0
- data/lib/slices/renderer.rb +52 -0
- data/lib/slices/slices_engine.rb +51 -0
- data/lib/slices/split_date_time_field.rb +14 -0
- data/lib/slices/tasks/assets.rake +35 -0
- data/lib/slices/tasks/db.rake +50 -0
- data/lib/slices/tasks/seeds.rake +93 -0
- data/lib/slices/tasks/validate.rake +62 -0
- data/lib/slices/tree.rb +306 -0
- data/lib/slices/version.rb +4 -0
- data/lib/slices/will_paginate.rb +12 -0
- data/lib/slices/will_paginate_mongoid.rb +45 -0
- data/lib/standard_tree.rb +193 -0
- metadata +483 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
// This view presents all useful details about an asset, and allows for
|
2
|
+
// actions such as replacement and deletion.
|
3
|
+
slices.AssetEditorView = Backbone.View.extend({
|
4
|
+
|
5
|
+
className: 'asset-editor',
|
6
|
+
|
7
|
+
// This template is a monster and needs to be extracted. We could aim to
|
8
|
+
// follow the Backbone/Jammit pattern, but aiming for asset pipeline
|
9
|
+
// integration seems a more worthwhile end goal.
|
10
|
+
template: Handlebars.compile(
|
11
|
+
'<div class="fields">' +
|
12
|
+
'<div class="overview">' +
|
13
|
+
'<div class="thumb asset-library-item">' +
|
14
|
+
'<img src="{{thumbUrl}}" alt="{{name}}">' +
|
15
|
+
'<span class="name">{{displayName}}</span>' +
|
16
|
+
'</div>' +
|
17
|
+
'<div class="meta">' +
|
18
|
+
'<dl class="details">' +
|
19
|
+
'<dd><input type="text" name="name" value="{{name}}"></dd>' +
|
20
|
+
'<dd>{{size}}</dd>' +
|
21
|
+
'<dd>Added {{createdAt}}</dd>' +
|
22
|
+
'<dd><a href="{{originalUrl}}">Download original</a></dd>' +
|
23
|
+
'</dl>' +
|
24
|
+
'<div class="options">' +
|
25
|
+
'<button data-action="replace">Upload a New Version</button>' +
|
26
|
+
'</div>' +
|
27
|
+
'</div>' +
|
28
|
+
'</div>' +
|
29
|
+
'<div class="tabs">' +
|
30
|
+
'<div class="tab meta">' +
|
31
|
+
'<label>Tags</label>' +
|
32
|
+
'<textarea name="tags" rows="5">{{tags}}</textarea>' +
|
33
|
+
'</div>' +
|
34
|
+
'{{#if pages}}' +
|
35
|
+
'<div class="tab pages">' +
|
36
|
+
'<label>Appears on</label>' +
|
37
|
+
'<ul>' +
|
38
|
+
'{{{pages}}}' +
|
39
|
+
'</ul>' +
|
40
|
+
'</div>' +
|
41
|
+
'{{/if}}' +
|
42
|
+
'</div>' +
|
43
|
+
'</div>' +
|
44
|
+
'<div class="bottom-toolbar">' +
|
45
|
+
'<div class="button-group aligned-right">' +
|
46
|
+
'<button type="submit" data-action="save">Save Changes</button>' +
|
47
|
+
'<button data-action="cancel">Cancel</button>' +
|
48
|
+
'</div>' +
|
49
|
+
'<div class="button-group pinned left top">' +
|
50
|
+
'<button data-action="delete" class="delete">Delete</button>' +
|
51
|
+
'</div>' +
|
52
|
+
'</div>'
|
53
|
+
),
|
54
|
+
|
55
|
+
pageItemTemplate: Handlebars.compile(
|
56
|
+
'<li><a href="/admin/pages/{{id}}" target="_blank">{{name}}</a></li>'
|
57
|
+
),
|
58
|
+
|
59
|
+
events: {
|
60
|
+
'click [data-action="save"]' : 'save',
|
61
|
+
'click [data-action="cancel"]' : 'cancel',
|
62
|
+
'click [data-action="delete"]' : 'destroy'
|
63
|
+
},
|
64
|
+
|
65
|
+
initialize: function() {
|
66
|
+
_.bindAll(this);
|
67
|
+
this.model.on('change', this.render);
|
68
|
+
},
|
69
|
+
|
70
|
+
render: function() {
|
71
|
+
$(this.el).html(this.template(this));
|
72
|
+
this.makeUploader();
|
73
|
+
return this;
|
74
|
+
},
|
75
|
+
|
76
|
+
thumbUrl: function() {
|
77
|
+
return this.model.get('asset_url')
|
78
|
+
|| '<%= asset_path 'slices/icon_generic_file.png' %>';
|
79
|
+
},
|
80
|
+
|
81
|
+
originalUrl: function() {
|
82
|
+
return this.model.get('original_url');
|
83
|
+
},
|
84
|
+
|
85
|
+
name: function() {
|
86
|
+
return this.model.get('name');
|
87
|
+
},
|
88
|
+
|
89
|
+
tags: function() {
|
90
|
+
return this.model.get('tags');
|
91
|
+
},
|
92
|
+
|
93
|
+
// Handlebars does not call functions passed to `#each` and `#if`,
|
94
|
+
// so `pages` has to render a collections of sub-templates.
|
95
|
+
pages: function() {
|
96
|
+
return _.map(this.model.get('pages'), this.pageItemTemplate).join('');
|
97
|
+
},
|
98
|
+
|
99
|
+
url: function() {
|
100
|
+
return this.model.url();
|
101
|
+
},
|
102
|
+
|
103
|
+
displayName: function() {
|
104
|
+
if (!this.model.isImage()) return this.name();
|
105
|
+
},
|
106
|
+
|
107
|
+
createdAt: function() {
|
108
|
+
return moment(this.model.get('created_at')).calendar();
|
109
|
+
},
|
110
|
+
|
111
|
+
size: function() {
|
112
|
+
return humanFileSize(this.model.get('file_file_size'));
|
113
|
+
},
|
114
|
+
|
115
|
+
// At present, it’s beneficial to specify exactly which attributes we’re
|
116
|
+
// sending back to Slices. This may be automated in future, but for now,
|
117
|
+
// remember to add any other fields in here.
|
118
|
+
save: function(e) {
|
119
|
+
this.model.save({
|
120
|
+
name: this.$('[name="name"]').val(),
|
121
|
+
tags: this.$('[name="tags"]').val()
|
122
|
+
}, {
|
123
|
+
success: this.whenSaveSucceeds,
|
124
|
+
error: this.whenSaveFails
|
125
|
+
});
|
126
|
+
},
|
127
|
+
|
128
|
+
whenSaveSucceeds: function(model, response) {
|
129
|
+
this.close();
|
130
|
+
},
|
131
|
+
|
132
|
+
whenSaveFails: function(model, response) {},
|
133
|
+
|
134
|
+
cancel: function(e) {
|
135
|
+
this.close();
|
136
|
+
},
|
137
|
+
|
138
|
+
destroy: function(e) {
|
139
|
+
if (confirm('Are you sure you want to delete this asset?')) {
|
140
|
+
this.model.destroy();
|
141
|
+
this.close();
|
142
|
+
}
|
143
|
+
},
|
144
|
+
|
145
|
+
close: function() {
|
146
|
+
this.trigger('close');
|
147
|
+
},
|
148
|
+
|
149
|
+
makeUploader: function() {
|
150
|
+
this.uploader = new slices.Uploader({
|
151
|
+
button : this.$('[data-action="replace"]'),
|
152
|
+
drop : this.$('.thumb'),
|
153
|
+
url : this.model.url(),
|
154
|
+
params : { _method: 'put' }
|
155
|
+
});
|
156
|
+
this.uploader.bind('filesAdded', this.onFilesAdded);
|
157
|
+
this.uploader.bind('fileUploaded', this.onFileUploaded);
|
158
|
+
},
|
159
|
+
|
160
|
+
onFilesAdded: function(event) {
|
161
|
+
var files = event.files,
|
162
|
+
file = files[0];
|
163
|
+
|
164
|
+
this.fileView = new slices.FileView({ model: file });
|
165
|
+
this.$('.thumb').append(this.fileView.el);
|
166
|
+
this.fileView.model.upload(file);
|
167
|
+
|
168
|
+
this.uploader.start();
|
169
|
+
},
|
170
|
+
|
171
|
+
onFileUploaded: function(event) {
|
172
|
+
this.model.set(event.response);
|
173
|
+
},
|
174
|
+
|
175
|
+
updateFileStatus: function(file) {
|
176
|
+
this.fileView.model.upload(file);
|
177
|
+
},
|
178
|
+
|
179
|
+
});
|
180
|
+
|
181
|
+
// This somewhat ungainly method opens the editor in a modal.
|
182
|
+
slices.AssetEditorView.openModal = function(options) {
|
183
|
+
var view = new slices.AssetEditorView(options);
|
184
|
+
var modal = $('<div class="jqmWindow modal" id="asset_editor_modal">');
|
185
|
+
|
186
|
+
modal.
|
187
|
+
appendTo('body').
|
188
|
+
append(view.el).
|
189
|
+
jqm({
|
190
|
+
modal: true,
|
191
|
+
onShow: function (h) {
|
192
|
+
h.w.fadeIn(150);
|
193
|
+
modal.fadeIn(300);
|
194
|
+
},
|
195
|
+
onHide: function (h) {
|
196
|
+
h.w.fadeOut(150, function() { view.remove() });
|
197
|
+
h.o.fadeOut(300, function() { modal.remove() });
|
198
|
+
},
|
199
|
+
overlay: 40
|
200
|
+
}).
|
201
|
+
jqmShow();
|
202
|
+
|
203
|
+
view.render();
|
204
|
+
|
205
|
+
view.bind('close', function() { modal.jqmHide() });
|
206
|
+
|
207
|
+
return view;
|
208
|
+
}
|
209
|
+
|
@@ -0,0 +1,720 @@
|
|
1
|
+
// The asset library is dedicated to browsing, searching and managing of
|
2
|
+
// assets. It can be rendered in-place, or to a specific element using the
|
3
|
+
// helper method.
|
4
|
+
//
|
5
|
+
// To render the library inline in html:
|
6
|
+
//
|
7
|
+
// <script>slices.renderAssetLibrary()</script>
|
8
|
+
//
|
9
|
+
// Otherwise:
|
10
|
+
//
|
11
|
+
// slices.renderAssetLibrary({ el: '#my-target' })
|
12
|
+
//
|
13
|
+
slices.AssetLibraryView = Backbone.View.extend({
|
14
|
+
|
15
|
+
DRAG_THRESHOLD: 15,
|
16
|
+
|
17
|
+
DRAWER_HEIGHT: 250,
|
18
|
+
|
19
|
+
events: {
|
20
|
+
'keyup [type="search"]' : 'search',
|
21
|
+
'click [type="search"]' : 'search',
|
22
|
+
'click [data-action="close"]' : 'close',
|
23
|
+
'click [data-action="show-all"]' : 'showAll',
|
24
|
+
'mousedown .library-container' : 'backgroundPress',
|
25
|
+
'mousedown .resize-handle' : 'startResize'
|
26
|
+
},
|
27
|
+
|
28
|
+
thumbs: {},
|
29
|
+
|
30
|
+
selection: [],
|
31
|
+
|
32
|
+
currentSearchTerm: '',
|
33
|
+
|
34
|
+
className: 'asset-library',
|
35
|
+
|
36
|
+
// We include the full template here, so the view can be rendered anywhere
|
37
|
+
// without html needing to be present ahead of time.
|
38
|
+
template: Handlebars.compile(
|
39
|
+
'<div class="toolbar">' +
|
40
|
+
'{{{resizeHandle}}}' +
|
41
|
+
'<input type="search" id="assets-search" placeholder="Search assets...">' +
|
42
|
+
'<div class="count"></div>' +
|
43
|
+
'{{{hint}}}' +
|
44
|
+
'<div class="actions"><ul>{{actions}}</ul></div>' +
|
45
|
+
'</div>' +
|
46
|
+
'<div class="library-container">' +
|
47
|
+
'<ul class="library"></ul>' +
|
48
|
+
'</div>'
|
49
|
+
),
|
50
|
+
|
51
|
+
// The collection is instantiated during initialization, as this is an
|
52
|
+
// 'AppView' style class.
|
53
|
+
initialize: function() {
|
54
|
+
_.bindAll(this);
|
55
|
+
|
56
|
+
this.collection = new slices.AssetCollection();
|
57
|
+
this.collection.bind('add', this.add);
|
58
|
+
this.collection.bind('remove', this.remove);
|
59
|
+
this.collection.bind('reset', this.reset);
|
60
|
+
$(window).on('assets:uploadCompleted', this.fetch);
|
61
|
+
|
62
|
+
this.render();
|
63
|
+
|
64
|
+
this.scrollArea = this.$('.library-container');
|
65
|
+
this.scrollArea.on('scroll', this.onScroll);
|
66
|
+
|
67
|
+
this.initializeUploader();
|
68
|
+
|
69
|
+
this.showLoadingSpinner();
|
70
|
+
|
71
|
+
this.fetch();
|
72
|
+
},
|
73
|
+
|
74
|
+
render: function() {
|
75
|
+
$(this.el).html(this.template(this));
|
76
|
+
return this;
|
77
|
+
},
|
78
|
+
|
79
|
+
// Clear all thumbs on display and re-render the collection.
|
80
|
+
reset: function() {
|
81
|
+
this.clearThumbs();
|
82
|
+
this.collection.each(this.add);
|
83
|
+
},
|
84
|
+
|
85
|
+
// Create a thumb view for the model, render it, and stick it in
|
86
|
+
// our reference hash.
|
87
|
+
add: function(model) {
|
88
|
+
var thumb = new slices.AssetThumbView({ model: model, selectable: true });
|
89
|
+
thumb.bind('thumb:press', this.thumbPress);
|
90
|
+
thumb.bind('thumb:release', this.thumbRelease);
|
91
|
+
thumb.bind('thumb:blur', this.thumbBlur);
|
92
|
+
this.loadingSpinner().before(thumb.render().el);
|
93
|
+
this.thumbs[model.cid] = thumb;
|
94
|
+
this.updateCount();
|
95
|
+
},
|
96
|
+
|
97
|
+
// Ask the thumb view to remove itself, and delete the reference.
|
98
|
+
remove: function(model) {
|
99
|
+
this.clearThumb(model.cid);
|
100
|
+
this.updateCount();
|
101
|
+
},
|
102
|
+
|
103
|
+
// Handle special keystrokes, or defer to our debounced general search.
|
104
|
+
search: function(e) {
|
105
|
+
if (e.which == 27) this.$('[type="search"]').val('');
|
106
|
+
this._search(e);
|
107
|
+
},
|
108
|
+
|
109
|
+
// This debounced method actually performs the search on our collection.
|
110
|
+
_search: _.debounce(function(e) {
|
111
|
+
var term = this.searchTerm();
|
112
|
+
|
113
|
+
if (term === this.currentSearchTerm) return;
|
114
|
+
|
115
|
+
this.currentSearchTerm = term;
|
116
|
+
this.fetch();
|
117
|
+
}, 300),
|
118
|
+
|
119
|
+
// Assuming we’re in upload-view, take us back to normal.
|
120
|
+
showAll: function() {
|
121
|
+
if (this.uploader.files.length > 0) {
|
122
|
+
alert('Uploads are still in progress, please wait for them to finish.');
|
123
|
+
} else {
|
124
|
+
this.fetch();
|
125
|
+
}
|
126
|
+
},
|
127
|
+
|
128
|
+
// Remove all thumb views currently on display.
|
129
|
+
clearThumbs: function() {
|
130
|
+
this.updateCount('Searching…');
|
131
|
+
for (var cid in this.thumbs) this.clearThumb(cid);
|
132
|
+
},
|
133
|
+
|
134
|
+
// Remove a specific thumb.
|
135
|
+
clearThumb: function(cid) {
|
136
|
+
var thumb = this.thumbs[cid];
|
137
|
+
this.selection = _(this.selection).without(thumb);
|
138
|
+
thumb.remove();
|
139
|
+
delete this.thumbs[cid];
|
140
|
+
},
|
141
|
+
|
142
|
+
// Helper method for getting the value of the search field.
|
143
|
+
searchTerm: function() {
|
144
|
+
return this.$('[type="search"]').val();
|
145
|
+
},
|
146
|
+
|
147
|
+
// If we’re in drawer mode, we’ll display a hint that users should
|
148
|
+
// drag and drop.
|
149
|
+
hint: function() {
|
150
|
+
if (this.options.mode === 'drawer') {
|
151
|
+
return '<div class="hint">Drag & drop to place on the page</div>';
|
152
|
+
}
|
153
|
+
},
|
154
|
+
|
155
|
+
// If we’re in drawer mode, we’ll add a resize handle in here.
|
156
|
+
resizeHandle: function() {
|
157
|
+
if (this.options.mode === 'drawer') {
|
158
|
+
return '<div class="resize-handle"></div>';
|
159
|
+
}
|
160
|
+
},
|
161
|
+
|
162
|
+
// We render different action button depending on the mode.
|
163
|
+
actions: function() {
|
164
|
+
var actions = [];
|
165
|
+
|
166
|
+
actions.push('<li><a class="button" data-action="upload">Upload</a></li>');
|
167
|
+
|
168
|
+
if (this.options.mode === 'drawer') {
|
169
|
+
actions.push('<li><a class="button" data-action="close">Close</a></li>');
|
170
|
+
}
|
171
|
+
|
172
|
+
return new Handlebars.SafeString(actions.join(''));
|
173
|
+
},
|
174
|
+
|
175
|
+
// The open and close methods only apply if in 'drawer' mode.
|
176
|
+
open: function(options) {
|
177
|
+
$('#container').stop().animate({ paddingBottom: this.DRAWER_HEIGHT + 'px' });
|
178
|
+
$(this.el).stop().animate(
|
179
|
+
{ height: this.DRAWER_HEIGHT + 'px' },
|
180
|
+
_.extend({}, options, { complete: this.afterOpen })
|
181
|
+
);
|
182
|
+
},
|
183
|
+
afterOpen: function() {
|
184
|
+
this.$('input[type="search"]')[0].focus();
|
185
|
+
},
|
186
|
+
close: function() {
|
187
|
+
this.$('input[type="search"]')[0].blur();
|
188
|
+
$('#container').stop().animate({ paddingBottom: '0px' });
|
189
|
+
$(this.el).stop().animate({ height: '0px' });
|
190
|
+
},
|
191
|
+
|
192
|
+
// Thumb press/release behaviour mimicks that of operating system file
|
193
|
+
// selection. Holding down shift selects ranges of files, with memory of
|
194
|
+
// the anchor. Holding down cmd/ctrl selects multiple assets. Clicks
|
195
|
+
// without modifiers select single assets.
|
196
|
+
//
|
197
|
+
// This method is disgraceful at the moment, because there are a few
|
198
|
+
// modes of behaviour. It’s an ideal candidate for refactoring.
|
199
|
+
thumbPress: function(event, thumb) {
|
200
|
+
_.invoke(this.selection, 'deselect');
|
201
|
+
|
202
|
+
if (event.shiftKey) {
|
203
|
+
if (this.selection.length === 0) {
|
204
|
+
this.selection.push(thumb);
|
205
|
+
this.selectionAnchor = thumb;
|
206
|
+
} else {
|
207
|
+
var first = this.selectionAnchor,
|
208
|
+
start = this.collection.indexOf(first.model),
|
209
|
+
end = this.collection.indexOf(thumb.model),
|
210
|
+
lowest = Math.min(start, end),
|
211
|
+
highest = Math.max(start, end);
|
212
|
+
|
213
|
+
this.selection = [];
|
214
|
+
|
215
|
+
for (var i = lowest; i <= highest; i++) {
|
216
|
+
var thumb = this.thumbs[this.collection.at(i).cid];
|
217
|
+
this.selection.push(thumb);
|
218
|
+
}
|
219
|
+
}
|
220
|
+
} else if (event.metaKey) {
|
221
|
+
if (_(this.selection).include(thumb)) {
|
222
|
+
this.selection = _(this.selection).without(thumb);
|
223
|
+
} else {
|
224
|
+
this.selection.push(thumb);
|
225
|
+
}
|
226
|
+
this.selectionAnchor = _.sortBy(this.selection, function(thumb) {
|
227
|
+
return thumb.model.collection.indexOf(thumb.model);
|
228
|
+
})[0];
|
229
|
+
} else {
|
230
|
+
if (!_(this.selection).include(thumb)) {
|
231
|
+
this.selection = [thumb];
|
232
|
+
this.selectionAnchor = thumb;
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
_.invoke(this.selection, 'select');
|
237
|
+
|
238
|
+
if (this.selection.length > 0) this.prepareForDrag(event);
|
239
|
+
|
240
|
+
this.listenForKeys();
|
241
|
+
},
|
242
|
+
|
243
|
+
// Most selection releated behaviours happen on mousedown. The exception
|
244
|
+
// is when multiple assets are selected - a single unmodified click will
|
245
|
+
// set the selection to the clicked asset, but only when the mouse is
|
246
|
+
// released.
|
247
|
+
thumbRelease: function(event, thumb) {
|
248
|
+
if (event.metaKey || event.shiftKey) return;
|
249
|
+
|
250
|
+
_.invoke(this.selection, 'deselect');
|
251
|
+
this.selection = [thumb];
|
252
|
+
this.selectionAnchor = thumb;
|
253
|
+
_.invoke(this.selection, 'select');
|
254
|
+
},
|
255
|
+
|
256
|
+
// Thumb Blur is not the best name, but this basically implies that an
|
257
|
+
// action has taken place which should cause asset library thumb-related
|
258
|
+
// actions to take a back-seat. This is used when the asset editor is
|
259
|
+
// opened.
|
260
|
+
thumbBlur: function() {
|
261
|
+
this.deselectAll();
|
262
|
+
},
|
263
|
+
|
264
|
+
// When the background is pressed, the selection is cleared out.
|
265
|
+
backgroundPress: function(event) {
|
266
|
+
if (event.shiftKey || event.metaKey) return;
|
267
|
+
event.preventDefault();
|
268
|
+
event.stopImmediatePropagation();
|
269
|
+
this.deselectAll();
|
270
|
+
},
|
271
|
+
|
272
|
+
// Deselect all thumbs and trigger appropriate actions.
|
273
|
+
deselectAll: function() {
|
274
|
+
_.invoke(this.selection, 'deselect');
|
275
|
+
this.selection = [];
|
276
|
+
},
|
277
|
+
|
278
|
+
// Array of models from the current selection.
|
279
|
+
selectedAssets: function() {
|
280
|
+
return _.pluck(this.selection, 'model');
|
281
|
+
},
|
282
|
+
|
283
|
+
// Update the UI count of asset(s) found.
|
284
|
+
updateCount: function(message) {
|
285
|
+
this.$('.count').html(message || this.countMessage());
|
286
|
+
},
|
287
|
+
|
288
|
+
// Messages for different asset counts.
|
289
|
+
countMessages: {
|
290
|
+
default: {
|
291
|
+
none: Handlebars.compile('No assets, yet…'),
|
292
|
+
one: Handlebars.compile('Showing 1 asset'),
|
293
|
+
some: Handlebars.compile(
|
294
|
+
'Showing latest {{count}} assets of {{total}} total'
|
295
|
+
),
|
296
|
+
all: Handlebars.compile(
|
297
|
+
'Showing all {{count}} assets, latest first'
|
298
|
+
)
|
299
|
+
},
|
300
|
+
uploads: {
|
301
|
+
one: Handlebars.compile(
|
302
|
+
'Showing 1 new asset ' +
|
303
|
+
'(<a data-action="show-all">Show everything</a>)'
|
304
|
+
),
|
305
|
+
some: Handlebars.compile(
|
306
|
+
'Showing {{count}} new assets ' +
|
307
|
+
'(<a data-action="show-all">Show everything</a>)'
|
308
|
+
),
|
309
|
+
},
|
310
|
+
search: {
|
311
|
+
none: Handlebars.compile('No matching assets'),
|
312
|
+
one: Handlebars.compile('Showing 1 matching asset'),
|
313
|
+
some: Handlebars.compile(
|
314
|
+
'Showing latest {{count}} matching assets of {{total}} total'
|
315
|
+
),
|
316
|
+
all: Handlebars.compile(
|
317
|
+
'Showing all {{count}} maching assets, latest first'
|
318
|
+
)
|
319
|
+
}
|
320
|
+
},
|
321
|
+
|
322
|
+
// Returns the appropriate message count for the current context.
|
323
|
+
countMessage: function() {
|
324
|
+
var t = this.countMessages,
|
325
|
+
total = this.collection.totalEntries,
|
326
|
+
count = this.collection.length;
|
327
|
+
|
328
|
+
if (this.inUploadView) {
|
329
|
+
t = t.uploads;
|
330
|
+
|
331
|
+
if (count === 1) {
|
332
|
+
return t.one({ count: count });
|
333
|
+
} else {
|
334
|
+
return t.some({ count: count });
|
335
|
+
}
|
336
|
+
} else {
|
337
|
+
t = this.currentSearchTerm ? t.search : t.default;
|
338
|
+
|
339
|
+
if (total === 0) {
|
340
|
+
return t.none();
|
341
|
+
} else if (total === 1) {
|
342
|
+
return t.one();
|
343
|
+
} else if (total > count) {
|
344
|
+
return t.some({ total: total, count: count });
|
345
|
+
} else {
|
346
|
+
return t.all({ total: total, count: count });
|
347
|
+
}
|
348
|
+
}
|
349
|
+
},
|
350
|
+
|
351
|
+
NEXT_PAGE_THRESHOLD: 600,
|
352
|
+
|
353
|
+
// Potentially load more entries on scroll.
|
354
|
+
onScroll: _.debounce(function(e) {
|
355
|
+
if (this.loading) return;
|
356
|
+
if (this.inUploadView) return;
|
357
|
+
|
358
|
+
var library = this.$('.library'),
|
359
|
+
scrollTop = this.scrollArea.scrollTop(),
|
360
|
+
scrollBottom = scrollTop + this.scrollArea.height(),
|
361
|
+
remaining = library.height() - scrollBottom;
|
362
|
+
|
363
|
+
if (remaining <= this.NEXT_PAGE_THRESHOLD && this.collection.hasMorePages()) {
|
364
|
+
this.showLoadingSpinner();
|
365
|
+
this.fetch({ add: true });
|
366
|
+
}
|
367
|
+
}, 300),
|
368
|
+
|
369
|
+
// Spinner graphic, revealed as necessary.
|
370
|
+
loadingSpinner: _.memoize(function() {
|
371
|
+
return $(
|
372
|
+
'<li class="loading-indicator">' +
|
373
|
+
'<span class="label">Loading…</label>' +
|
374
|
+
'</li>'
|
375
|
+
).appendTo(this.$('.library'));
|
376
|
+
}),
|
377
|
+
|
378
|
+
showLoadingSpinner: function() {
|
379
|
+
this.loadingSpinner().css({ display: 'block' });
|
380
|
+
},
|
381
|
+
|
382
|
+
hideLoadingSpinner: function() {
|
383
|
+
this.loadingSpinner().css({ display: 'none' });
|
384
|
+
},
|
385
|
+
|
386
|
+
// Wrapper around the collection’s fetch method. Takes seach
|
387
|
+
// and paging into account.
|
388
|
+
fetch: function(options) {
|
389
|
+
options = _.extend({ add: false }, options);
|
390
|
+
|
391
|
+
this.loading = true;
|
392
|
+
|
393
|
+
this.exitUploadView();
|
394
|
+
this.showLoadingSpinner();
|
395
|
+
|
396
|
+
if (!options.add) this.collection.reset();
|
397
|
+
|
398
|
+
this.collection.fetch({
|
399
|
+
data: {
|
400
|
+
search: this.searchTerm(),
|
401
|
+
page: options.add ? (this.collection.currentPage + 1) : 1
|
402
|
+
},
|
403
|
+
add: options.add,
|
404
|
+
success: this.onFetchSuccess
|
405
|
+
});
|
406
|
+
},
|
407
|
+
|
408
|
+
// Hide spinner and set loading state back to false.
|
409
|
+
onFetchSuccess: function() {
|
410
|
+
this.loading = false;
|
411
|
+
this.hideLoadingSpinner();
|
412
|
+
this.updateCount();
|
413
|
+
},
|
414
|
+
|
415
|
+
// ## Drag-Drop API:
|
416
|
+
//
|
417
|
+
// Any object can hook into AssetLibrary’s drag-drop API by
|
418
|
+
// implementing the following protocol:
|
419
|
+
//
|
420
|
+
// Bind to the `assets:dragStarted` event and register to receive assets:
|
421
|
+
//
|
422
|
+
// var self = this;
|
423
|
+
//
|
424
|
+
// $(window).on('assets:dragStarted', function(event, library) {
|
425
|
+
// library.registerReceiver(self);
|
426
|
+
// });
|
427
|
+
//
|
428
|
+
// Implement the `withinBounds` method:
|
429
|
+
//
|
430
|
+
// this.withinBounds = function(x, y) {
|
431
|
+
// // Return true or false
|
432
|
+
// }
|
433
|
+
//
|
434
|
+
// Implement the `assetsOver` method:
|
435
|
+
//
|
436
|
+
// this.assetsOver = function(x, y) { ... }
|
437
|
+
//
|
438
|
+
// Implement the `assetsNotOver` method:
|
439
|
+
//
|
440
|
+
// this.assetsNotOver = function() { ... }
|
441
|
+
//
|
442
|
+
// Implement the `receiveAssets` method:
|
443
|
+
//
|
444
|
+
// this.receiveAssets = function(assets, x, y) { ... }
|
445
|
+
//
|
446
|
+
|
447
|
+
// Store the originating click, and bind onto relevant mouse events.
|
448
|
+
prepareForDrag: function(e) {
|
449
|
+
$(document).on('mousemove', this.onDrag).
|
450
|
+
on('mouseup', this.stopDrag);
|
451
|
+
|
452
|
+
this.dragOrigin = { x: e.pageX, y: e.pageY };
|
453
|
+
},
|
454
|
+
|
455
|
+
// If we’ve already started dragging, update the little helper so it follows
|
456
|
+
// the user’s mouse pointer around. Otherwise, test if we’ve crossed the
|
457
|
+
// threshold yet.
|
458
|
+
onDrag: function(e) {
|
459
|
+
if (this.dragging) {
|
460
|
+
this.dragHelper.css({ left: e.pageX + 'px', top: e.pageY + 'px' });
|
461
|
+
this.reviewReceiversForDrag(e.pageX, e.pageY);
|
462
|
+
} else {
|
463
|
+
this.testDragTreshold(e);
|
464
|
+
}
|
465
|
+
},
|
466
|
+
|
467
|
+
// Dragging only begins when the mouse is dragged over DRAG_TRESHOLD.
|
468
|
+
testDragTreshold: function(e) {
|
469
|
+
var absDelta = Math.max(
|
470
|
+
Math.abs(e.pageX - this.dragOrigin.x),
|
471
|
+
Math.abs(e.pageY - this.dragOrigin.y)
|
472
|
+
);
|
473
|
+
|
474
|
+
if (absDelta >= this.DRAG_THRESHOLD) this.startDrag(e);
|
475
|
+
},
|
476
|
+
|
477
|
+
// Set the dragging state to true and create the drag helper -
|
478
|
+
// the collection of little thumbs representing the current selection.
|
479
|
+
startDrag: function(e) {
|
480
|
+
this.dragging = true;
|
481
|
+
|
482
|
+
this.dragHelper = $('<span class="asset-drag-helper">');
|
483
|
+
this.dragHelper.css({ position: 'absolute' });
|
484
|
+
|
485
|
+
for (var i in this.selection) { var thumb = this.selection[i];
|
486
|
+
var img = thumb.$('img').clone(false);
|
487
|
+
this.dragHelper.append(img);
|
488
|
+
}
|
489
|
+
|
490
|
+
this.dragHelper.appendTo('body');
|
491
|
+
|
492
|
+
var width = Math.ceil(Math.sqrt(this.selection.length)) *
|
493
|
+
(this.dragHelper.width() / this.selection.length);
|
494
|
+
|
495
|
+
this.dragHelper.width(width);
|
496
|
+
|
497
|
+
this.receivers = [this];
|
498
|
+
|
499
|
+
$(window).trigger('assets:dragStarted', this);
|
500
|
+
|
501
|
+
window.autoscroll.start();
|
502
|
+
|
503
|
+
this.onDrag(e);
|
504
|
+
},
|
505
|
+
|
506
|
+
// Unbind mousey events, delete the drag helper, and inform potenital
|
507
|
+
// recipients that a drop is taking place.
|
508
|
+
stopDrag: function(e) {
|
509
|
+
$(document).off('mousemove', this.onDrag).
|
510
|
+
off('mouseup', this.stopDrag);
|
511
|
+
|
512
|
+
window.autoscroll.stop();
|
513
|
+
|
514
|
+
if (this.dragHelper) {
|
515
|
+
this.dragHelper.remove();
|
516
|
+
delete this.dragHelper;
|
517
|
+
}
|
518
|
+
|
519
|
+
delete this.dragging;
|
520
|
+
|
521
|
+
this.reviewReceiversForDrop(e.pageX, e.pageY);
|
522
|
+
delete this.receivers;
|
523
|
+
},
|
524
|
+
|
525
|
+
// Registers a potential asset receiver. Receivers are polled in order.
|
526
|
+
registerReceiver: function(receiver) {
|
527
|
+
this.receivers.push(receiver);
|
528
|
+
},
|
529
|
+
|
530
|
+
// On drag, we’ll want to send `assetsNotOver` to all receivers,
|
531
|
+
// then identify the receiver under the cursor (if any) and it
|
532
|
+
// `assetsOver`.
|
533
|
+
reviewReceiversForDrag: function(x, y) {
|
534
|
+
var receiver = this.identifyReceiver(x, y);
|
535
|
+
|
536
|
+
_.each(this.receivers, function(r) {
|
537
|
+
if (r !== receiver) r.assetsNotOver();
|
538
|
+
else r.assetsOver(x, y);
|
539
|
+
});
|
540
|
+
},
|
541
|
+
|
542
|
+
// On drop, we’ll identify the receiver under the cursor and send
|
543
|
+
// `receiveAssets`, with the current selection.
|
544
|
+
reviewReceiversForDrop: function(x, y) {
|
545
|
+
_.invoke(this.receivers, 'assetsNotOver');
|
546
|
+
var receiver = this.identifyReceiver(x, y);
|
547
|
+
if (receiver) receiver.receiveAssets(this.selectedAssets(), x, y);
|
548
|
+
},
|
549
|
+
|
550
|
+
// Find the first receiver in the stack for which `withinBounds`
|
551
|
+
// returns true.
|
552
|
+
identifyReceiver: function(x, y) {
|
553
|
+
return _.find(this.receivers, function(receiver) {
|
554
|
+
return receiver.withinBounds(x, y);
|
555
|
+
});
|
556
|
+
},
|
557
|
+
|
558
|
+
// Internal implementation of asset-receiver interface.
|
559
|
+
//
|
560
|
+
// The library itself is first on the stack of receivers.
|
561
|
+
// So, if an asset payload is over the library,
|
562
|
+
// it will get be the identified receiver.
|
563
|
+
|
564
|
+
withinBounds: function(x, y) {
|
565
|
+
var offset = $(this.el).offset(),
|
566
|
+
top = offset.top,
|
567
|
+
left = offset.left,
|
568
|
+
bottom = top + $(this.el).height(),
|
569
|
+
right = left + $(this.el).width();
|
570
|
+
|
571
|
+
return x >= left && x <= right && y >= top && y <= bottom;
|
572
|
+
},
|
573
|
+
|
574
|
+
assetsOver: function() {},
|
575
|
+
assetsNotOver: function() {},
|
576
|
+
receiveAssets: function() {},
|
577
|
+
|
578
|
+
keydown: function(e) {
|
579
|
+
if (this.selection.length > 0 && e.which === 8) {
|
580
|
+
e.preventDefault();
|
581
|
+
e.stopImmediatePropagation();
|
582
|
+
this.destroySelection();
|
583
|
+
}
|
584
|
+
},
|
585
|
+
|
586
|
+
// Destroy the selected assets. This sends 'destroy' to each asset
|
587
|
+
// indvidually. If the responses to the DELETE requests are a bit slow,
|
588
|
+
// then this can look a little inelegant.
|
589
|
+
destroySelection: function() {
|
590
|
+
if (this.selection.length === 1) {
|
591
|
+
var message = 'Are you sure you want to delete this asset?';
|
592
|
+
} else {
|
593
|
+
var message = Handlebars.compile(
|
594
|
+
'Are you sure you want to delete these {{count}} assets?'
|
595
|
+
)({ count: this.selection.length });
|
596
|
+
}
|
597
|
+
|
598
|
+
if (confirm(message)) {
|
599
|
+
_.invoke(this.selectedAssets(), 'destroy');
|
600
|
+
}
|
601
|
+
},
|
602
|
+
|
603
|
+
// When this mode is switched on, AssetLibrary will listen out globally
|
604
|
+
// for keydown events; primarily to catch the delete key at the moment.
|
605
|
+
//
|
606
|
+
// If a click is encountered outside the area of the library, we jump
|
607
|
+
// out of this mode.
|
608
|
+
listenForKeys: function() {
|
609
|
+
if (this.listeningForKeys) return;
|
610
|
+
this.listeningForKeys = true;
|
611
|
+
$(window).on('keydown', this.keydown);
|
612
|
+
$(window).one('mousedown', this.stopListeningForKeys);
|
613
|
+
},
|
614
|
+
|
615
|
+
// Jump out of keydown-aware mode.
|
616
|
+
stopListeningForKeys: function() {
|
617
|
+
this.listeningForKeys = false;
|
618
|
+
$(window).off('keydown', this.keydown);
|
619
|
+
},
|
620
|
+
|
621
|
+
startResize: function(e) {
|
622
|
+
e.preventDefault();
|
623
|
+
e.stopImmediatePropagation();
|
624
|
+
|
625
|
+
this.resizeOrigin = { y: e.pageY, h: this.$el.height() };
|
626
|
+
|
627
|
+
$(window).on('mousemove', this.performResize).
|
628
|
+
on('mouseup', this.endResize);
|
629
|
+
},
|
630
|
+
|
631
|
+
performResize: function(e) {
|
632
|
+
var delta = e.pageY - this.resizeOrigin.y,
|
633
|
+
height = Math.max(this.resizeOrigin.h - delta, this.DRAWER_HEIGHT);
|
634
|
+
|
635
|
+
this.$el.height(height);
|
636
|
+
},
|
637
|
+
|
638
|
+
endResize: function(e) {
|
639
|
+
$('#container').css({ paddingBottom: this.$el.height() + 'px' });
|
640
|
+
$(window).off('mousemove', this.performResize).
|
641
|
+
off('mouseup', this.endResize);
|
642
|
+
},
|
643
|
+
|
644
|
+
// Enables upload-to-library functionality.
|
645
|
+
initializeUploader: function() {
|
646
|
+
if (this.uploader) return;
|
647
|
+
|
648
|
+
this.uploader = new slices.Uploader({
|
649
|
+
button : this.$el.find('[data-action="upload"]'),
|
650
|
+
drop : this.$el.find('.library-container')
|
651
|
+
});
|
652
|
+
this.uploader.on('filesAdded', this.onFilesAdded);
|
653
|
+
this.uploader.on('fileUploaded', this.onFileUploaded);
|
654
|
+
},
|
655
|
+
|
656
|
+
// When files are added to the upload queue we create corresponding
|
657
|
+
// asset objects and add them to the collection.
|
658
|
+
onFilesAdded: function(event) {
|
659
|
+
this.enterUploadView();
|
660
|
+
|
661
|
+
_(event.files).each(function(file) {
|
662
|
+
var a = new slices.Asset({ file: file });
|
663
|
+
// This is clearly a code-smell!
|
664
|
+
file.asset = a;
|
665
|
+
// These bits are fine.
|
666
|
+
this.collection.add(a);
|
667
|
+
this.updateFileStatus(file);
|
668
|
+
}, this);
|
669
|
+
|
670
|
+
this.uploader.start();
|
671
|
+
},
|
672
|
+
|
673
|
+
// This looks weird, I know, but really all we’re doing is taking the
|
674
|
+
// response from our upload to /assets and feeding it into our
|
675
|
+
// asset model.
|
676
|
+
//onFileUploaded: function(uploader, file, transport) {
|
677
|
+
onFileUploaded: function(event) {
|
678
|
+
var file = event.file,
|
679
|
+
response = event.response;
|
680
|
+
|
681
|
+
// Update the attachment model. Silently, because we want to control
|
682
|
+
// how it redraws.
|
683
|
+
var asset = file.asset;
|
684
|
+
asset.set(response);
|
685
|
+
// Finally complete upload progress display and transition to thumbnail.
|
686
|
+
this.viewForFile(file).updateFileAndComplete(file);
|
687
|
+
},
|
688
|
+
|
689
|
+
// When uploading files we just display the new uploads. All this does is
|
690
|
+
// clear the current collection. There's also a catch in there to ensure
|
691
|
+
// we don’t accidentally clear the collection when we don’t mean to.
|
692
|
+
enterUploadView: function() {
|
693
|
+
if (this.inUploadView) return;
|
694
|
+
this.inUploadView = true;
|
695
|
+
this.collection.reset();
|
696
|
+
this.$('[type="search"]').hide();
|
697
|
+
this.updateCount('Uploading…');
|
698
|
+
},
|
699
|
+
|
700
|
+
// When we’re no longer concerned with just our uploaded files, we return
|
701
|
+
// to normal.
|
702
|
+
exitUploadView: function() {
|
703
|
+
if (!this.inUploadView) return;
|
704
|
+
this.inUploadView = false;
|
705
|
+
this.$('[type="search"]').show();
|
706
|
+
this.updateCount();
|
707
|
+
},
|
708
|
+
|
709
|
+
// Passes information from upload on to the file’s view.
|
710
|
+
updateFileStatus: function(file) {
|
711
|
+
this.viewForFile(file).updateFile(file);
|
712
|
+
},
|
713
|
+
|
714
|
+
// Returns the view object associated with the given file.
|
715
|
+
viewForFile: function(file) {
|
716
|
+
return this.thumbs[file.asset.cid];
|
717
|
+
}
|
718
|
+
|
719
|
+
});
|
720
|
+
|