1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

customize home page css

This commit is contained in:
Sylvain 2020-01-27 17:10:29 +01:00
parent 22b6560baf
commit 656a603d6c
25 changed files with 189 additions and 51 deletions

View File

@ -47,6 +47,7 @@
- [TODO DEPLOY] add the `USER_CONFIRMATION_NEEDED_TO_SIGN_IN` environment variable (see [doc/environment.md](doc/environment.md#USER_CONFIRMATION_NEEDED_TO_SIGN_IN) for configuration details)
- [TODO DEPLOY] -> (only dev) `bundle install && yarn install`
- [TODO DEPLOY] `rake db:migrate && rake db:seed`
- [TODO DEPLOY] `rake fablab:fix:name_stylesheet`
## v4.2.4 2019 October 30

View File

@ -20,7 +20,7 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout
'ui.select', 'ui.calendar', 'angularMoment', 'Devise', 'DeviseModal', 'angular-growl', 'xeditable',
'checklist-model', 'unsavedChanges', 'angular-loading-bar', 'ngTouch', 'angular-google-analytics',
'angularUtils.directives.dirDisqus', 'summernote', 'elasticsearch', 'angular-medium-editor', 'naif.base64',
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock', 'vcRecaptcha'])
'minicolors', 'pascalprecht.translate', 'ngFitText', 'ngAside', 'ngCapsLock', 'vcRecaptcha', 'ui.codemirror'])
.config(['$httpProvider', 'AuthProvider', 'growlProvider', 'unsavedWarningsConfigProvider', 'AnalyticsProvider', 'uibDatepickerPopupConfig', '$provide', '$translateProvider',
function ($httpProvider, AuthProvider, growlProvider, unsavedWarningsConfigProvider, AnalyticsProvider, uibDatepickerPopupConfig, $provide, $translateProvider) {
// Google analytics

View File

@ -67,6 +67,11 @@
//= require angular-aside/dist/js/angular-aside
//= require ng-caps-lock/ng-caps-lock
//= require angular-recaptcha
//= require codemirror/lib/codemirror
//= require codemirror/addon/edit/matchbrackets
//= require codemirror/mode/css/css
//= require codemirror/mode/sass/sass
//= require angular-ui-codemirror/src/ui-codemirror
//= require_tree ./controllers
//= require_tree ./services
//= require_tree ./directives

View File

@ -60,6 +60,7 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
$scope.aboutContactsSetting = { name: 'about_contacts', value: settingsPromise.about_contacts };
$scope.homeBlogpostSetting = { name: 'home_blogpost', value: settingsPromise.home_blogpost };
$scope.homeContent = { name: 'home_content', value: settingsPromise.home_content };
$scope.homeCss = { name: 'home_css', value: settingsPromise.home_css };
$scope.machineExplicationsAlert = { name: 'machine_explications_alert', value: settingsPromise.machine_explications_alert };
$scope.trainingExplicationsAlert = { name: 'training_explications_alert', value: settingsPromise.training_explications_alert };
$scope.trainingInformationMessage = { name: 'training_information_message', value: settingsPromise.training_information_message };
@ -151,6 +152,21 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
]
}
// codemirror editor
$scope.codeMirrorEditor = null;
// Options for codemirror editor, used for custom css
$scope.codemirrorOpts = {
matchBrackets : true,
lineNumbers: true,
mode: 'sass'
}
// Show or hide advanced settings
$scope.advancedSettings = {
open: false
}
/**
* For use with 'ng-class', returns the CSS class name for the uploads previews.
* The preview may show a placeholder or the content of the file depending on the upload state.
@ -332,6 +348,14 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
)
}
/**
* Callback triggered when the codemirror editor is loaded into the DOM
* @param editor codemirror instance
*/
$scope.codemirrorLoaded = function (editor) {
$scope.codeMirrorEditor = editor;
}
/* PRIVATE SCOPE */
/**
@ -377,6 +401,11 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
privacyDraftsPromise.setting.history.forEach(function (draft) {
$scope.privacyDraftsHistory.push({ id: draft.id, name: _t('app.admin.settings.privacy.draft_from_USER_DATE', { USER: draft.user.name, DATE: moment(draft.created_at).format('L LT') }), content: draft.value });
});
// refresh codemirror to display the fetched setting
$scope.$watch('advancedSettings.open', function (newValue) {
if (newValue) $scope.codeMirrorEditor.refresh();
})
};
// init the controller (call at the end !)

View File

@ -1002,7 +1002,7 @@ angular.module('application.router', ['ui.router'])
'fablab_name', 'name_genre', 'reminder_enable', \
'reminder_delay', 'visibility_yearly', 'visibility_others', \
'display_name_enable', 'machines_sort_by', 'fab_analytics', \
'link_name', 'home_content']` }).$promise;
'link_name', 'home_content', 'home_css']` }).$promise;
}],
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }],

View File

@ -14,6 +14,7 @@
*= require summernote/dist/summernote
*= require jquery-minicolors/jquery.minicolors.css
*= require angular-aside/dist/css/angular-aside
*= require codemirror/lib/codemirror
*/
@import "app.functions";

View File

@ -28,4 +28,4 @@ li.abuse {
margin-top: 1em;
}
}
}
}

