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",
"feather-reactjs": "^2.0.13",
"jquery": "^3.5.1",
"jsdoc": "^3.6.5",
"less-vars-to-js": "^1.3.0",
"lint-staged": "^10.0.7",
"localforage": "^1.7.4",

View File

@ -3,6 +3,7 @@ import { format } from 'timeago.js';
import { cloneDeep } from 'lodash';
import store from 'store';
import { i18n, app_config } from 'config';
import * as errorHandlers from 'core/libs/errorhandler'
const { pathToRegexp } = require('path-to-regexp');
@ -24,6 +25,53 @@ export const app_info = {
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
*
@ -36,28 +84,96 @@ export function objectLast(array, n) {
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
*
* @param array {array}
* @param value {string}
* @return object
* @param id {string}
* @return array
*/
export function arrayRemoveByID(arr, value) {
export function arrayRemoveByID(arr, id) {
return arr.filter(function(ele) {
return ele.id != value;
return ele.id != id;
});
}
/**
* Remove an element by key from an array
*
* @param array {array}
* @param value {string}
* @return object
* @param key {string}
* @return array
*/
export function arrayRemoveByKEY(arr, value) {
export function arrayRemoveByKEY(arr, key) {
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'
*
* @param e {int} Numeric boolean reference
* @return {bool} Boolean value
* @return bool
*/
export function booleanFix(e) {
if (e == 1) return true;
@ -109,6 +225,9 @@ export const time = {
relativeToNow: (a, b) => {
return moment(a, b || 'DDMMYYYY').fromNow();
},
now: () => {
return new Date().toLocaleString();
}
};
export function pathMatchRegexp(regexp, pathname) {

View File

@ -4,11 +4,28 @@ import verbosity from 'core/libs/verbosity'
// STRINGS
export const OVERLAY_BADPOSITION = `Invalid overlay position! Was expected "primary" or "secondary"`
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
export const onError = {
internal_proccess: (...rest) => {
verbosity.error(...rest)
notify.warn(INTERNAL_PROCESS_FAILED, ...rest)
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'
export const notify = {
@ -79,4 +79,14 @@ export const notify = {
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 verbosity from 'core/libs/verbosity'
import * as errorHandlers from 'core/libs/errorhandler'
import { negate } from 'lodash';
import * as core from 'core'
const { appTheme_desiredContrast, appTheme_container } = app_config
export const theme = {
get: (key) => {
if(!localStorage.getItem(appTheme_container)) return false
const raw = store.get(appTheme_container)
if(!raw) return false
let container = []
if (raw) {
raw.forEach((e)=>{
container[e.key] = e.value
})
return container
try {
raw.forEach((e)=>{container[e.key] = e.value})
} catch (error) {
return errorHandlers.onError.invalid_data(error, "ThemeScheme")
}
return null
return container
},
set: (data) => {
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 { session } from 'core/cores';
import verbosity from 'core/libs/verbosity'
import { theme } from 'core/libs/style'
export default {
namespace: 'app',
@ -27,7 +28,7 @@ export default {
feedOutdated: false,
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: [],
locationQuery: {},
@ -114,24 +115,24 @@ export default {
}
});
},
*updateTheme({payload}, {call, put, select}){
*updateTheme({payload}, {put, select}){
if (!payload) return false;
let tmp = []
let container = yield select(state => state.app.app_theme);
let container_2 = []
const keys = Object.keys(payload)
const values = Object.values(payload)
const lenght = keys.length
for (let i = 0; i < lenght; i++) {
let obj = {}
obj.key = keys[i]
obj.value = values[i]
tmp[i] = obj
const containerlength = Object.entries(container).length
if (container && containerlength > 1) {
container.forEach(e =>{
let tmp = {key: e.key}
e.key === payload.key? (tmp.value = payload.value) : (tmp.value = e.value)
container_2.push(tmp)
})
}else{
container_2 = [payload]
}
return yield put({ type: 'handleUpdateTheme', payload: tmp });
return container_2? yield put({ type: 'handleUpdateTheme', payload: container_2 }) : null
},
},
reducers: {

View File

@ -4,32 +4,11 @@ import * as antd from 'antd'
import themeSettings from 'globals/theme_settings'
import {connect} from 'umi'
import styles from './index.less'
import json_prune from 'core/libs/json_prune'
import { SketchPicker } from 'react-color';
import { theme, getOptimalOpacityFromIMG, get_style_rule_value } from 'core/libs/style'
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();
}
import { urlToBase64, imageToBase64, arrayToObject } from 'core'
import exportDataAsFile from 'core/libs/interface/export_data'
class BackgroundColor extends React.Component{
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 }))
class BackgroundImage extends React.Component{
state = {
@ -67,7 +65,7 @@ class BackgroundImage extends React.Component{
}
if (info.file.status === 'done') {
this.setState({ processing: true })
getBase64(info.file.originFileObj, fileURL => {
imageToBase64(info.file.originFileObj, fileURL => {
this.setState({ fileURL: fileURL })
this.proccessBackground(fileURL)
})
@ -75,10 +73,8 @@ class BackgroundImage extends React.Component{
}
handleCustomURL(url){
this.setState({ processing: true })
this.setState({ fileURL: url })
toDataUrl(url, fileURL => {
this.setState({ processing: true, fileURL: url })
urlToBase64(url, fileURL => {
this.proccessBackground(fileURL)
})
}
@ -90,43 +86,19 @@ class BackgroundImage extends React.Component{
this.setState({ model: payload, processing: false })
this.props.dispatch({
type: 'app/updateTheme',
payload: { backgroundImage: payload }
payload: {
key: 'backgroundImage',
value: payload
}
});
}
handleErase(){
this.handleUpdate({})
this.handleUpdate({})
}
handleExport(){
const string = JSON.stringify(this.state.model)
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(),
});
exportDataAsFile({data: JSON.stringify(this.state.model), type: 'text/json'})
}
proccessBackground(data){
@ -149,9 +121,10 @@ class BackgroundImage extends React.Component{
}
componentDidMount(){
const storaged = theme.get()["backgroundImage"]
const storaged = theme.get()
if(storaged){
this.setState({ model: storaged })
this.setState({ model: storaged["backgroundImage"] })
}
let textColor = this.rgbToScheme(get_style_rule_value('#root', 'color'))
@ -200,30 +173,23 @@ class BackgroundImage extends React.Component{
checkedChildren="Enabled"
unCheckedChildren="Disabled"
loading={this.state.processing}
onChange={(e) => {
promiseState(prevState => ({ model: { ...prevState.model, active: e }})).then(() => this.handleUpdate())
}}
onChange={(e) => {promiseState(prevState => ({ model: { ...prevState.model, active: e }})).then(() => this.handleUpdate())}}
checked={this.state.model.active}
/>
</div>
<div>
<h4><Icons.Layers />Opacity</h4>
<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} />
<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} />
</div>
<div>
<h4><Icons.Code />Export Code</h4>
<antd.Button disabled={!this.state.model.src} size="small" onClick={() => this.handleExport()}> Export </antd.Button>
</div>
<div>
<h4><Icons.Copy />Import Code</h4>
<antd.Button size="small" onClick={() => null}> Import </antd.Button>
</div>
<div>
<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">
@ -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 = {
helper_visible: false,
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 = {
@ -303,18 +249,20 @@ export default class ThemeSettings extends React.PureComponent{
},
close: () => {
this.setState({ helper_visible: false, helper_fragment: null })
},
backgroundImage: () => {
this.helper.open(<BackgroundImage />)
},
backgroundColor: () => {
this.helper.open(<BackgroundColor />)
}
}
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(
<div>
<h2><Icons.Layers/> Theme</h2>
@ -322,16 +270,18 @@ export default class ThemeSettings extends React.PureComponent{
itemLayout="horizontal"
dataSource={themeSettings}
renderItem={item => (
<antd.Card size="small" bodyStyle={{ width: '100%' }} style={{ display: "flex", flexDirection: "row", margin: 'auto' }} hoverable onClick={settingClick[item.id]}>
<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>
<div style={{ margin: '10px 0 10px 0' }} >
<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>
</antd.Card>
</div>
)}
/>
<antd.Drawer
placement="right"
width="600px"
width="700px"
closable={true}
onClose={this.helper.close}
visible={this.state.helper_visible}