This commit is contained in:
unknown 2020-07-31 16:08:38 +02:00
parent 660d4848cb
commit 3d9140d599
8 changed files with 277 additions and 138 deletions

View File

@ -47,6 +47,7 @@
"enquire-js": "^0.2.1", "enquire-js": "^0.2.1",
"feather-reactjs": "^2.0.13", "feather-reactjs": "^2.0.13",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"jsdoc": "^3.6.5",
"less-vars-to-js": "^1.3.0", "less-vars-to-js": "^1.3.0",
"lint-staged": "^10.0.7", "lint-staged": "^10.0.7",
"localforage": "^1.7.4", "localforage": "^1.7.4",

View File

@ -3,6 +3,7 @@ import { format } from 'timeago.js';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import store from 'store'; import store from 'store';
import { i18n, app_config } from 'config'; import { i18n, app_config } from 'config';
import * as errorHandlers from 'core/libs/errorhandler'
const { pathToRegexp } = require('path-to-regexp'); const { pathToRegexp } = require('path-to-regexp');
@ -24,6 +25,53 @@ export const app_info = {
logo_dark: app_config.DarkFullLogoPath, logo_dark: app_config.DarkFullLogoPath,
}; };
export function imageToBase64(img, callback){
const reader = new FileReader()
reader.addEventListener('load', () => callback(reader.result))
reader.readAsDataURL(img)
}
export function urlToBase64(url, callback){
let xhr = new XMLHttpRequest();
xhr.onload = function() {
let reader = new FileReader();
reader.onloadend = function() {
callback(reader.result);
}
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
xhr.responseType = 'blob';
xhr.send();
}
/**
* Generate a download with encoded uri
*
* @param {object} payload - Generation Data
*/
export function downloadEncodedURI(payload){
if(!payload) return false
let { data, type, charset, filename } = payload
/**
*
* @param {object} payload - Generation Data
*/
if (!data || !type) return false
try {
if (!filename) {
filename = `export_${time.now()}.${type.split("/")[1]}`
}
let tmp = document.createElement('a')
tmp.href = `data:${type};charset=${charset},${encodeURIComponent(data)}`
tmp.download=filename
tmp.click()
} catch (error) {
errorHandlers.onError.internal_proccess(error)
}
}
/** /**
* Return the last object from array * Return the last object from array
* *
@ -36,28 +84,96 @@ export function objectLast(array, n) {
return array.slice(Math.max(array.length - n, 0)); return array.slice(Math.max(array.length - n, 0));
} }
/**
* Object to array scheme RSA-YCORE-ARRAYPROTO.2
*
* @param object {object}
* @return array
*/
export function objectToArray(object) {
if(!object) return false
let tmp = []
const keys = Object.keys(object)
const values = Object.values(object)
const lenght = keys.length
for (let i = 0; i < lenght; i++) {
let obj = {}
obj.key = keys[i]
obj.value = values[i]
tmp[i] = obj
}
return tmp
}
/**
* Object to array scheme RSA-YCORE-ARRAYPROTO.2
*
* @param object {object}
* @return array
*/
export function arrayToObject(array) {
if(!array) return false
let tmp = []
array.forEach((e) => {
tmp[e.key] = e.value
})
return tmp
}
/**
* Remove an element by id from an object array
*
* @param object {object}
* @param id {string}
* @return array
*/
export function objectRemoveByID(object, id) {
let arr = objectToArray(object)
return arr.filter((e) => {
return e.id != id;
});
}
/**
* Remove an element by key from an object array
*
* @param object {object}
* @param key {string}
* @return array
*/
export function objectRemoveByKEY(object, key) {
let arr = objectToArray(object)
return arr.filter((e) => {
return e.key != key;
});
}
/** /**
* Remove an element by id from an array * Remove an element by id from an array
* *
* @param array {array} * @param array {array}
* @param value {string} * @param id {string}
* @return object * @return array
*/ */
export function arrayRemoveByID(arr, value) { export function arrayRemoveByID(arr, id) {
return arr.filter(function(ele) { return arr.filter(function(ele) {
return ele.id != value; return ele.id != id;
}); });
} }
/** /**
* Remove an element by key from an array * Remove an element by key from an array
* *
* @param array {array} * @param array {array}
* @param value {string} * @param key {string}
* @return object * @return array
*/ */
export function arrayRemoveByKEY(arr, value) { export function arrayRemoveByKEY(arr, key) {
return arr.filter(function(ele) { return arr.filter(function(ele) {
return ele.key != value; return ele.key != key;
}); });
} }
@ -65,7 +181,7 @@ export function arrayRemoveByKEY(arr, value) {
* Global fix for convert '1, 0' to string boolean 'true, false' * Global fix for convert '1, 0' to string boolean 'true, false'
* *
* @param e {int} Numeric boolean reference * @param e {int} Numeric boolean reference
* @return {bool} Boolean value * @return bool
*/ */
export function booleanFix(e) { export function booleanFix(e) {
if (e == 1) return true; if (e == 1) return true;
@ -109,6 +225,9 @@ export const time = {
relativeToNow: (a, b) => { relativeToNow: (a, b) => {
return moment(a, b || 'DDMMYYYY').fromNow(); return moment(a, b || 'DDMMYYYY').fromNow();
}, },
now: () => {
return new Date().toLocaleString();
}
}; };
export function pathMatchRegexp(regexp, pathname) { export function pathMatchRegexp(regexp, pathname) {

View File

@ -4,11 +4,28 @@ import verbosity from 'core/libs/verbosity'
// STRINGS // STRINGS
export const OVERLAY_BADPOSITION = `Invalid overlay position! Was expected "primary" or "secondary"` export const OVERLAY_BADPOSITION = `Invalid overlay position! Was expected "primary" or "secondary"`
export const INTERNAL_PROCESS_FAILED = `An internal error has occurred! ` export const INTERNAL_PROCESS_FAILED = `An internal error has occurred! `
export const INVALID_DATA = `A function has been executed with invalid data and has caused an error!`
// HANDLERS // HANDLERS
export const onError = { export const onError = {
internal_proccess: (...rest) => { internal_proccess: (...rest) => {
verbosity.error(...rest) verbosity.error(...rest)
notify.warn(INTERNAL_PROCESS_FAILED, ...rest) notify.warn(INTERNAL_PROCESS_FAILED, ...rest)
return false return false
},
invalid_data: (error, expecting) => {
verbosity.error(error)
notify.open({
message: 'Invalid Data',
description:
<div style={{ display: 'flex', flexDirection: 'column', margin: 'auto' }}>
<div style={{ margin: '10px 0' }}> {INVALID_DATA} </div>
<div style={{ margin: '10px 0', color: '#333' }}>
<h4>Expected: {expecting}</h4>
<h4 style={{ backgroundColor: 'rgba(221, 42, 42, 0.8)' }} >{`${error}`} </h4>
</div>
</div>,
})
return false
} }
} }

View File

@ -0,0 +1,40 @@
import * as React from 'react'
import * as antd from 'antd'
import * as Icons from 'components/Icons'
import { downloadEncodedURI } from 'core'
export interface exportData_props {
data: string;
type: string;
}
const exportCodeRender = (data) => {
if(data.length > 500){
return <div style={{ textAlign: 'center', width: '100%', padding: '30px 0 30px 0' }}>
<Icons.HardDrive style={{ fontSize: '45px', margin: '0' }} />
<h4>Hey, this file is too much large!</h4>
<span>So it couldn't be displayed.</span>
</div>
}
return <div>
{data}
</div>
}
const exportData_render = (props: exportData_props) => {
antd.Modal.confirm({
title: <div><Icons.Code /> Your export <antd.Tag> {`${props.type.split("/")[1]}`} </antd.Tag></div>,
icon: null,
onOk: () => downloadEncodedURI({data: props.data, type: props.type}),
okText: <><Icons.Download />Download as File</> ,
cancelText: "Done",
content: exportCodeRender(props.data),
});
}
exportData_render.defaultProps = {
data: '',
type: 'text/txt',
}
export default exportData_render

View File

@ -1,4 +1,4 @@
import { notification } from 'antd' import { notification, message } from 'antd'
import * as Icons from 'components/Icons' import * as Icons from 'components/Icons'
export const notify = { export const notify = {
@ -79,4 +79,14 @@ export const notify = {
placement: 'bottomLeft', placement: 'bottomLeft',
}) })
}, },
open: (props) => {
notification.open({
placement: props.placement? props.placement : 'bottomLeft',
duration: props.duration? props.placement : 15,
icon: props.icon? props.icon : <Icons.Triangle style={{ color: '#fa8c16' }} />,
message: props.message? props.message : '',
description: props.description? props.description : ''
})
},
} }

