Skip to content

Commit

Permalink
FEATURE: support uploads for themes
Browse files Browse the repository at this point in the history
This allows themes to bundle various assets
  • Loading branch information
SamSaffron committed May 10, 2017
1 parent f709899 commit bc0b9af
Show file tree
Hide file tree
Showing 25 changed files with 368 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';
import { url } from 'discourse/lib/computed';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import showModal from 'discourse/lib/show-modal';

const THEME_UPLOAD_VAR = 2;

export default Ember.Controller.extend({

Expand Down Expand Up @@ -96,6 +99,16 @@ export default Ember.Controller.extend({
});
},

addUploadModal() {
showModal('admin-add-upload', {admin: true, name: ''});
},

addUpload(info) {
let model = this.get("model");
model.setField('common', info.name, '', info.upload_id, THEME_UPLOAD_VAR);
model.saveChanges('theme_fields').catch(e => popupAjaxError(e));
},

cancelChangeScheme() {
this.set("colorSchemeId", this.get("model.color_scheme_id"));
},
Expand Down Expand Up @@ -154,6 +167,10 @@ export default Ember.Controller.extend({
this.get("model").addChildTheme(theme);
},

removeUpload(upload) {
this.get("model").removeField(upload);
},

removeChildTheme(theme) {
this.get("model").removeChildTheme(theme);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import { ajax } from 'discourse/lib/ajax';
// import computed from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';

export default Ember.Controller.extend(ModalFunctionality, {
adminCustomizeThemesShow: Ember.inject.controller(),

actions: {
updateName() {
let name = this.get('name');
if (Em.isEmpty(name)) {
name = $('#file-input')[0].files[0].name;
this.set('name', name.split(".")[0]);
}
},
upload() {

let options = {
type: 'POST'
};

options.processData = false;
options.contentType = false;
options.data = new FormData();
let file = $('#file-input')[0].files[0];
options.data.append('file', file);

ajax('/admin/themes/upload_asset', options).then(result=>{
let upload = {
upload_id: result.upload_id,
name: this.get('name'),
original_filename: file.name
};
this.get('adminCustomizeThemesShow').send('addUpload', upload);
this.send('closeModal');
}).catch(e => {
popupAjaxError(e);
});

}
}
});
60 changes: 51 additions & 9 deletions app/assets/javascripts/admin/models/theme.js.es6
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import RestModel from 'discourse/models/rest';
import { default as computed } from 'ember-addons/ember-computed-decorators';

const THEME_UPLOAD_VAR = 2;

const Theme = RestModel.extend({

@computed('theme_fields')
Expand All @@ -14,12 +16,26 @@ const Theme = RestModel.extend({
let hash = {};
if (fields) {
fields.forEach(field=>{
hash[field.target + " " + field.name] = field;
if (!field.type_id || field.type_id < THEME_UPLOAD_VAR) {
hash[this.getKey(field)] = field;
}
});
}
return hash;
},

@computed('theme_fields', 'theme_fields.@each')
uploads(fields) {
if (!fields) {
return [];
}
return fields.filter((f)=> f.target === 'common' && f.type_id === THEME_UPLOAD_VAR);
},

getKey(field){
return field.target + " " + field.name;
},

hasEdited(target, name){
if (name) {
return !Em.isEmpty(this.getField(target, name));
Expand All @@ -31,30 +47,56 @@ const Theme = RestModel.extend({

getError(target, name) {
let themeFields = this.get("themeFields");
let key = target + " " + name;
let key = this.getKey({target,name});
let field = themeFields[key];
return field ? field.error : "";
},

getField(target, name) {
let themeFields = this.get("themeFields");
let key = target + " " + name;
let key = this.getKey({target, name})
let field = themeFields[key];
return field ? field.value : "";
},

setField(target, name, value) {
removeField(field) {
this.set("changed", true);

field.upload_id = null;
field.value = null;

return this.saveChanges("theme_fields");
},

setField(target, name, value, upload_id, type_id) {
this.set("changed", true);
let themeFields = this.get("themeFields");
let key = target + " " + name;
let field = themeFields[key];
if (!field) {
field = {name, target, value};
let field = {name, target, value, upload_id, type_id};

// slow path for uploads and so on
if (type_id && type_id > 1) {
let fields = this.get("theme_fields");
let existing = fields.find((f) =>
f.target === target &&
f.name === name &&
f.type_id === type_id);
if (existing) {
existing.value = value;
existing.upload_id = upload_id;
} else {
fields.push(field);
}
return;
}

// fast path
let key = this.getKey({target,name});
let existingField = themeFields[key];
if (!existingField) {
this.theme_fields.push(field);
themeFields[key] = field;
} else {
field.value = value;
existingField.value = value;
}
},

Expand Down
20 changes: 17 additions & 3 deletions app/assets/javascripts/admin/templates/customize-themes-show.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@

<h3>{{i18n "admin.customize.theme.css_html"}}</h3>
{{#if hasEditedFields}}

<p>{{i18n "admin.customize.theme.custom_sections"}}</p>
<ul>
{{#each editedDescriptions as |desc|}}
Expand Down Expand Up @@ -87,6 +86,21 @@
{{/if}}
</p>


<h3>{{i18n "admin.customize.theme.uploads"}}</h3>
{{#if model.uploads}}
<ul class='removable-list'>
{{#each model.uploads as |upload|}}
<li><span class='first'>${{upload.name}}: <a href={{upload.url}} target='_blank'>{{upload.filename}}</a></span>{{d-button action="removeUpload" actionParam=upload class="second btn-small cancel-edit" icon="times"}}</li>
{{/each}}
</ul>
{{else}}
<p>{{i18n "admin.customize.theme.no_uploads"}}</p>
{{/if}}
<p>
{{#d-button action="addUploadModal" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
</p>

{{#if availableChildThemes}}
<h3>{{i18n "admin.customize.theme.theme_components"}}</h3>
{{#unless model.childThemes.length}}
Expand All @@ -97,9 +111,9 @@
</label>
</p>
{{else}}
<ul>
<ul class='removable-list'>
{{#each model.childThemes as |child|}}
<li>{{#link-to 'adminCustomizeThemes.show' child replace=true}}{{child.name}}{{/link-to}} {{d-button action="removeChildTheme" actionParam=child class="btn-small cancel-edit" icon="times"}}</li>
<li>{{#link-to 'adminCustomizeThemes.show' child replace=true class='first'}}{{child.name}}{{/link-to}} {{d-button action="removeChildTheme" actionParam=child class="btn-small cancel-edit second" icon="times"}}</li>
{{/each}}
</ul>
{{/unless}}
Expand Down
12 changes: 12 additions & 0 deletions app/assets/javascripts/admin/templates/modal/admin-add-upload.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{#d-modal-body class='upload-selector' title="admin.customize.theme.add_upload"}}
<div class="inputs">
<input id="name" placeholder={{i18n 'admin.customize.theme.upload_name'}} value={{name}}><br>
<input onchange={{action "updateName"}} type="file" id="file-input" accept='*'><br>
<span class="description">{{i18n 'admin.customize.theme.upload_file_tip'}}</span>
</div>
{{/d-modal-body}}

<div class="modal-footer">
{{d-button action="upload" disabled=loading class='btn btn-primary' icon='upload' label='admin.customize.theme.upload'}}
<a href {{action "closeModal"}}>{{i18n 'cancel'}}</a>
</div>
16 changes: 16 additions & 0 deletions app/assets/stylesheets/common/admin/customize.scss
Original file line number Diff line number Diff line change
Expand Up @@ -187,5 +187,21 @@
font-size: 0.8em;
margin-top: 8px;
}

.removable-list {
list-style: none;
margin-left: 0;
li {
display: table-row;
.first {
padding-right: 8px;
padding-bottom: 10px;
min-width: 100px;
}
.first, .second {
display: table-cell;
}
}
}
}

26 changes: 24 additions & 2 deletions app/controllers/admin/themes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ def preview
redirect_to path("/"), flash: {preview_theme_key: @theme.key}
end

def upload_asset
path = params[:file].path
File.open(path) do |file|
upload = Upload.create_for(current_user.id,
file,
params[:original_filename] || File.basename(path),
File.size(path),
for_theme: true)
if upload.errors.count > 0
render json: upload.errors, status: :unprocessable_entity
else
render json: {upload_id: upload.id}, status: :created
end
end
end

def import

@theme = nil
Expand Down Expand Up @@ -184,7 +200,7 @@ def theme_params
:color_scheme_id,
:default,
:user_selectable,
theme_fields: [:name, :target, :value],
theme_fields: [:name, :target, :value, :upload_id, :type_id],
child_theme_ids: [])
end
end
Expand All @@ -194,7 +210,13 @@ def set_fields
return unless fields = theme_params[:theme_fields]

fields.each do |field|
@theme.set_field(target: field[:target], name: field[:name], value: field[:value], type_id: field[:type_id])
@theme.set_field(
target: field[:target],
name: field[:name],
value: field[:value],
type_id: field[:type_id],
upload_id: field[:upload_id]
)
end
end

Expand Down
29 changes: 28 additions & 1 deletion app/models/remote_theme.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,34 @@ def update_from_remote(importer=nil)
importer.import!
end

theme_info = JSON.parse(importer["about.json"])

theme_info["assets"]&.each do |name, relative_path|
if path = importer.real_path(relative_path)
upload = Upload.create_for(theme.user_id, File.open(path), File.basename(relative_path), File.size(path), for_theme: true)
theme.set_field(target: :common, name: name, type: :theme_upload_var, upload_id: upload.id)
end
end

theme_info["fields"]&.each do |name, info|
unless Hash === info
info = {
"target" => :common,
"type" => :theme_var,
"value" => info
}
end

if info["type"] == "color"
info["type"] = :theme_color_var
end

theme.set_field(target: info["target"] || :common,
name: name,
value: info["value"],
type: info["type"] || :theme_var)
end

Theme.targets.keys.each do |target|
ALLOWED_FIELDS.each do |field|
lookup =
Expand All @@ -62,7 +90,6 @@ def update_from_remote(importer=nil)
end
end

theme_info = JSON.parse(importer["about.json"])
self.license_url ||= theme_info["license_url"]
self.about_url ||= theme_info["about_url"]
self.remote_updated_at = Time.zone.now
Expand Down
11 changes: 4 additions & 7 deletions app/models/theme.rb
Original file line number Diff line number Diff line change
Expand Up @@ -252,19 +252,16 @@ def set_field(target:, name:, value: nil, type: nil, type_id: nil, upload_id: ni
type_id ||= type ? ThemeField.types[type.to_sym] : ThemeField.guess_type(name)
raise "Unknown type #{type} passed to set field" unless type_id

if upload_id && !value
value = ""
end

raise "Missing value for theme field" unless value
value ||= ""

field = theme_fields.find{|f| f.name==name && f.target_id == target_id && f.type_id == type_id}
if field
if value.blank?
if value.blank? && !upload_id
theme_fields.delete field.destroy
else
if field.value != value
if field.value != value || field.upload_id != upload_id
field.value = value
field.upload_id = upload_id
changed_fields << field
end
end
Expand Down
4 changes: 3 additions & 1 deletion app/models/theme_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ def ensure_scss_compiles!
begin
Stylesheet::Compiler.compile("@import \"theme_variables\"; @import \"theme_field\";",
"theme.scss",
theme_field: self.value.dup)
theme_field: self.value.dup,
theme: self.theme
)
self.error = nil unless error.nil?
rescue SassC::SyntaxError => e
self.error = e.message
Expand Down

0 comments on commit bc0b9af

Please sign in to comment.