mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-11-14 17:14:02 +01:00
Usable proof of concept of generated variables
This commit is contained in:
62
src/datatables/data/american_wire_gauge.ts
Normal file
62
src/datatables/data/american_wire_gauge.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
export default {
|
||||||
|
title: 'American Wire Gauge',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
diameter: {
|
||||||
|
title: 'Diameter',
|
||||||
|
type: 'number',
|
||||||
|
unit: 'mm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: {
|
||||||
|
'0000': { diameter: 11.684 },
|
||||||
|
'000': { diameter: 10.405 },
|
||||||
|
'00': { diameter: 9.266 },
|
||||||
|
'0': { diameter: 8.251 },
|
||||||
|
'(4/0)': { diameter: 11.684 },
|
||||||
|
'(3/0)': { diameter: 10.405 },
|
||||||
|
'(2/0)': { diameter: 9.266 },
|
||||||
|
'(1/0)': { diameter: 8.251 },
|
||||||
|
'1': { diameter: 7.348 },
|
||||||
|
'2': { diameter: 6.544 },
|
||||||
|
'3': { diameter: 5.827 },
|
||||||
|
'4': { diameter: 5.189 },
|
||||||
|
'5': { diameter: 4.621 },
|
||||||
|
'6': { diameter: 4.115 },
|
||||||
|
'7': { diameter: 3.665 },
|
||||||
|
'8': { diameter: 3.264 },
|
||||||
|
'9': { diameter: 2.906 },
|
||||||
|
'10': { diameter: 2.588 },
|
||||||
|
'11': { diameter: 2.305 },
|
||||||
|
'12': { diameter: 2.053 },
|
||||||
|
'13': { diameter: 1.828 },
|
||||||
|
'14': { diameter: 1.628 },
|
||||||
|
'15': { diameter: 1.45 },
|
||||||
|
'16': { diameter: 1.291 },
|
||||||
|
'17': { diameter: 1.15 },
|
||||||
|
'18': { diameter: 1.024 },
|
||||||
|
'19': { diameter: 0.912 },
|
||||||
|
'20': { diameter: 0.812 },
|
||||||
|
'21': { diameter: 0.723 },
|
||||||
|
'22': { diameter: 0.644 },
|
||||||
|
'23': { diameter: 0.573 },
|
||||||
|
'24': { diameter: 0.511 },
|
||||||
|
'25': { diameter: 0.455 },
|
||||||
|
'26': { diameter: 0.405 },
|
||||||
|
'27': { diameter: 0.361 },
|
||||||
|
'28': { diameter: 0.321 },
|
||||||
|
'29': { diameter: 0.286 },
|
||||||
|
'30': { diameter: 0.255 },
|
||||||
|
'31': { diameter: 0.227 },
|
||||||
|
'32': { diameter: 0.202 },
|
||||||
|
'33': { diameter: 0.18 },
|
||||||
|
'34': { diameter: 0.16 },
|
||||||
|
'35': { diameter: 0.143 },
|
||||||
|
'36': { diameter: 0.127 },
|
||||||
|
'37': { diameter: 0.113 },
|
||||||
|
'38': { diameter: 0.101 },
|
||||||
|
'39': { diameter: 0.0897 },
|
||||||
|
'40': { diameter: 0.0799 }
|
||||||
|
}
|
||||||
|
};
|
||||||
3
src/datatables/data/index.ts
Normal file
3
src/datatables/data/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const allDataTables: { [key: string]: { [key: string]: any } } = {
|
||||||
|
'american-wire-gauge': {}
|
||||||
|
};
|
||||||
20
src/datatables/data/material_electrical_properties.ts
Normal file
20
src/datatables/data/material_electrical_properties.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export default {
|
||||||
|
title: 'Material Electrical Properties',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
resistivity_20c: {
|
||||||
|
title: 'Resistivity at 20°C',
|
||||||
|
type: 'number',
|
||||||
|
unit: 'Ω/m'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: {
|
||||||
|
copper: {
|
||||||
|
resistivity: 1.68e-8
|
||||||
|
},
|
||||||
|
aluminum: {
|
||||||
|
resistivity: 2.82e-8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
17
src/datatables/index.ts
Normal file
17
src/datatables/index.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { DataTable } from './types.ts';
|
||||||
|
import { allDataTables } from './data/index';
|
||||||
|
|
||||||
|
export async function getDataTable(name: string): Promise<DataTable> {
|
||||||
|
const x = await import(`./${name}`);
|
||||||
|
return x.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listDataTables(): Promise<{ name: string }[]> {
|
||||||
|
const x: { name: string }[] = [];
|
||||||
|
for (const key in allDataTables) {
|
||||||
|
x.push({ name: key });
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DataTable };
|
||||||
19
src/datatables/types.ts
Normal file
19
src/datatables/types.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Represents a set of rows indexed by a key.
|
||||||
|
Used for calculator presets
|
||||||
|
|
||||||
|
*/
|
||||||
|
export interface DataTable {
|
||||||
|
[key: string]: {
|
||||||
|
title: string;
|
||||||
|
/* A JSON schema properties */
|
||||||
|
columns: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
[key: string]: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
3
src/pages/tools/number/generic-calc/data/index.ts
Normal file
3
src/pages/tools/number/generic-calc/data/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import ohmslaw from './ohms_law';
|
||||||
|
|
||||||
|
export default [ohmslaw];
|
||||||
29
src/pages/tools/number/generic-calc/data/ohms_law.ts
Normal file
29
src/pages/tools/number/generic-calc/data/ohms_law.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { GenericCalcType } from './types';
|
||||||
|
|
||||||
|
const ohmsLawCalc: GenericCalcType = {
|
||||||
|
title: "Ohm's Law",
|
||||||
|
name: 'ohms-law',
|
||||||
|
formula: 'V = I * R',
|
||||||
|
selections: [],
|
||||||
|
variables: [
|
||||||
|
{
|
||||||
|
name: 'V',
|
||||||
|
title: 'Voltage',
|
||||||
|
unit: 'V',
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'I',
|
||||||
|
title: 'Current',
|
||||||
|
unit: 'A',
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'R',
|
||||||
|
title: 'Resistance',
|
||||||
|
unit: 'Ω'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ohmsLawCalc;
|
||||||
21
src/pages/tools/number/generic-calc/data/types.ts
Normal file
21
src/pages/tools/number/generic-calc/data/types.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export interface GenericCalcType {
|
||||||
|
title: string;
|
||||||
|
name: string;
|
||||||
|
formula: string;
|
||||||
|
selections?: {
|
||||||
|
title: string;
|
||||||
|
source: string;
|
||||||
|
default: string;
|
||||||
|
bind: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
variables: {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
unit: string;
|
||||||
|
|
||||||
|
// If absence, assume it's the default target var
|
||||||
|
default?: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
InputLabel,
|
Autocomplete,
|
||||||
|
TextField,
|
||||||
Radio,
|
Radio,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -13,54 +14,41 @@ import ToolContent from '@components/ToolContent';
|
|||||||
import { ToolComponentProps } from '@tools/defineTool';
|
import { ToolComponentProps } from '@tools/defineTool';
|
||||||
import ToolTextResult from '@components/result/ToolTextResult';
|
import ToolTextResult from '@components/result/ToolTextResult';
|
||||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||||
import { GetGroupsType, UpdateField } from '@components/options/ToolOptions';
|
import { UpdateField } from '@components/options/ToolOptions';
|
||||||
import { InitialValuesType } from './types';
|
import { InitialValuesType } from './types';
|
||||||
|
import type { GenericCalcType } from './data/types';
|
||||||
|
import type { DataTable } from 'datatables';
|
||||||
|
import { getDataTable } from 'datatables';
|
||||||
|
|
||||||
import nerdamer from 'nerdamer';
|
import nerdamer from 'nerdamer';
|
||||||
import 'nerdamer/Algebra';
|
import 'nerdamer/Algebra';
|
||||||
import 'nerdamer/Solve';
|
import 'nerdamer/Solve';
|
||||||
import 'nerdamer/Calculus';
|
import 'nerdamer/Calculus';
|
||||||
|
|
||||||
const ohmsLawCalc: {
|
export default async function makeTool(
|
||||||
name: string;
|
calcData: GenericCalcType
|
||||||
formula: string;
|
): Promise<React.JSXElementConstructor<ToolComponentProps>> {
|
||||||
variables: {
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
unit: string;
|
|
||||||
}[];
|
|
||||||
} = {
|
|
||||||
name: "Ohm's Law Calculator",
|
|
||||||
formula: 'V = I * R',
|
|
||||||
variables: [
|
|
||||||
{
|
|
||||||
name: 'V',
|
|
||||||
title: 'Voltage',
|
|
||||||
unit: 'V'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'I',
|
|
||||||
title: 'Current',
|
|
||||||
unit: 'A'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'R',
|
|
||||||
title: 'Resistance',
|
|
||||||
unit: 'Ω'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function makeTool(): React.JSXElementConstructor<ToolComponentProps> {
|
|
||||||
const initialValues: InitialValuesType = {
|
const initialValues: InitialValuesType = {
|
||||||
outputVariable: '',
|
outputVariable: '',
|
||||||
vars: {}
|
vars: {},
|
||||||
|
presets: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dataTables: { [key: string]: DataTable } = {};
|
||||||
|
|
||||||
|
for (const selection of calcData.selections || []) {
|
||||||
|
dataTables[selection.source] = await getDataTable(selection.source);
|
||||||
|
}
|
||||||
|
|
||||||
return function GenericCalc({ title }: ToolComponentProps) {
|
return function GenericCalc({ title }: ToolComponentProps) {
|
||||||
const [result, setResult] = useState<string>('');
|
const [result, setResult] = useState<string>('');
|
||||||
const [shortResult, setShortResult] = useState<string>('');
|
const [shortResult, setShortResult] = useState<string>('');
|
||||||
|
|
||||||
|
// For UX purposes we need to track what vars are
|
||||||
|
const [valsBoundToPreset, setValsBoundToPreset] = useState<{
|
||||||
|
[key: string]: string;
|
||||||
|
}>({});
|
||||||
|
|
||||||
const updateVarField = (
|
const updateVarField = (
|
||||||
name: string,
|
name: string,
|
||||||
value: number,
|
value: number,
|
||||||
@@ -83,12 +71,89 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
|
|||||||
updateFieldFunc('outputVariable', varName);
|
updateFieldFunc('outputVariable', varName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectedPresetChange = (
|
||||||
|
selection: string,
|
||||||
|
preset: string,
|
||||||
|
currentValues: InitialValuesType,
|
||||||
|
updateFieldFunc: UpdateField<InitialValuesType>
|
||||||
|
) => {
|
||||||
|
const newValsBoundToPreset = { ...valsBoundToPreset };
|
||||||
|
|
||||||
|
const newPresets = { ...currentValues.presets };
|
||||||
|
newPresets[selection] = preset;
|
||||||
|
updateFieldFunc('presets', newPresets);
|
||||||
|
|
||||||
|
// Clear old selection
|
||||||
|
for (const key in valsBoundToPreset) {
|
||||||
|
if (valsBoundToPreset[key] === selection) {
|
||||||
|
delete newValsBoundToPreset[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectionData = calcData.selections?.find(
|
||||||
|
(sel) => sel.title === selection
|
||||||
|
);
|
||||||
|
|
||||||
|
if (preset != '<custom>') {
|
||||||
|
if (selectionData) {
|
||||||
|
for (const key in selectionData.bind) {
|
||||||
|
newValsBoundToPreset[key] = selection;
|
||||||
|
updateVarField(
|
||||||
|
key,
|
||||||
|
dataTables[selectionData.source].data[preset][
|
||||||
|
selectionData.bind[key]
|
||||||
|
],
|
||||||
|
currentValues,
|
||||||
|
updateFieldFunc
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setValsBoundToPreset(newValsBoundToPreset);
|
||||||
|
throw new Error(
|
||||||
|
`Preset "${preset}" is not valid for selection "${selection}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setValsBoundToPreset(newValsBoundToPreset);
|
||||||
|
};
|
||||||
|
|
||||||
|
calcData.variables.forEach((variable) => {
|
||||||
|
if (variable.default === undefined) {
|
||||||
|
initialValues.vars[variable.name] = {
|
||||||
|
value: NaN,
|
||||||
|
unit: variable.unit
|
||||||
|
};
|
||||||
|
initialValues.outputVariable = variable.name;
|
||||||
|
} else {
|
||||||
|
initialValues.vars[variable.name] = {
|
||||||
|
value: variable.default || 0,
|
||||||
|
unit: variable.unit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
calcData.selections?.forEach((selection) => {
|
||||||
|
initialValues.presets[selection.title] = selection.default;
|
||||||
|
if (selection.default == '<custom>') return;
|
||||||
|
for (const key in selection.bind) {
|
||||||
|
initialValues.vars[key] = {
|
||||||
|
value:
|
||||||
|
dataTables[selection.source].data[selection.default][
|
||||||
|
selection.bind[key]
|
||||||
|
],
|
||||||
|
unit: dataTables[selection.source].cols[selection.bind[key]].unit
|
||||||
|
};
|
||||||
|
valsBoundToPreset[key] = selection.default;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolContent
|
<ToolContent
|
||||||
title={title}
|
title={title}
|
||||||
inputComponent={null}
|
inputComponent={null}
|
||||||
resultComponent={
|
resultComponent={
|
||||||
<ToolTextResult title={ohmsLawCalc.name} value={result} />
|
<ToolTextResult title={calcData.title} value={result} />
|
||||||
}
|
}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
toolInfo={{
|
toolInfo={{
|
||||||
@@ -97,6 +162,50 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
|
|||||||
'Common mathematical equations that can be used in calculations.'
|
'Common mathematical equations that can be used in calculations.'
|
||||||
}}
|
}}
|
||||||
getGroups={({ values, updateField }) => [
|
getGroups={({ values, updateField }) => [
|
||||||
|
{
|
||||||
|
title: 'Presets',
|
||||||
|
component: (
|
||||||
|
<Box>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Option</TableCell>
|
||||||
|
<TableCell>Value</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{calcData.selections?.map((preset) => (
|
||||||
|
<TableRow key={preset.title}>
|
||||||
|
<TableCell>{preset.title}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Autocomplete
|
||||||
|
disablePortal
|
||||||
|
id="combo-box-demo"
|
||||||
|
value={values.presets[preset.title]}
|
||||||
|
options={Object.keys(
|
||||||
|
dataTables[preset.source].data
|
||||||
|
).sort()}
|
||||||
|
sx={{ width: 300 }}
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
handleSelectedPresetChange(
|
||||||
|
preset.title,
|
||||||
|
newValue || '',
|
||||||
|
values,
|
||||||
|
updateField
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField {...params} label="Preset" />
|
||||||
|
)}
|
||||||
|
></Autocomplete>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Input Variables',
|
title: 'Input Variables',
|
||||||
component: (
|
component: (
|
||||||
@@ -110,20 +219,23 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{ohmsLawCalc.variables.map((variable) => (
|
{calcData.variables.map((variable) => (
|
||||||
<TableRow key={variable.name}>
|
<TableRow key={variable.name}>
|
||||||
<TableCell>{variable.name}</TableCell>
|
<TableCell>{variable.name}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<TextFieldWithDesc
|
<TextFieldWithDesc
|
||||||
title={variable.title}
|
title={variable.title}
|
||||||
sx={{ width: '25ch' }}
|
sx={{ width: '25ch' }}
|
||||||
description=""
|
description={valsBoundToPreset[variable.name] || ''}
|
||||||
value={
|
value={
|
||||||
values.outputVariable === variable.name
|
values.outputVariable === variable.name
|
||||||
? shortResult
|
? shortResult
|
||||||
: values.vars[variable.name]?.value || NaN
|
: values.vars[variable.name]?.value || NaN
|
||||||
}
|
}
|
||||||
disabled={values.outputVariable === variable.name}
|
disabled={
|
||||||
|
values.outputVariable === variable.name ||
|
||||||
|
valsBoundToPreset[variable.name] !== undefined
|
||||||
|
}
|
||||||
onOwnChange={(val) =>
|
onOwnChange={(val) =>
|
||||||
updateVarField(
|
updateVarField(
|
||||||
variable.name,
|
variable.name,
|
||||||
@@ -161,7 +273,7 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
|
|||||||
setResult('Please select a solve for variable');
|
setResult('Please select a solve for variable');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let expr = nerdamer(ohmsLawCalc.formula);
|
let expr = nerdamer(calcData.formula);
|
||||||
|
|
||||||
Object.keys(values.vars).forEach((key) => {
|
Object.keys(values.vars).forEach((key) => {
|
||||||
if (key === values.outputVariable) return;
|
if (key === values.outputVariable) return;
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
import { defineTool } from '@tools/defineTool';
|
import { DefinedTool, defineTool } from '@tools/defineTool';
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
|
import type { GenericCalcType } from './data/types';
|
||||||
|
import allGenericCalcs from './data/index';
|
||||||
|
|
||||||
async function importComponent() {
|
async function importComponent(data: GenericCalcType) {
|
||||||
const x = await import('./index');
|
const x = await import('./index');
|
||||||
return { default: x.default() };
|
return { default: await x.default(data) };
|
||||||
}
|
}
|
||||||
export const tool = defineTool('number', {
|
|
||||||
name: 'Generic calc',
|
const tools: DefinedTool[] = [];
|
||||||
path: 'generic-calc',
|
|
||||||
icon: '',
|
allGenericCalcs.forEach((x) => {
|
||||||
description: '',
|
async function importComponent2() {
|
||||||
shortDescription: '',
|
return await importComponent(x);
|
||||||
keywords: ['generic', 'calc'],
|
}
|
||||||
longDescription: '',
|
|
||||||
component: lazy(importComponent)
|
tools.push(
|
||||||
|
defineTool('number', {
|
||||||
|
name: x.title,
|
||||||
|
path: 'generic-calc/x.name',
|
||||||
|
icon: '',
|
||||||
|
description: '',
|
||||||
|
shortDescription: '',
|
||||||
|
keywords: ['generic', 'calc'],
|
||||||
|
longDescription: '',
|
||||||
|
component: lazy(importComponent2)
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export { tools };
|
||||||
|
|||||||
@@ -6,5 +6,9 @@ export type InitialValuesType = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Track preset selections
|
||||||
|
presets: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
outputVariable: string;
|
outputVariable: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { tool as numberSum } from './sum/meta';
|
import { tool as numberSum } from './sum/meta';
|
||||||
import { tool as numberGenerate } from './generate/meta';
|
import { tool as numberGenerate } from './generate/meta';
|
||||||
import { tool as numberArithmeticSequence } from './arithmetic-sequence/meta';
|
import { tool as numberArithmeticSequence } from './arithmetic-sequence/meta';
|
||||||
import { tool as genericCalc } from './generic-calc/meta';
|
import { tools as genericCalcTools } from './generic-calc/meta';
|
||||||
export const numberTools = [
|
export const numberTools = [
|
||||||
numberSum,
|
numberSum,
|
||||||
numberGenerate,
|
numberGenerate,
|
||||||
numberArithmeticSequence,
|
numberArithmeticSequence,
|
||||||
genericCalc
|
...genericCalcTools
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user