View File

@ -40,5 +40,24 @@
}
}
}
.home-page-style {
.panel {
border: 0;
.panel-heading {
background: none;
.panel-title {
font-size: 12px;
font-style: italic;
}
}
.CodeMirror {
border: 1px solid #ddd;
font-size: 12px;
height: 400px;
}
}
}
}
}

View File

@ -36,5 +36,27 @@
</form>
</div>
</div>
<div class="row m-t-lg home-page-style">
<uib-accordion>
<uib-accordion-group is-open="advancedSettings.open">
<uib-accordion-heading>
<span translate>{{ 'app.admin.settings.advanced' }}</span> <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': advancedSettings.open, 'glyphicon-chevron-right': !advancedSettings.open}"></i>
</uib-accordion-heading>
<div class="col-md-12 ">
<h4 translate>{{ 'app.admin.settings.customize_home_page_css' }}</h4>
<div ui-codemirror="{ onLoad: codemirrorLoaded }" ng-model="homeCss.value"
ui-codemirror-opts="codemirrorOpts">
</div>
<uib-alert type="info m">
<p class="text-sm font-bold">
<i class="fa fa-lightbulb-o"></i>
<span translate>{{ 'app.admin.settings.home_css_notice' }}</span>
</p>
</uib-alert>
<button name="button" class="btn btn-warning" ng-click="save(homeCss)" translate>{{ 'app.shared.buttons.save' }}</button>
</div>
</uib-accordion-group>
</uib-accordion>
</div>
</div>
</div>

View File

@ -12,6 +12,7 @@ class API::SettingsController < API::ApiController
authorize Setting
@setting = Setting.find_or_initialize_by(name: params[:name])
if @setting.save && @setting.history_values.create(value: setting_params[:value], invoicing_profile: current_user.invoicing_profile)
SettingService.new.after_update(@setting)
render status: :ok
else
render json: @setting.errors.full_messages, status: :unprocessable_entity

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# Admin defined assets (like PDF or images uploaded)
class CustomAsset < ActiveRecord::Base
has_one :custom_asset_file, as: :viewable, dependent: :destroy
accepts_nested_attributes_for :custom_asset_file, allow_destroy: true
@ -11,6 +14,6 @@ class CustomAsset < ActiveRecord::Base
after_update :update_stylesheet if :viewable_changed?
def update_stylesheet
Stylesheet.first.rebuild! if %w[profile-image-file].include? name
Stylesheet.theme.rebuild! if %w[profile-image-file].include? name
end
end

View File

@ -3,6 +3,7 @@
# Setting is a configuration element of the platform. Only administrators are allowed to modify Settings
# For some settings, changing them will involve some callback actions (like rebuilding the stylesheets if the theme color Setting is changed).
# A full history of the previous values is kept in database with the date and the author of the change
# after_update callback is handled by SettingService
class Setting < ActiveRecord::Base
has_many :history_values
validates :name, inclusion:
@ -71,20 +72,8 @@ class Setting < ActiveRecord::Base
hub_public_key
fab_analytics
link_name
home_content] }
after_update :update_stylesheet, :notify_privacy_policy_changed if :value_changed?
def update_stylesheet
Stylesheet.first&.rebuild! if %w[main_color secondary_color].include? name
end
def notify_privacy_policy_changed
return unless name == 'privacy_body'
NotifyPrivacyUpdateWorker.perform_async(id)
end
home_content
home_css] }
def value
last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).first
last_value&.value

