2023-08-17 14:42:47 +08:00
|
|
|
'use strict';
|
|
|
|
|
2023-08-17 16:41:50 +08:00
|
|
|
import React, {Component} from "react";
|
2023-08-17 14:42:47 +08:00
|
|
|
import jsonLogic from 'json-logic-js';
|
2023-08-23 16:58:02 +08:00
|
|
|
import {useState, useEffect} from 'react';
|
2023-08-23 12:58:13 +08:00
|
|
|
import {useClickAway} from "@uidotdev/usehooks";
|
2023-08-17 14:42:47 +08:00
|
|
|
|
|
|
|
// https://stackoverflow.com/a/70511311
|
|
|
|
const trueTypeOf = (obj) => Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
|
|
|
|
|
2023-08-22 12:41:32 +08:00
|
|
|
|
2023-08-17 16:41:50 +08:00
|
|
|
class Radio extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
// Initialize the state object with the initial values from the props
|
|
|
|
this.state = {
|
2023-08-18 12:34:30 +08:00
|
|
|
variant: props.outvar in props.data ? props.data[props.outvar] : props.variants[0],
|
2023-08-17 16:41:50 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Bind the event handler to this
|
|
|
|
this.handleClick = this.handleClick.bind(this);
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.construct(this.props.outvar, this.state.variant);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.target.unmount(this.props.outvar);
|
2023-08-17 16:41:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
handleClick(variant) {
|
|
|
|
// Update the state object with the new value for outvar
|
|
|
|
this.setState({
|
|
|
|
...this.state,
|
|
|
|
variant: variant
|
|
|
|
});
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.update(this.props.outvar, variant);
|
2023-08-17 16:41:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2023-08-18 15:51:00 +08:00
|
|
|
let key = this.props.id + this.props.outvar;
|
2023-08-17 16:41:50 +08:00
|
|
|
return (
|
2023-08-18 15:51:00 +08:00
|
|
|
<div className="shop-radio" key={this.props.id}>
|
2023-09-06 14:22:26 +08:00
|
|
|
<div>
|
|
|
|
{this.props.icon ? <img src={`/images${this.props.icon}`} className="options-icon" /> : null}
|
|
|
|
{this.props.title}
|
|
|
|
</div>
|
2023-08-17 16:41:50 +08:00
|
|
|
{this.props.variants.map((variant, _) => (
|
|
|
|
<div className="form-check" key={key + variant}>
|
|
|
|
<input
|
|
|
|
className="form-check-input"
|
|
|
|
type="radio"
|
|
|
|
name={key}
|
|
|
|
id={key + variant}
|
|
|
|
checked={this.state.variant === variant}
|
|
|
|
onClick={() => this.handleClick(variant)}
|
|
|
|
onChange={() => this.handleClick(variant)}
|
|
|
|
/>
|
|
|
|
<label className="form-check-label" htmlFor={key + variant}>
|
|
|
|
{variant}
|
|
|
|
</label>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
);
|
2023-08-17 14:42:47 +08:00
|
|
|
}
|
2023-08-17 16:41:50 +08:00
|
|
|
}
|
2023-08-17 14:42:47 +08:00
|
|
|
|
2023-09-06 14:22:26 +08:00
|
|
|
function RadioWrapper(target, id, data, {title, variants, outvar, icon}) {
|
|
|
|
return <Radio target={target} title={title} variants={variants} outvar={outvar} icon={icon} key={id} id={id} data={data}/>;
|
2023-08-18 15:51:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
class Switch extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
// Initialize the state object with the initial values from the props
|
|
|
|
this.state = {
|
2023-09-06 14:22:26 +08:00
|
|
|
checked: props.outvar in props.data ? !!(props.data[props.outvar]) : !!(props.fallback)
|
2023-08-18 15:51:00 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Bind the event handler to this
|
|
|
|
this.handleClick = this.handleClick.bind(this);
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.construct(this.props.outvar, this.state.checked);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.target.unmount(this.props.outvar);
|
2023-08-18 15:51:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
handleClick() {
|
|
|
|
// Update the state object with the new value for outvar
|
|
|
|
let new_checked = !this.state.checked;
|
|
|
|
this.setState({
|
|
|
|
checked: new_checked
|
|
|
|
});
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.update(this.props.outvar, new_checked);
|
2023-08-18 15:51:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let key = this.props.id + this.props.outvar;
|
|
|
|
return (
|
|
|
|
<div className="shop-switch" key={this.props.id}>
|
|
|
|
<div className="form-check form-switch" key={key}>
|
|
|
|
<input
|
|
|
|
className="form-check-input"
|
|
|
|
type="checkbox"
|
|
|
|
role="switch"
|
|
|
|
id={key}
|
|
|
|
checked={this.state.checked}
|
|
|
|
onClick={this.handleClick}
|
|
|
|
onChange={this.handleClick}
|
|
|
|
/>
|
|
|
|
<label className="form-check-label" htmlFor={key}>
|
2023-09-06 14:22:26 +08:00
|
|
|
{this.props.icon ? <img src={`/images${this.props.icon}`} className="options-icon" /> : null}
|
2023-08-18 15:51:00 +08:00
|
|
|
{this.props.title}
|
|
|
|
</label>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-06 14:22:26 +08:00
|
|
|
function SwitchWrapper(target, id, data, {title, fallback, outvar, icon}) {
|
|
|
|
return <Switch target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} key={id} id={id} data={data}/>;
|
2023-08-18 15:51:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Line extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
// Initialize the state object with the initial values from the props
|
|
|
|
this.state = {
|
2023-08-18 17:09:33 +08:00
|
|
|
text: props.outvar in props.data ? props.data[props.outvar] : (props.fallback ? props.fallback : "")
|
2023-08-18 15:51:00 +08:00
|
|
|
};
|
|
|
|
// Bind the event handler to this
|
|
|
|
this.handleClick = this.handleClick.bind(this);
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.construct(this.props.outvar, this.state.text);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.target.unmount(this.props.outvar);
|
2023-08-18 15:51:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
handleClick(element) {
|
|
|
|
let text = element.target.value;
|
|
|
|
this.setState({
|
|
|
|
text: text
|
|
|
|
});
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.update(this.props.outvar, text);
|
2023-08-18 15:51:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let key = this.props.id + this.props.outvar;
|
|
|
|
return (
|
2023-08-18 17:34:52 +08:00
|
|
|
<div className="shop-line" key={this.props.id}>
|
2023-09-06 14:22:26 +08:00
|
|
|
<label htmlFor={key} className="form-label">
|
|
|
|
{this.props.icon ? <img src={`/images${this.props.icon}`} className="options-icon" /> : null}
|
|
|
|
{this.props.title}:
|
|
|
|
</label>
|
2023-08-21 12:38:17 +08:00
|
|
|
<input type="text" className="form-control form-control-sm" id={key} onChange={this.handleClick}
|
2023-08-18 17:09:33 +08:00
|
|
|
value={this.state.text}/>
|
2023-08-18 15:51:00 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-06 14:22:26 +08:00
|
|
|
function LineWrapper(target, id, data, {title, fallback, outvar, icon}) {
|
|
|
|
return <Line target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} key={id} id={id} data={data}/>;
|
2023-08-17 14:42:47 +08:00
|
|
|
}
|
|
|
|
|
2023-08-18 16:21:56 +08:00
|
|
|
class SwitchLine extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
// Initialize the state object with the initial values from the props
|
|
|
|
this.state = {
|
2023-08-18 17:09:33 +08:00
|
|
|
text: props.outvar in props.data ? props.data[props.outvar].text : (props.fallback ? props.fallback.text : ""),
|
2023-08-18 16:21:56 +08:00
|
|
|
checked: props.outvar in props.data ? props.data[props.outvar].checked : (props.fallback ? props.fallback.checked : false)
|
|
|
|
};
|
|
|
|
// Bind the event handler to this
|
|
|
|
this.handleText = this.handleText.bind(this);
|
|
|
|
this.handleCheck = this.handleCheck.bind(this);
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.construct(this.props.outvar, this.state);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.target.unmount(this.props.outvar);
|
2023-08-18 16:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
handleText(element) {
|
|
|
|
let new_state = {
|
|
|
|
...this.state,
|
|
|
|
text: element.target.value
|
|
|
|
}
|
|
|
|
this.setState(new_state);
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.update(this.props.outvar, new_state);
|
2023-08-18 16:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
handleCheck() {
|
|
|
|
// Update the state object with the new value for outvar
|
|
|
|
let new_state = {
|
2023-08-21 12:38:17 +08:00
|
|
|
...this.state,
|
2023-08-18 16:21:56 +08:00
|
|
|
checked: !this.state.checked
|
|
|
|
}
|
|
|
|
this.setState(new_state);
|
2023-08-18 17:09:33 +08:00
|
|
|
this.props.target.update(this.props.outvar, new_state);
|
2023-08-18 16:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let key = this.props.id + this.props.outvar;
|
|
|
|
return (
|
|
|
|
<div className="shop-switch-line" key={this.props.id}>
|
|
|
|
<div className="form-check form-switch" key={key}>
|
|
|
|
<input
|
|
|
|
className="form-check-input"
|
|
|
|
type="checkbox"
|
|
|
|
role="switch"
|
2023-08-18 17:09:33 +08:00
|
|
|
id={key + "switch"}
|
2023-08-18 16:21:56 +08:00
|
|
|
checked={this.state.checked}
|
|
|
|
onClick={this.handleCheck}
|
|
|
|
onChange={this.handleCheck}
|
|
|
|
/>
|
2023-08-18 17:09:33 +08:00
|
|
|
<label className="form-check-label" htmlFor={key + "switch"}>
|
2023-09-06 14:22:26 +08:00
|
|
|
{this.props.icon ? <img src={`/images${this.props.icon}`} className="options-icon" /> : null}
|
2023-08-18 16:21:56 +08:00
|
|
|
{this.props.title}
|
|
|
|
</label>
|
|
|
|
</div>
|
2023-08-21 12:38:17 +08:00
|
|
|
<input type="text" className="form-control form-control-sm" id={key + "line"} onChange={this.handleText}
|
2023-08-18 17:09:33 +08:00
|
|
|
value={this.state.text} disabled={!this.state.checked}/>
|
2023-08-18 16:21:56 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-06 14:22:26 +08:00
|
|
|
function SwitchLineWrapper(target, id, data, {title, fallback, outvar, icon}) {
|
|
|
|
return <SwitchLine target={target} title={title} fallback={fallback} outvar={outvar} icon={icon} key={id} id={id} data={data}/>;
|
2023-08-18 16:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-08-18 15:51:00 +08:00
|
|
|
function UnimplementedComponent(type, id) {
|
2023-08-17 16:41:50 +08:00
|
|
|
//console.error("Missing component with type:", type)
|
2023-08-18 15:51:00 +08:00
|
|
|
return <div key={type + id} style={{background: "red"}}>UNIMPLEMENTED</div>
|
2023-08-17 14:42:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const componentsList = {
|
2023-08-17 16:41:50 +08:00
|
|
|
"Radio": RadioWrapper,
|
2023-08-18 15:51:00 +08:00
|
|
|
"Switch": SwitchWrapper,
|
|
|
|
"Line": LineWrapper,
|
2023-08-18 16:21:56 +08:00
|
|
|
"SwitchLine": SwitchLineWrapper,
|
2023-08-18 15:51:00 +08:00
|
|
|
"Default": UnimplementedComponent,
|
2023-08-17 14:42:47 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2023-08-23 12:58:13 +08:00
|
|
|
export function ProcessOptions({options, data, target, id}) {
|
2023-08-17 14:42:47 +08:00
|
|
|
let options_t = trueTypeOf(options);
|
|
|
|
|
|
|
|
if (options_t === "array") {
|
|
|
|
return Array.from(
|
2023-08-17 16:41:50 +08:00
|
|
|
options.map((option_item, i) => ProcessOptions({
|
|
|
|
options: option_item,
|
|
|
|
data: data,
|
|
|
|
target: target,
|
|
|
|
id: id + i
|
|
|
|
}))
|
2023-08-17 14:42:47 +08:00
|
|
|
);
|
|
|
|
} else if (options_t === "object") {
|
|
|
|
if (
|
|
|
|
trueTypeOf(options.type) === "string" &&
|
|
|
|
trueTypeOf(options.args) === "object"
|
|
|
|
) {
|
|
|
|
if (options.type in componentsList) {
|
2023-08-18 12:34:30 +08:00
|
|
|
return componentsList[options.type](target, id + options.type, data, options.args);
|
2023-08-17 16:41:50 +08:00
|
|
|
} else {
|
|
|
|
return componentsList["Default"](options.type, id + "missing");
|
2023-08-17 14:42:47 +08:00
|
|
|
}
|
|
|
|
} else {
|
2023-08-17 16:41:50 +08:00
|
|
|
return ProcessOptions({options: jsonLogic.apply(options, data), data: data, target: target, id: id});
|
2023-08-17 14:42:47 +08:00
|
|
|
}
|
|
|
|
}
|
2023-08-22 10:21:44 +08:00
|
|
|
}
|
|
|
|
|
2023-08-23 16:58:02 +08:00
|
|
|
export function OptionsDialogPopup({options, data, target, id, big}) {
|
2023-08-22 10:21:44 +08:00
|
|
|
const [show, setShow] = useState(false);
|
2023-08-23 12:58:13 +08:00
|
|
|
const ref = useClickAway(() => setShow(false));
|
2023-08-22 10:21:44 +08:00
|
|
|
|
|
|
|
let div_classes = "overlayVariant border rounded " + (big ? "overlay-bigcard" : "overlay-smallcard")
|
|
|
|
const handleClick = (event) => {
|
|
|
|
setShow(!show);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2023-08-23 12:58:13 +08:00
|
|
|
<div ref={ref}>
|
2023-08-22 12:41:32 +08:00
|
|
|
<img className="alert-info" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
|
|
|
|
onClick={handleClick}/>
|
2023-08-22 10:21:44 +08:00
|
|
|
<div style={{'display': show ? 'flex' : 'none'}} className={div_classes}>
|
|
|
|
<ProcessOptions
|
|
|
|
options={options}
|
|
|
|
data={data}
|
|
|
|
key={"processed_options_" + id}
|
|
|
|
id={"processed_options_" + id}
|
|
|
|
target={target}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2023-08-22 12:41:32 +08:00
|
|
|
|
2023-08-23 16:58:02 +08:00
|
|
|
export function OptionsSummaryPopup({id, data}) {
|
2023-08-22 12:41:32 +08:00
|
|
|
const [show, setShow] = useState(false);
|
|
|
|
const [position, setPosition] = useState({x: 0, y: 0});
|
2023-08-23 16:58:02 +08:00
|
|
|
const [size, setSize] = useState({w: 0, h: 0});
|
|
|
|
const close = () => {
|
|
|
|
setShow(false);
|
|
|
|
document.removeEventListener("scroll", close, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ref = useClickAway(close);
|
|
|
|
|
|
|
|
const reposition = () => {
|
|
|
|
let popup_button = document.getElementById(id + "img");
|
|
|
|
let rect = popup_button.getBoundingClientRect()
|
|
|
|
let pos_x = (rect.left + rect.right) / 2;
|
|
|
|
let pos_y = (rect.top + rect.bottom) / 2;
|
|
|
|
if (pos_x + size.w > window.innerWidth) {
|
|
|
|
setPosition({x: pos_x - size.w - 20, y: pos_y - size.h / 2});
|
|
|
|
} else {
|
|
|
|
setPosition({x: pos_x - size.w / 2, y: pos_y - size.h - 20});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (show) {
|
|
|
|
let popup = document.getElementById(id);
|
|
|
|
let width = popup.offsetWidth;
|
|
|
|
let height = popup.offsetHeight;
|
|
|
|
setSize({w: width, h: height});
|
|
|
|
reposition()
|
|
|
|
}
|
|
|
|
}, [show])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (show) {
|
|
|
|
reposition();
|
|
|
|
}
|
|
|
|
}, [show, size])
|
2023-08-22 12:41:32 +08:00
|
|
|
|
|
|
|
const handleClick = (event) => {
|
|
|
|
setShow(!show);
|
2023-08-23 16:58:02 +08:00
|
|
|
if (!show) {
|
|
|
|
document.addEventListener("scroll", close, true);
|
|
|
|
}
|
2023-08-22 12:41:32 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const stringify = (value) => {
|
|
|
|
let value_type = trueTypeOf(value);
|
|
|
|
if (value_type === "string") {
|
|
|
|
return value;
|
|
|
|
} else if (value_type === "object") {
|
|
|
|
if (value.checked === false) {
|
|
|
|
return "off";
|
|
|
|
} else if (value.checked === true && value.text) {
|
|
|
|
return value.text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return JSON.stringify(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2023-08-23 12:58:13 +08:00
|
|
|
<div ref={ref}>
|
2023-08-22 12:41:32 +08:00
|
|
|
<img className="alert-info" src={show ? "/images/shop/icon-close.svg" : "/images/shop/icon-customize.svg"}
|
2023-08-22 17:39:18 +08:00
|
|
|
style={{'marginLeft': '10px'}}
|
2023-08-23 16:58:02 +08:00
|
|
|
id={id + "img"}
|
2023-08-22 12:41:32 +08:00
|
|
|
onClick={handleClick}/>
|
2023-08-23 16:58:02 +08:00
|
|
|
<div style={{'display': show ? 'flex' : 'none', 'top': position.y, 'left': position.x}}
|
|
|
|
className="overlayVariant card border rounded"
|
|
|
|
id={id}>
|
2023-08-22 12:41:32 +08:00
|
|
|
<div className="card-body">
|
|
|
|
{Array.from(Object.entries(data).map(([key, value], _) => {
|
|
|
|
return (<p className="card-text" key={id + key}><i>{key}</i>: {stringify(value)}</p>);
|
|
|
|
}))}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|