View File

@ -2,22 +2,23 @@ import store from 'store';
import { app_config } from 'config'; import { app_config } from 'config';
import verbosity from 'core/libs/verbosity' import verbosity from 'core/libs/verbosity'
import * as errorHandlers from 'core/libs/errorhandler' import * as errorHandlers from 'core/libs/errorhandler'
import { negate } from 'lodash'; import * as core from 'core'
const { appTheme_desiredContrast, appTheme_container } = app_config const { appTheme_desiredContrast, appTheme_container } = app_config
export const theme = { export const theme = {
get: (key) => { get: (key) => {
if(!localStorage.getItem(appTheme_container)) return false
const raw = store.get(appTheme_container) const raw = store.get(appTheme_container)
if(!raw) return false
let container = [] let container = []
if (raw) { try {
raw.forEach((e)=>{ raw.forEach((e)=>{container[e.key] = e.value})
container[e.key] = e.value } catch (error) {
}) return errorHandlers.onError.invalid_data(error, "ThemeScheme")
return container
} }
return null return container
}, },
set: (data) => { set: (data) => {
if (!data || data.length > 2) return false if (!data || data.length > 2) return false

View File

@ -6,6 +6,7 @@ import keys from 'config/app_keys';
import * as core from 'core'; import * as core from 'core';
import { session } from 'core/cores'; import { session } from 'core/cores';
import verbosity from 'core/libs/verbosity' import verbosity from 'core/libs/verbosity'
import { theme } from 'core/libs/style'
export default { export default {
namespace: 'app', namespace: 'app',
@ -27,7 +28,7 @@ export default {
feedOutdated: false, feedOutdated: false,
app_settings: store.get(app_config.app_settings_storage), app_settings: store.get(app_config.app_settings_storage),
app_theme: store.get(app_config.appTheme_container), app_theme: store.get(app_config.appTheme_container) || [],
notifications: [], notifications: [],
locationQuery: {}, locationQuery: {},
@ -114,24 +115,24 @@ export default {
} }
}); });
}, },
*updateTheme({payload}, {call, put, select}){ *updateTheme({payload}, {put, select}){
if (!payload) return false; if (!payload) return false;
let tmp = [] let container = yield select(state => state.app.app_theme);
let container_2 = []
const keys = Object.keys(payload) const containerlength = Object.entries(container).length
const values = Object.values(payload)
const lenght = keys.length
for (let i = 0; i < lenght; i++) { if (container && containerlength > 1) {
let obj = {} container.forEach(e =>{
obj.key = keys[i] let tmp = {key: e.key}
obj.value = values[i] e.key === payload.key? (tmp.value = payload.value) : (tmp.value = e.value)
container_2.push(tmp)
tmp[i] = obj })
}else{
container_2 = [payload]
} }
return yield put({ type: 'handleUpdateTheme', payload: tmp }); return container_2? yield put({ type: 'handleUpdateTheme', payload: container_2 }) : null
}, },
}, },
reducers: { reducers: {

View File

@ -4,32 +4,11 @@ import * as antd from 'antd'
import themeSettings from 'globals/theme_settings' import themeSettings from 'globals/theme_settings'
import {connect} from 'umi' import {connect} from 'umi'
import styles from './index.less' import styles from './index.less'
import json_prune from 'core/libs/json_prune'
import { SketchPicker } from 'react-color'; import { SketchPicker } from 'react-color';
import { theme, getOptimalOpacityFromIMG, get_style_rule_value } from 'core/libs/style' import { theme, getOptimalOpacityFromIMG, get_style_rule_value } from 'core/libs/style'
import { urlToBase64, imageToBase64, arrayToObject } from 'core'
import exportDataAsFile from 'core/libs/interface/export_data'
function getBase64(img, callback) {
const reader = new FileReader()
reader.addEventListener('load', () => callback(reader.result))
reader.readAsDataURL(img)
}
function toDataUrl(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var reader = new FileReader();
reader.onloadend = function() {
callback(reader.result);
}
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
class BackgroundColor extends React.Component{ class BackgroundColor extends React.Component{
state = { state = {
@ -52,6 +31,25 @@ class BackgroundColor extends React.Component{
} }
} }
@connect(({ app }) => ({ app }))
class DarkMode extends React.Component{
state = {
model: { active: false, autoTime: '' }
}
render(){
return <>
<div>
<h2><Icons.Moon /> Dark Mode</h2>
</div>
<div>
</div>
</>
}
}
@connect(({ app }) => ({ app })) @connect(({ app }) => ({ app }))
class BackgroundImage extends React.Component{ class BackgroundImage extends React.Component{
state = { state = {
@ -67,7 +65,7 @@ class BackgroundImage extends React.Component{
} }
if (info.file.status === 'done') { if (info.file.status === 'done') {
this.setState({ processing: true }) this.setState({ processing: true })
getBase64(info.file.originFileObj, fileURL => { imageToBase64(info.file.originFileObj, fileURL => {
this.setState({ fileURL: fileURL }) this.setState({ fileURL: fileURL })
this.proccessBackground(fileURL) this.proccessBackground(fileURL)
}) })
@ -75,10 +73,8 @@ class BackgroundImage extends React.Component{
} }
handleCustomURL(url){ handleCustomURL(url){
this.setState({ processing: true }) this.setState({ processing: true, fileURL: url })
this.setState({ fileURL: url }) urlToBase64(url, fileURL => {
toDataUrl(url, fileURL => {
this.proccessBackground(fileURL) this.proccessBackground(fileURL)
}) })
} }
@ -90,43 +86,19 @@ class BackgroundImage extends React.Component{
this.setState({ model: payload, processing: false }) this.setState({ model: payload, processing: false })
this.props.dispatch({ this.props.dispatch({
type: 'app/updateTheme', type: 'app/updateTheme',
payload: { backgroundImage: payload } payload: {
key: 'backgroundImage',
value: payload
}
}); });
} }
handleErase(){ handleErase(){
this.handleUpdate({}) this.handleUpdate({})
} }
handleExport(){ handleExport(){
const string = JSON.stringify(this.state.model) exportDataAsFile({data: JSON.stringify(this.state.model), type: 'text/json'})
const exportCodeRender = () => {
if(string.length > 500){
return <div style={{ textAlign: 'center', width: '100%', padding: '30px 0 30px 0' }}>
<Icons.HardDrive style={{ fontSize: '45px', margin: '0' }} />
<h4>Hey, this file is too much large!</h4>
<span>So it couldn't be displayed.</span>
</div>
}
return <div>
{string}
</div>
}
antd.Modal.confirm({
title: <div><Icons.Code /> Your export <antd.Tag> JSON </antd.Tag></div>,
icon: null,
onOk: () => {
let tmp = document.createElement('a')
tmp.href = `data:text/json;charset=utf-8,${encodeURIComponent(string)}`
tmp.download="export.json"
tmp.click()
},
okText: <><Icons.Download />Download as File</> ,
cancelText: "Done",
content: exportCodeRender(),
});
} }
proccessBackground(data){ proccessBackground(data){
@ -149,9 +121,10 @@ class BackgroundImage extends React.Component{
} }
componentDidMount(){ componentDidMount(){
const storaged = theme.get()["backgroundImage"] const storaged = theme.get()
if(storaged){ if(storaged){
this.setState({ model: storaged }) this.setState({ model: storaged["backgroundImage"] })
} }
let textColor = this.rgbToScheme(get_style_rule_value('#root', 'color')) let textColor = this.rgbToScheme(get_style_rule_value('#root', 'color'))
@ -200,30 +173,23 @@ class BackgroundImage extends React.Component{
checkedChildren="Enabled" checkedChildren="Enabled"
unCheckedChildren="Disabled" unCheckedChildren="Disabled"
loading={this.state.processing} loading={this.state.processing}
onChange={(e) => { onChange={(e) => {promiseState(prevState => ({ model: { ...prevState.model, active: e }})).then(() => this.handleUpdate())}}
promiseState(prevState => ({ model: { ...prevState.model, active: e }})).then(() => this.handleUpdate())
}}
checked={this.state.model.active} checked={this.state.model.active}
/> />
</div> </div>
<div> <div>
<h4><Icons.Layers />Opacity</h4> <h4><Icons.Layers />Opacity</h4>
<antd.Slider disabled={!this.state.model.src} onChange={(e) => { <antd.Slider disabled={!this.state.model.src} onChange={(e) => {this.setState(prevState => ({model: {...prevState.model, opacity: e/100}}))}} onAfterChange={() => this.handleUpdate()} value={this.state.model.opacity*100} />
this.setState(
prevState => ({
model: {
...prevState.model,
opacity: e/100
}
})
)
}} onAfterChange={() => this.handleUpdate()} value={this.state.model.opacity*100} />
</div> </div>
<div> <div>
<h4><Icons.Code />Export Code</h4> <h4><Icons.Code />Export Code</h4>
<antd.Button disabled={!this.state.model.src} size="small" onClick={() => this.handleExport()}> Export </antd.Button> <antd.Button disabled={!this.state.model.src} size="small" onClick={() => this.handleExport()}> Export </antd.Button>
</div> </div>
<div>
<h4><Icons.Copy />Import Code</h4>
<antd.Button size="small" onClick={() => null}> Import </antd.Button>
</div>
<div> <div>
<h4><Icons.Trash />Erase</h4> <h4><Icons.Trash />Erase</h4>
<antd.Popconfirm disabled={!this.state.model.src} placement="topLeft" title="Are you sure?" onConfirm={() => this.handleErase()} okText="Yes" cancelText="No"> <antd.Popconfirm disabled={!this.state.model.src} placement="topLeft" title="Are you sure?" onConfirm={() => this.handleErase()} okText="Yes" cancelText="No">
@ -270,31 +236,11 @@ class BackgroundImage extends React.Component{
} }
} }
export default class ThemeSettings extends React.PureComponent{ @connect(({ app }) => ({ app }))
export default class ThemeSettings extends React.Component{
state = { state = {
helper_visible: false, helper_visible: false,
helper_fragment: null, helper_fragment: null,
style: theme.get(),
}
handleRemove(key){
try {
const storaged = JSON.parse(app.app_theme.getString())
let mix = {};
storaged.forEach((e)=>{
return e.key !== key? mix[e.key] = e.value : null
})
console.log(mix)
this.encode(mix, (res)=> {
app.app_theme.set(res)
this.decodeData()
})
} catch (error) {
console.log(error)
return false
}
} }
helper = { helper = {
@ -303,18 +249,20 @@ export default class ThemeSettings extends React.PureComponent{
}, },
close: () => { close: () => {
this.setState({ helper_visible: false, helper_fragment: null }) this.setState({ helper_visible: false, helper_fragment: null })
},
backgroundImage: () => {
this.helper.open(<BackgroundImage />)
},
backgroundColor: () => {
this.helper.open(<BackgroundColor />)
} }
} }
render(){ render(){
const settingClick = { backgroundImage: () => this.helper.backgroundImage(), backgroundColor: () => this.helper.backgroundColor() } const settingClick = {
backgroundImage: () => this.helper.open(<BackgroundImage />),
overlay: () => this.helper.open(<BackgroundColor />) ,
darkmode: () => this.helper.open(<DarkMode />)
}
const isActive = (key) => {
return key? key.active : false
}
return( return(
<div> <div>
<h2><Icons.Layers/> Theme</h2> <h2><Icons.Layers/> Theme</h2>
@ -322,16 +270,18 @@ export default class ThemeSettings extends React.PureComponent{
itemLayout="horizontal" itemLayout="horizontal"
dataSource={themeSettings} dataSource={themeSettings}
renderItem={item => ( renderItem={item => (
<antd.Card size="small" bodyStyle={{ width: '100%' }} style={{ display: "flex", flexDirection: "row", margin: 'auto' }} hoverable onClick={settingClick[item.id]}> <div style={{ margin: '10px 0 10px 0' }} >
<h3>{item.icon}{item.title} <div style={{ float: "right" }}><antd.Tag color={this.state.style[item.id]? "green" : "default"} > {this.state.style[item.id]? "Enabled" : "Disabled"} </antd.Tag></div></h3> <antd.Card size="small" bodyStyle={{ width: '100%' }} style={{ display: "flex", flexDirection: "row", margin: 'auto', borderRadius: '12px' }} hoverable onClick={settingClick[item.id]}>
<h3>{item.icon}{item.title} <div style={{ float: "right" }}><antd.Tag color={isActive(arrayToObject(this.props.app.app_theme)[item.id])? "green" : "default"} > {isActive(arrayToObject(this.props.app.app_theme)[item.id])? "Enabled" : "Disabled"} </antd.Tag></div></h3>
<p>{item.description}</p> <p>{item.description}</p>
</antd.Card> </antd.Card>
</div>
)} )}
/> />
<antd.Drawer <antd.Drawer
placement="right" placement="right"
width="600px" width="700px"
closable={true} closable={true}
onClose={this.helper.close} onClose={this.helper.close}
visible={this.state.helper_visible} visible={this.state.helper_visible}