View File

@ -6,19 +6,23 @@
class Stylesheet < ActiveRecord::Base
validates_presence_of :contents
def rebuild!
return unless Stylesheet.primary && Stylesheet.secondary
## ===== THEME =====
update(contents: Stylesheet.css)
def rebuild!
if Stylesheet.primary && Stylesheet.secondary && name == 'theme'
update(contents: Stylesheet.css)
elsif name == 'home_page'
update(contents: Stylesheet.home_page_css)
end
end
def self.build_sheet!
return unless Stylesheet.primary && Stylesheet.secondary
if Stylesheet.first
Stylesheet.first.rebuild!
if Stylesheet.theme
Stylesheet.theme.rebuild!
else
Stylesheet.create!(contents: Stylesheet.css)
Stylesheet.create!(contents: Stylesheet.css, name: 'theme')
end
end
@ -50,6 +54,10 @@ class Stylesheet < ActiveRecord::Base
Stylesheet.primary.paint.to_rgb.insert(3, 'a').insert(-2, ", #{alpha}")
end
def self.theme
Stylesheet.find_by(name: 'theme')
end
def self.css # rubocop:disable Metrics/AbcSize
<<~CSS
.bg-red { background-color: #{Stylesheet.primary}; }
@ -91,4 +99,28 @@ class Stylesheet < ActiveRecord::Base
section#cookies-modal div.cookies-consent .cookies-actions button.accept { background-color: #{Stylesheet.secondary}; }
CSS
end
## ===== HOME PAGE =====
def self.home_style
style = Setting.find_by(name: 'home_css')&.value
".home-page { #{style} }"
end
def self.build_home!
if Stylesheet.home_page
Stylesheet.home_page.rebuild!
else
Stylesheet.create!(contents: Stylesheet.home_page_css, name: 'home_page')
end
end
def self.home_page
Stylesheet.find_by(name: 'home_page')
end
def self.home_page_css
engine = Sass::Engine.new(home_style, syntax: :scss)
engine.render.presence || '.home-page {}'
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
# Settings are saved in two database tables: Settings and HistoryValues.
# Due to the way the controller updates the settings, we cannot safely use ActiveRecord's callbacks (eg. after_update, after_commit...)
# so this service provides a wrapper around these operations.
class SettingService
def after_update(setting)
# update the stylesheet
Stylesheet.theme&.rebuild! if %w[main_color secondary_color].include? setting.name
Stylesheet.home_page&.rebuild! if setting.name == 'home_css'
# notify about a change in privacy policy
NotifyPrivacyUpdateWorker.perform_async(id) if setting.name == 'privacy_body'
end
end

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true
# Build a cached version of the CSS stylesheet
class StylesheetSweeper < ActionController::Caching::Sweeper
observe Stylesheet
def after_update(record)
if record.contents_changed?
expire_page(:controller => 'stylesheets', action: 'show', id: record.id)
end
expire_page(controller: 'stylesheets', action: 'show', id: record.id) if record.contents_changed?
end
end
end

View File

@ -65,9 +65,12 @@
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= stylesheet_link_tag 'app.printer', media: 'print' %>
<% if !Stylesheet.first.nil? %>
<link rel="stylesheet" media="all" href="<%= stylesheet_path(Stylesheet.first.id) %>-<%=Stylesheet.first.updated_at.to_i.to_s%>.css" />
<% end %>
<% unless Stylesheet.theme.nil? %>
<link rel="stylesheet" media="all" href="<%= stylesheet_path(Stylesheet.theme.id) %>-<%= Stylesheet.theme.updated_at.to_i.to_s %>.css" />
<% end %>
<% unless Stylesheet.home_page.nil? %>
<link rel="stylesheet" media="all" href="<%= stylesheet_path(Stylesheet.home_page.id) %>-<%= Stylesheet.home_page.updated_at.to_i.to_s %>.css" />
<% end %>
<base href="/"></base>
<% if CustomAsset.get_url('favicon-file') %>

View File

@ -1,3 +1,6 @@
# For development & test environments, copy this file to database.yml
# For staging & production environments, use docker/database.yml
development:
adapter: postgresql
host: localhost
@ -18,22 +21,3 @@ test:
pool: 25
username: postgres
password: postgres
staging:
adapter: postgresql
host: localhost
encoding: unicode
database: fabmanager_development
pool: 25
username: postgres
password: postgres
production:
adapter: postgresql
host: localhost
encoding: unicode
database: fabmanager_development
pool: 25
username: postgres
password: postgres

View File

@ -940,6 +940,7 @@ fr:
item_events: "Prochains événements"
home_content: "la page d'accueil"
home_content_reset: "La page d'accueil a bien été restaurée dans sa configuration initiale."
home_css: "la feuille de style de la page d'accueil"
home_blogpost: "la brève de la page d'accueil"
twitter_name: "nom du flux Twitter"
link_name: "l'intitulé du lien vers la page \"À propos\""
@ -969,6 +970,9 @@ fr:
elements_ordering: "Ordre d'affichage des éléments"
machines_order: "Ordre des machines"
display_machines_sorted_by: "Afficher les machines triées par"
advanced: "Paramètres avancés"
customize_home_page_css: "Personnaliser la feuille de style de la page d'accueil"
home_css_notice: "Vous pouvez utiliser la syntaxe SASS. Pas de garantie que ça va pas péter avec les updates"
sort_by:
default: "Défaut"
name: "Nom"

View File

@ -0,0 +1,5 @@
class AddNameToStylesheet < ActiveRecord::Migration
def change
add_column :stylesheets, :name, :string
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20191202135507) do
ActiveRecord::Schema.define(version: 20200127111404) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -760,6 +760,7 @@ ActiveRecord::Schema.define(version: 20191202135507) do
t.text "contents"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
end
create_table "subscriptions", force: :cascade do |t|

