mirror of
https://github.com/Retrospring/retrospring.git
synced 2025-01-19 09:56:03 +01:00
Merge pull request #277 from Retrospring/refactor/typescript-settings
Port Settings functionality to TypeScript
This commit is contained in:
commit
fed37ec81c
11 changed files with 231 additions and 252 deletions
|
@ -1,200 +0,0 @@
|
||||||
# see GitHub issue #11
|
|
||||||
($ document).on "submit", "form#edit_user", (evt) ->
|
|
||||||
if ($ "input#user_current_password").val().length == 0
|
|
||||||
evt.preventDefault()
|
|
||||||
$("button[data-target=#modal-passwd]").trigger 'click'
|
|
||||||
|
|
||||||
readImage = (file, callback) ->
|
|
||||||
fr = new FileReader()
|
|
||||||
fr.addEventListener "load", (e) ->
|
|
||||||
callback e.target.result
|
|
||||||
fr.readAsBinaryString file
|
|
||||||
|
|
||||||
freeURL = () ->
|
|
||||||
|
|
||||||
if window.URL? or window.webkitURL?
|
|
||||||
readImage = (file, callback) ->
|
|
||||||
callback (window.URL || window.webkitURL).createObjectURL file
|
|
||||||
freeURL = (url) ->
|
|
||||||
URL.revokeObjectURL url
|
|
||||||
|
|
||||||
|
|
||||||
# Profile pic
|
|
||||||
($ document).on 'change', 'input#user_profile_picture[type=file]', ->
|
|
||||||
input = this
|
|
||||||
|
|
||||||
($ '#profile-picture-crop-controls').slideUp 400, ->
|
|
||||||
if input.files and input.files[0]
|
|
||||||
readImage input.files[0], (src) ->
|
|
||||||
cropper = ($ '#profile-picture-cropper')
|
|
||||||
preview = ($ '#profile-picture-preview')
|
|
||||||
|
|
||||||
updateVars = (data, action) ->
|
|
||||||
($ '#profile_picture_x').val Math.floor(data.x / data.scale)
|
|
||||||
($ '#profile_picture_y').val Math.floor(data.y / data.scale)
|
|
||||||
($ '#profile_picture_w').val Math.floor(data.w / data.scale)
|
|
||||||
($ '#profile_picture_h').val Math.floor(data.h / data.scale)
|
|
||||||
# rx = 100 / data.w
|
|
||||||
# ry = 100 / data.h
|
|
||||||
# ($ '#profile-picture-preview').css
|
|
||||||
# width: Math.round(rx * preview[0].naturalWidth) + 'px'
|
|
||||||
# height: Math.round(ry * preview[0].naturalHeight) + 'px'
|
|
||||||
# marginLeft: '-' + Math.round(rx * data.x) + 'px'
|
|
||||||
# marginTop: '-' + Math.round(ry * data.y) + 'px'
|
|
||||||
|
|
||||||
cropper.on 'load', ->
|
|
||||||
if ({}.toString).call(src) == "[object URL]"
|
|
||||||
freeURL src
|
|
||||||
|
|
||||||
side = if cropper[0].naturalWidth > cropper[0].naturalHeight
|
|
||||||
cropper[0].naturalHeight
|
|
||||||
else
|
|
||||||
cropper[0].naturalWidth
|
|
||||||
|
|
||||||
cropper.guillotine
|
|
||||||
width: side
|
|
||||||
height: side
|
|
||||||
onChange: updateVars
|
|
||||||
|
|
||||||
updateVars cropper.guillotine('getData'), 'drag' # just because
|
|
||||||
|
|
||||||
unless ($ '#profile-picture-crop-controls')[0].dataset.bound?
|
|
||||||
($ '#cropper-zoom-out').click -> cropper.guillotine 'zoomOut'
|
|
||||||
($ '#cropper-zoom-in').click -> cropper.guillotine 'zoomIn'
|
|
||||||
($ '#profile-picture-crop-controls')[0].dataset.bound = true
|
|
||||||
($ '#profile-picture-crop-controls').slideDown()
|
|
||||||
|
|
||||||
cropper.attr 'src', src
|
|
||||||
|
|
||||||
($ document).on 'change', 'input#user_profile_header[type=file]', ->
|
|
||||||
input = this
|
|
||||||
|
|
||||||
($ '#profile-header-crop-controls').slideUp 400, ->
|
|
||||||
if input.files and input.files[0]
|
|
||||||
readImage input.files[0], (src) ->
|
|
||||||
cropper = ($ '#profile-header-cropper')
|
|
||||||
preview = ($ '#profile-header-preview')
|
|
||||||
|
|
||||||
updateVars = (data, action) ->
|
|
||||||
($ '#profile_header_x').val Math.floor(data.x / data.scale)
|
|
||||||
($ '#profile_header_y').val Math.floor(data.y / data.scale)
|
|
||||||
($ '#profile_header_w').val Math.floor(data.w / data.scale)
|
|
||||||
($ '#profile_header_h').val Math.floor(data.h / data.scale)
|
|
||||||
|
|
||||||
cropper.on 'load', ->
|
|
||||||
if ({}.toString).call(src) == "[object URL]"
|
|
||||||
freeURL src
|
|
||||||
|
|
||||||
cropper.guillotine
|
|
||||||
width: 1500
|
|
||||||
height: 350
|
|
||||||
onChange: updateVars
|
|
||||||
|
|
||||||
updateVars cropper.guillotine('getData'), 'drag'
|
|
||||||
|
|
||||||
unless ($ '#profile-header-crop-controls')[0].dataset.bound?
|
|
||||||
($ '#cropper-header-zoom-out').click -> cropper.guillotine 'zoomOut'
|
|
||||||
($ '#cropper-header-zoom-in').click -> cropper.guillotine 'zoomIn'
|
|
||||||
($ '#profile-header-crop-controls')[0].dataset.bound = true
|
|
||||||
($ '#profile-header-crop-controls').slideDown()
|
|
||||||
|
|
||||||
cropper.attr 'src', src
|
|
||||||
|
|
||||||
# theming
|
|
||||||
|
|
||||||
previewStyle = null
|
|
||||||
|
|
||||||
$(document).on 'ready turbolinks:load', ->
|
|
||||||
if $('#update_theme').length > 0
|
|
||||||
previewStyle = document.createElement 'style'
|
|
||||||
document.body.appendChild previewStyle
|
|
||||||
|
|
||||||
previewTimeout = null
|
|
||||||
|
|
||||||
$('#update_theme .color').each ->
|
|
||||||
$this = $ this
|
|
||||||
this.value = '#' + getHexColorFromThemeValue(this.value)
|
|
||||||
|
|
||||||
$this.minicolors
|
|
||||||
control: 'hue'
|
|
||||||
defaultValue: this.value
|
|
||||||
letterCase: 'lowercase'
|
|
||||||
position: 'bottom left'
|
|
||||||
theme: 'bootstrap'
|
|
||||||
inline: false
|
|
||||||
change: ->
|
|
||||||
clearTimeout previewTimeout
|
|
||||||
previewTimeout = setTimeout(previewTheme, 1000)
|
|
||||||
|
|
||||||
$(document).on 'click', 'a.theme_preset', (event) ->
|
|
||||||
preset = [].concat themePresets[this.dataset.preset]
|
|
||||||
$('#update_theme .color').each ->
|
|
||||||
$(this).minicolors 'value', '#' + getHexColorFromThemeValue(preset.shift())
|
|
||||||
|
|
||||||
previewTheme = ->
|
|
||||||
payload = {}
|
|
||||||
|
|
||||||
$('#update_theme').find('.color').each ->
|
|
||||||
n = this.name.substr 6, this.name.length - 7
|
|
||||||
payload[n] = parseInt this.value.substr(1, 6), 16
|
|
||||||
|
|
||||||
generateTheme payload
|
|
||||||
|
|
||||||
null
|
|
||||||
|
|
||||||
generateTheme = (payload) ->
|
|
||||||
theme_attribute_map = {
|
|
||||||
'primary_color': 'primary',
|
|
||||||
'primary_text': 'primary-text',
|
|
||||||
'danger_color': 'danger',
|
|
||||||
'danger_text': 'danger-text',
|
|
||||||
'warning_color': 'warning',
|
|
||||||
'warning_text': 'warning-text',
|
|
||||||
'info_color': 'info',
|
|
||||||
'info_text': 'info-text',
|
|
||||||
'success_color': 'success',
|
|
||||||
'success_text': 'success-text',
|
|
||||||
'dark_color': 'dark',
|
|
||||||
'dark_text': 'dark-text',
|
|
||||||
'light_color': 'light',
|
|
||||||
'light_text': 'light-text',
|
|
||||||
'raised_background': 'raised-bg',
|
|
||||||
'raised_accent': 'raised-accent',
|
|
||||||
'background_color': 'background',
|
|
||||||
'body_text': 'body-text',
|
|
||||||
'input_color': 'input-bg',
|
|
||||||
'input_text': 'input-text',
|
|
||||||
'muted_text': 'muted-text'
|
|
||||||
}
|
|
||||||
|
|
||||||
body = ":root {\n"
|
|
||||||
|
|
||||||
(Object.keys(payload)).forEach (plKey) ->
|
|
||||||
if theme_attribute_map[plKey]
|
|
||||||
if theme_attribute_map[plKey].includes 'text'
|
|
||||||
hex = getHexColorFromThemeValue(payload[plKey])
|
|
||||||
body += "--#{theme_attribute_map[plKey]}: #{getDecimalTripletsFromHex(hex)};\n"
|
|
||||||
else
|
|
||||||
body += "--#{theme_attribute_map[plKey]}: ##{getHexColorFromThemeValue(payload[plKey])};\n"
|
|
||||||
|
|
||||||
body += "}"
|
|
||||||
|
|
||||||
previewStyle.innerHTML = body
|
|
||||||
|
|
||||||
getHexColorFromThemeValue = (themeValue) ->
|
|
||||||
return ('000000' + parseInt(themeValue).toString(16)).substr(-6, 6)
|
|
||||||
|
|
||||||
getDecimalTripletsFromHex = (hex) ->
|
|
||||||
return hex.match(/.{1,2}/g).map((value) -> parseInt(value, 16)).join(', ')
|
|
||||||
|
|
||||||
themePresets = {
|
|
||||||
rs: [0x5E35B1, 0xFFFFFF, 0xFF0039, 0xFFFFFF, 0x3FB618, 0xFFFFFF, 0xFF7518, 0xFFFFFF, 0x9954BB, 0xFFFFFF, 0x222222, 0xEEEEEE, 0xF9F9F9, 0x151515, 0x5E35B1, 0xFFFFFF, 0x222222, 0xbbbbbb, 0xFFFFFF, 0x000000, 0x5E35B1],
|
|
||||||
dc: [0x141414, 0xeeeeee, 0x362222, 0xeeeeee, 0x1d2e1d, 0xeeeeee, 0x404040, 0xeeeeee, 0xb8b8b8, 0x3b3b3b, 0x303030, 0xEEEEEE, 0x202020, 0xeeeeee, 0x9c9a9a, 0x363636, 0xe6e6e6, 0xbbbbbb, 0x383838, 0xebebeb, 0x787676],
|
|
||||||
lc: [0xebebeb, 0x111111, 0xf76363, 0x111111, 0x8aff94, 0x111111, 0xffbd7f, 0x111111, 0x474747, 0xc4c4c4, 0xcfcfcf, 0x111111, 0xdfdfdf, 0x111111, 0x636565, 0xc9c9c9, 0x191919, 0x444444, 0xc7c7c7, 0x141414, 0x878989]
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).on 'submit', '#update_theme', (event) ->
|
|
||||||
$this = $ this
|
|
||||||
$this.find('.color').each ->
|
|
||||||
this.value = parseInt this.value.substr(1, 6), 16
|
|
||||||
true
|
|
|
@ -5,19 +5,15 @@ import '../legacy/jquery'
|
||||||
import {} from 'jquery-ujs'
|
import {} from 'jquery-ujs'
|
||||||
import 'popper.js'
|
import 'popper.js'
|
||||||
import 'bootstrap'
|
import 'bootstrap'
|
||||||
import 'jquery.guillotine'
|
|
||||||
import 'particleground/jquery.particleground.min'
|
import 'particleground/jquery.particleground.min'
|
||||||
import 'jquery.growl'
|
import 'jquery.growl'
|
||||||
import 'jquery-minicolors'
|
|
||||||
import 'sweetalert'
|
import 'sweetalert'
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
require('nprogress/nprogress.css')
|
require('nprogress/nprogress.css')
|
||||||
require('jquery.growl/stylesheets/jquery.growl.css')
|
require('jquery.growl/stylesheets/jquery.growl.css')
|
||||||
require('jquery.guillotine/css/jquery.guillotine.css')
|
|
||||||
require('sweetalert/dist/sweetalert.css')
|
require('sweetalert/dist/sweetalert.css')
|
||||||
require('jquery-minicolors/jquery.minicolors.css')
|
|
||||||
|
|
||||||
# this file is generated by Rails
|
# this file is generated by Rails
|
||||||
import I18n from '../legacy/i18n'
|
import I18n from '../legacy/i18n'
|
||||||
|
@ -26,7 +22,6 @@ import '../legacy/answerbox'
|
||||||
import '../legacy/memes'
|
import '../legacy/memes'
|
||||||
import '../legacy/notifications'
|
import '../legacy/notifications'
|
||||||
import '../legacy/pagination'
|
import '../legacy/pagination'
|
||||||
import '../legacy/settings'
|
|
||||||
import '../legacy/report'
|
import '../legacy/report'
|
||||||
import '../legacy/locale-box'
|
import '../legacy/locale-box'
|
||||||
import '../legacy/util'
|
import '../legacy/util'
|
||||||
|
|
62
app/javascript/retrospring/features/settings/crop.ts
Normal file
62
app/javascript/retrospring/features/settings/crop.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import 'croppr/dist/croppr.css';
|
||||||
|
import Croppr from 'croppr';
|
||||||
|
|
||||||
|
const readImage = (file, callback) => callback((window.URL || window.webkitURL).createObjectURL(file));
|
||||||
|
|
||||||
|
export function profilePictureChangeHandler(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
|
||||||
|
const cropControls = document.querySelector('#profile-picture-crop-controls');
|
||||||
|
cropControls.classList.toggle('d-none');
|
||||||
|
|
||||||
|
if (input.files && input.files[0]) {
|
||||||
|
readImage(input.files[0], (src) => {
|
||||||
|
const updateValues = (data) => {
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_picture_x').value = data.x;
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_picture_y').value = data.y;
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_picture_w').value = data.width;
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_picture_h').value = data.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cropper = document.querySelector<HTMLImageElement>('#profile-picture-cropper');
|
||||||
|
cropper.src = src;
|
||||||
|
|
||||||
|
new Croppr(cropper, {
|
||||||
|
aspectRatio: 1,
|
||||||
|
startSize: [100, 100, '%'],
|
||||||
|
onCropStart: updateValues,
|
||||||
|
onCropMove: updateValues,
|
||||||
|
onCropEnd: updateValues
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function profileHeaderChangeHandler(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
|
||||||
|
const cropControls = document.querySelector('#profile-header-crop-controls');
|
||||||
|
cropControls.classList.toggle('d-none');
|
||||||
|
|
||||||
|
if (input.files && input.files[0]) {
|
||||||
|
readImage(input.files[0], (src) => {
|
||||||
|
const updateValues = (data) => {
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_header_x').value = data.x;
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_header_y').value = data.y;
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_header_w').value = data.width;
|
||||||
|
document.querySelector<HTMLInputElement>('#profile_header_h').value = data.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cropper = document.querySelector<HTMLImageElement>('#profile-header-cropper');
|
||||||
|
cropper.src = src;
|
||||||
|
|
||||||
|
new Croppr(cropper, {
|
||||||
|
aspectRatio: 7/30,
|
||||||
|
startSize: [100, 100, '%'],
|
||||||
|
onCropStart: updateValues,
|
||||||
|
onCropMove: updateValues,
|
||||||
|
onCropEnd: updateValues
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,17 @@
|
||||||
import {createDeleteEvent, createSubmitEvent} from "retrospring/features/settings/mute";
|
import registerEvents from "utilities/registerEvents";
|
||||||
|
import { muteDocumentHandler } from "./mute";
|
||||||
|
import { profileHeaderChangeHandler, profilePictureChangeHandler } from "./crop";
|
||||||
|
import { themeDocumentHandler, themeSubmitHandler } from "./theme";
|
||||||
|
import { userSubmitHandler } from "./password";
|
||||||
|
|
||||||
export default (): void => {
|
export default (): void => {
|
||||||
const submit: HTMLButtonElement = document.getElementById('new-rule-submit') as HTMLButtonElement;
|
muteDocumentHandler();
|
||||||
if (!submit || submit.classList.contains('js-initialized')) return;
|
themeDocumentHandler();
|
||||||
|
|
||||||
const rulesList = document.querySelector<HTMLDivElement>('.js-rules-list');
|
registerEvents([
|
||||||
rulesList.querySelectorAll<HTMLDivElement>('.form-group:not(.js-initalized)').forEach(entry => {
|
{ type: 'submit', target: document.querySelector('#update_theme'), handler: themeSubmitHandler },
|
||||||
const button = entry.querySelector('button')
|
{ type: 'submit', target: document.querySelector('#edit_user'), handler: userSubmitHandler },
|
||||||
button.onclick = createDeleteEvent(entry, button)
|
{ type: 'change', target: document.querySelector('#user_profile_picture[type=file]'), handler: profilePictureChangeHandler },
|
||||||
});
|
{ type: 'change', target: document.querySelector('#user_profile_header[type=file]'), handler: profileHeaderChangeHandler }
|
||||||
const textEntry: HTMLButtonElement = document.getElementById('new-rule-text') as HTMLButtonElement;
|
]);
|
||||||
const template: HTMLTemplateElement = document.getElementById('rule-template') as HTMLTemplateElement;
|
|
||||||
|
|
||||||
submit.form.onsubmit = createSubmitEvent(submit, rulesList, textEntry, template)
|
|
||||||
|
|
||||||
submit.classList.add('js-initialized')
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import Rails from '@rails/ujs';
|
import Rails from '@rails/ujs';
|
||||||
|
|
||||||
export function createSubmitEvent(
|
function createSubmitEvent(
|
||||||
submit: HTMLButtonElement,
|
submit: HTMLButtonElement,
|
||||||
rulesList: HTMLDivElement,
|
rulesList: HTMLDivElement,
|
||||||
textEntry: HTMLButtonElement,
|
textEntry: HTMLButtonElement,
|
||||||
|
@ -36,7 +36,7 @@ export function createSubmitEvent(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDeleteEvent(
|
function createDeleteEvent(
|
||||||
entry: HTMLDivElement,
|
entry: HTMLDivElement,
|
||||||
deleteButton: HTMLButtonElement
|
deleteButton: HTMLButtonElement
|
||||||
): () => void {
|
): () => void {
|
||||||
|
@ -56,4 +56,21 @@ export function createDeleteEvent(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function muteDocumentHandler(): void {
|
||||||
|
const submit: HTMLButtonElement = document.getElementById('new-rule-submit') as HTMLButtonElement;
|
||||||
|
if (!submit || submit.classList.contains('js-initialized')) return;
|
||||||
|
|
||||||
|
const rulesList = document.querySelector<HTMLDivElement>('.js-rules-list');
|
||||||
|
rulesList.querySelectorAll<HTMLDivElement>('.form-group:not(.js-initalized)').forEach(entry => {
|
||||||
|
const button = entry.querySelector('button')
|
||||||
|
button.onclick = createDeleteEvent(entry, button)
|
||||||
|
});
|
||||||
|
const textEntry: HTMLButtonElement = document.getElementById('new-rule-text') as HTMLButtonElement;
|
||||||
|
const template: HTMLTemplateElement = document.getElementById('rule-template') as HTMLTemplateElement;
|
||||||
|
|
||||||
|
submit.form.onsubmit = createSubmitEvent(submit, rulesList, textEntry, template)
|
||||||
|
|
||||||
|
submit.classList.add('js-initialized');
|
||||||
}
|
}
|
6
app/javascript/retrospring/features/settings/password.ts
Normal file
6
app/javascript/retrospring/features/settings/password.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export function userSubmitHandler(event: Event): void {
|
||||||
|
if (document.querySelector<HTMLInputElement>('#user_current_password').value.length === 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
document.querySelector<HTMLButtonElement>('[data-target=#modal-passwd]').click();
|
||||||
|
}
|
||||||
|
}
|
115
app/javascript/retrospring/features/settings/theme.ts
Normal file
115
app/javascript/retrospring/features/settings/theme.ts
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import "@melloware/coloris/dist/coloris.css";
|
||||||
|
import Coloris from "@melloware/coloris";
|
||||||
|
|
||||||
|
let previewStyle = null;
|
||||||
|
let previewTimeout = null;
|
||||||
|
|
||||||
|
const previewTheme = (): void => {
|
||||||
|
const payload = {};
|
||||||
|
|
||||||
|
Array.from(document.querySelectorAll('#update_theme .color')).forEach((color: HTMLInputElement) => {
|
||||||
|
const name = color.name.substring(6, color.name.length - 1);
|
||||||
|
payload[name] = parseInt(color.value.substr(1, 6), 16);
|
||||||
|
});
|
||||||
|
|
||||||
|
generateTheme(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateTheme = (payload: Record<string, string>): void => {
|
||||||
|
const themeAttributeMap = {
|
||||||
|
'primary_color': 'primary',
|
||||||
|
'primary_text': 'primary-text',
|
||||||
|
'danger_color': 'danger',
|
||||||
|
'danger_text': 'danger-text',
|
||||||
|
'warning_color': 'warning',
|
||||||
|
'warning_text': 'warning-text',
|
||||||
|
'info_color': 'info',
|
||||||
|
'info_text': 'info-text',
|
||||||
|
'success_color': 'success',
|
||||||
|
'success_text': 'success-text',
|
||||||
|
'dark_color': 'dark',
|
||||||
|
'dark_text': 'dark-text',
|
||||||
|
'light_color': 'light',
|
||||||
|
'light_text': 'light-text',
|
||||||
|
'raised_background': 'raised-bg',
|
||||||
|
'raised_accent': 'raised-accent',
|
||||||
|
'background_color': 'background',
|
||||||
|
'body_text': 'body-text',
|
||||||
|
'input_color': 'input-bg',
|
||||||
|
'input_text': 'input-text',
|
||||||
|
'muted_text': 'muted-text'
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = ":root {\n";
|
||||||
|
|
||||||
|
(Object.keys(payload)).forEach((payloadKey) => {
|
||||||
|
if (themeAttributeMap[payloadKey]) {
|
||||||
|
if (themeAttributeMap[payloadKey].includes('text')) {
|
||||||
|
const hex = getHexColorFromThemeValue(payload[payloadKey]);
|
||||||
|
body += `--${themeAttributeMap[payloadKey]}: ${getDecimalTripletsFromHex(hex)};\n`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
body += `--${themeAttributeMap[payloadKey]}: #${getHexColorFromThemeValue(payload[payloadKey])};\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
body += "}";
|
||||||
|
|
||||||
|
previewStyle.innerHTML = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHexColorFromThemeValue = (themeValue: string): string => {
|
||||||
|
return ('000000' + parseInt(themeValue).toString(16)).substr(-6, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDecimalTripletsFromHex = (hex: string): string => {
|
||||||
|
return hex.match(/.{1,2}/g).map((value) => parseInt(value, 16)).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function themeDocumentHandler(): void {
|
||||||
|
if (!document.querySelector('#update_theme')) return;
|
||||||
|
if (document.querySelector('#clr-picker')) return;
|
||||||
|
|
||||||
|
previewStyle = document.createElement('style');
|
||||||
|
previewStyle.setAttribute('data-preview-style', '');
|
||||||
|
document.body.appendChild(previewStyle);
|
||||||
|
|
||||||
|
Coloris.init();
|
||||||
|
|
||||||
|
Array.from(document.querySelectorAll('#update_theme .color')).forEach((color: HTMLInputElement) => {
|
||||||
|
// If there already is a hex-color in the input, skip
|
||||||
|
if (color.value.startsWith('#')) return;
|
||||||
|
|
||||||
|
let colorValue;
|
||||||
|
|
||||||
|
// match for value="[digits]" to ALWAYS get a color value
|
||||||
|
// TODO: Fix this later with rethinking the entire lifecycle, or dropping Turbolinks
|
||||||
|
colorValue = color.outerHTML.match(/value="(\d+)"/)[1];
|
||||||
|
|
||||||
|
// matching failed, or no result was found, we just fallback to the input value
|
||||||
|
if (colorValue === null) {
|
||||||
|
colorValue = color.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
color.value = `#${getHexColorFromThemeValue(colorValue)}`;
|
||||||
|
|
||||||
|
Coloris({
|
||||||
|
el: '.color',
|
||||||
|
wrap: false,
|
||||||
|
formatToggle: false,
|
||||||
|
alpha: false
|
||||||
|
});
|
||||||
|
|
||||||
|
color.addEventListener('input', () => {
|
||||||
|
clearTimeout(previewTimeout);
|
||||||
|
previewTimeout = setTimeout(previewTheme, 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function themeSubmitHandler(): void {
|
||||||
|
Array.from(document.querySelectorAll('#update_theme .color')).forEach((color: HTMLInputElement) => {
|
||||||
|
color.value = String(parseInt(color.value.substr(1, 6), 16));
|
||||||
|
});
|
||||||
|
}
|
|
@ -8,16 +8,10 @@
|
||||||
.media-body
|
.media-body
|
||||||
= f.file_field :profile_picture, label: t('views.settings.profile.avatar'), accept: APP_CONFIG[:accepted_image_formats].join(',')
|
= f.file_field :profile_picture, label: t('views.settings.profile.avatar'), accept: APP_CONFIG[:accepted_image_formats].join(',')
|
||||||
|
|
||||||
.row#profile-picture-crop-controls{ style: 'display: none;' }
|
.row.d-none#profile-picture-crop-controls
|
||||||
.col-sm-10.col-md-8
|
.col-sm-10.col-md-8
|
||||||
%strong= t('views.settings.profile.avatar_adjust')
|
%strong= t('views.settings.profile.avatar_adjust')
|
||||||
%img#profile-picture-cropper{ src: current_user.profile_picture.url(:medium) }
|
%img#profile-picture-cropper{ src: current_user.profile_picture.url(:medium) }
|
||||||
.col-sm-2.col-md-4
|
|
||||||
.btn-group
|
|
||||||
%button.btn.btn-inverse#cropper-zoom-out{ type: :button }
|
|
||||||
%i.fa.fa-search-minus
|
|
||||||
%button.btn.btn-inverse#cropper-zoom-in{ type: :button }
|
|
||||||
%i.fa.fa-search-plus
|
|
||||||
|
|
||||||
.row.mb-2#profile-header-media
|
.row.mb-2#profile-header-media
|
||||||
.col
|
.col
|
||||||
|
@ -25,16 +19,10 @@
|
||||||
.col-xs-12.mt-3.mt-sm-0.pl-3.pr-3
|
.col-xs-12.mt-3.mt-sm-0.pl-3.pr-3
|
||||||
= f.file_field :profile_header, label: t('views.settings.profile.header'), accept: APP_CONFIG[:accepted_image_formats].join(',')
|
= f.file_field :profile_header, label: t('views.settings.profile.header'), accept: APP_CONFIG[:accepted_image_formats].join(',')
|
||||||
|
|
||||||
.row#profile-header-crop-controls{ style: 'display: none;' }
|
.row.d-none#profile-header-crop-controls
|
||||||
.col-sm-10.col-md-8
|
.col-sm-10.col-md-8
|
||||||
%strong= t('views.settings.profile.header_adjust')
|
%strong= t('views.settings.profile.header_adjust')
|
||||||
%img#profile-header-cropper{ src: current_user.profile_header.url(:web) }
|
%img#profile-header-cropper{ src: current_user.profile_header.url(:web) }
|
||||||
.col-sm-2.col-md-4
|
|
||||||
.btn-group
|
|
||||||
%button.btn.btn-inverse#cropper-header-zoom-out{ type: :button }
|
|
||||||
%i.fa.fa-search-minus
|
|
||||||
%button.btn.btn-inverse#cropper-header-zoom-in{ type: :button }
|
|
||||||
%i.fa.fa-search-plus
|
|
||||||
|
|
||||||
= f.check_box :show_foreign_themes, label: 'Render other user themes when visiting their profile'
|
= f.check_box :show_foreign_themes, label: 'Render other user themes when visiting their profile'
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,16 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
|
"@melloware/coloris": "^0.10.0",
|
||||||
"@rails/ujs": "^6.1.0",
|
"@rails/ujs": "^6.1.0",
|
||||||
"bootstrap": "^4.5.3",
|
"bootstrap": "^4.5.3",
|
||||||
"cheet.js": "^0.3.3",
|
"cheet.js": "^0.3.3",
|
||||||
"core-js": "^3.8.1",
|
"core-js": "^3.8.1",
|
||||||
|
"croppr": "^2.3.1",
|
||||||
"i18n-js": "^3.8.0",
|
"i18n-js": "^3.8.0",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"jquery-minicolors": "^2.1.10",
|
|
||||||
"jquery-ujs": "^1.2.2",
|
"jquery-ujs": "^1.2.2",
|
||||||
"jquery.growl": "^1.3.5",
|
"jquery.growl": "^1.3.5",
|
||||||
"jquery.guillotine": "^1.4.3",
|
|
||||||
"jquery.turbolinks": "^2.1.0",
|
"jquery.turbolinks": "^2.1.0",
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es6", "dom"],
|
||||||
"module": "es6",
|
"module": "es6",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
|
26
yarn.lock
26
yarn.lock
|
@ -869,6 +869,11 @@
|
||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
|
"@melloware/coloris@^0.10.0":
|
||||||
|
version "0.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@melloware/coloris/-/coloris-0.10.0.tgz#13483d2b35a78d52c8b802c444c85f3c0c1f6f05"
|
||||||
|
integrity sha512-AsHgSIZJKjdELApdcX05WA+Nul4zyeN72YyJtNMuVegPHszafkJKdaj+vmWYZBE4f8naKqwaNY9BuU6v1o0ivQ==
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.3":
|
"@nodelib/fs.scandir@2.1.3":
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
|
||||||
|
@ -2370,6 +2375,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
|
croppr@^2.3.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/croppr/-/croppr-2.3.1.tgz#d279e006531240fa8ebf2681e4127ae7c42b074e"
|
||||||
|
integrity sha512-0rvTl4VmR3I4AahjJPF1u9IlT7ckvjIcgaLnUjYaY+UZsP9oxlVYZWYDuqM3SVCQiaI7DXMjR7wOEYT+mydOFg==
|
||||||
|
|
||||||
cross-spawn@^3.0.0:
|
cross-spawn@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
|
||||||
|
@ -4485,13 +4495,6 @@ jest-worker@^26.5.0:
|
||||||
merge-stream "^2.0.0"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^7.0.0"
|
supports-color "^7.0.0"
|
||||||
|
|
||||||
jquery-minicolors@^2.1.10:
|
|
||||||
version "2.1.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/jquery-minicolors/-/jquery-minicolors-2.1.10.tgz#b29eea541d9a32b4e26923168fb2c16271867379"
|
|
||||||
integrity sha1-sp7qVB2aMrTiaSMWj7LBYnGGc3k=
|
|
||||||
dependencies:
|
|
||||||
jquery ">= 1.7.x"
|
|
||||||
|
|
||||||
jquery-ujs@^1.2.2:
|
jquery-ujs@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.2.tgz#6a8ef1020e6b6dda385b90a4bddc128c21c56397"
|
resolved "https://registry.yarnpkg.com/jquery-ujs/-/jquery-ujs-1.2.2.tgz#6a8ef1020e6b6dda385b90a4bddc128c21c56397"
|
||||||
|
@ -4504,19 +4507,12 @@ jquery.growl@^1.3.5:
|
||||||
resolved "https://registry.yarnpkg.com/jquery.growl/-/jquery.growl-1.3.5.tgz#fa1c4d758e0d7686551e15896b1aaac2bb1af7f1"
|
resolved "https://registry.yarnpkg.com/jquery.growl/-/jquery.growl-1.3.5.tgz#fa1c4d758e0d7686551e15896b1aaac2bb1af7f1"
|
||||||
integrity sha1-+hxNdY4NdoZVHhWJaxqqwrsa9/E=
|
integrity sha1-+hxNdY4NdoZVHhWJaxqqwrsa9/E=
|
||||||
|
|
||||||
jquery.guillotine@^1.4.3:
|
|
||||||
version "1.4.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/jquery.guillotine/-/jquery.guillotine-1.4.3.tgz#c2463c367feaa8b229e02e2fbbae2e29cfd07225"
|
|
||||||
integrity sha1-wkY8Nn/qqLIp4C4vu64uKc/QciU=
|
|
||||||
dependencies:
|
|
||||||
jquery ">= 1.8.0"
|
|
||||||
|
|
||||||
jquery.turbolinks@^2.1.0:
|
jquery.turbolinks@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/jquery.turbolinks/-/jquery.turbolinks-2.1.0.tgz#36036bb997c48d53bceb345521fbdf7da887a62c"
|
resolved "https://registry.yarnpkg.com/jquery.turbolinks/-/jquery.turbolinks-2.1.0.tgz#36036bb997c48d53bceb345521fbdf7da887a62c"
|
||||||
integrity sha1-NgNruZfEjVO86zRVIfvffaiHpiw=
|
integrity sha1-NgNruZfEjVO86zRVIfvffaiHpiw=
|
||||||
|
|
||||||
"jquery@>= 1.7.x", "jquery@>= 1.8.0", jquery@>=1.8.0, jquery@^3.0, jquery@^3.5.1:
|
jquery@>=1.8.0, jquery@^3.0, jquery@^3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5"
|
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5"
|
||||||
integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==
|
integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==
|
||||||
|
|
Loading…
Reference in a new issue