View File

@ -427,6 +427,7 @@ unless Setting.find_by(name: 'secondary_color').try(:value)
end
Stylesheet.build_sheet!
Stylesheet.build_home!
unless Setting.find_by(name: 'training_information_message').try(:value)
setting = Setting.find_or_initialize_by(name: 'training_information_message')

View File

@ -1,3 +1,6 @@
# For staging & production environments, copy this file to database.yml
# For development & test environments, you can use this file OR config/database.yml.default
default: &default
adapter: postgresql
encoding: unicode

View File

@ -143,5 +143,12 @@ namespace :fablab do
attached_object: u
end
end
desc '[release 4.3.0] add name to theme stylesheet'
task name_stylesheet: :environment do
Stylesheet.order(:created_at).first.update_attributes(
name: 'theme'
)
end
end
end

View File

@ -51,10 +51,12 @@
"angular-translate-loader-partial": "2.18",
"angular-ui-bootstrap": "0.14.3",
"angular-ui-calendar": "https://github.com/angular-ui/ui-calendar.git#1.0.1",
"angular-ui-codemirror": "^0.3.0",
"angular-unsavedchanges": "0.2",
"angular-xeditable": "0.10",
"bootstrap-switch": "3.3.2",
"checklist-model": "0.2",
"codemirror": "^4.8.0",
"d3": "3.5",
"elasticsearch-browser": "3.1",
"font-awesome": "4.3.0",

View File

@ -182,6 +182,11 @@ angular-ui-bootstrap@0.14.3:
version "1.0.1"
resolved "https://github.com/angular-ui/ui-calendar.git#f0ab8e186da6b946eafa12ab4154578c1fdf1e1d"
angular-ui-codemirror@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/angular-ui-codemirror/-/angular-ui-codemirror-0.3.0.tgz#e4286fc50f393f2a6e697c1bf3170424e12cbb60"
integrity sha1-5ChvxQ85PypuaXwb8xcEJOEsu2A=
angular-unsavedchanges@0.2:
version "0.2.5"
resolved "https://registry.yarnpkg.com/angular-unsavedchanges/-/angular-unsavedchanges-0.2.5.tgz#34be961d547051f2c6d536c60a5b42f565c8858b"
@ -301,6 +306,11 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
codemirror@^4.8.0:
version "4.13.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-4.13.0.tgz#209772d38a7bb99647c37b500db121110dd9af6f"
integrity sha1-IJdy04p7uZZHw3tQDbEhEQ3Zr28=
coffee-script@~1.7.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.7.1.tgz#62996a861780c75e6d5069d13822723b73404bfc"