forked from React-Group/interstellar_ai
main (#137)
Reviewed-on: https://interstellardevelopment.org/code/code/React-Group/interstellar_ai/pulls/137 Reviewed-by: Patrick <patrick_pluto@noreply.localhost> Co-authored-by: sageTheDM <info@photofuel.tech> Co-committed-by: sageTheDM <info@photofuel.tech>
This commit is contained in:
parent
f9bce3b22a
commit
91353bd051
34 changed files with 682 additions and 567 deletions
|
@ -1,17 +1,19 @@
|
|||
import React from 'react';
|
||||
|
||||
// Define the props for the ButtonSetting component
|
||||
interface ButtonSettingProps {
|
||||
label: string; // The label to display on the button
|
||||
onClick: () => void; // The function to call when the button is clicked
|
||||
className?: string; // Optional additional classes for styling
|
||||
}
|
||||
|
||||
const ButtonSetting: React.FC<ButtonSettingProps> = ({ label, onClick }) => {
|
||||
// Functional component definition
|
||||
const ButtonSetting: React.FC<ButtonSettingProps> = ({ label, onClick, className }) => {
|
||||
return (
|
||||
<div className="settings-option">
|
||||
<div className="settings-option"> {/* Container for the button */}
|
||||
<button
|
||||
onClick={onClick} // Call the onClick function when the button is clicked
|
||||
className={"export-button"} // Apply any additional classes
|
||||
className={`export-button ${className || ''}`} // Apply any additional classes, default to empty if not provided
|
||||
>
|
||||
{label} {/* Display the label on the button */}
|
||||
</button>
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
import React from 'react';
|
||||
|
||||
// Define the props for the CheckboxSetting component
|
||||
interface CheckboxSettingProps {
|
||||
label: string; // The label to display
|
||||
checked: boolean; // The checked state of the checkbox
|
||||
setChecked: (value: boolean) => void; // Method to update the state
|
||||
}
|
||||
|
||||
// Functional component definition
|
||||
const CheckboxSetting: React.FC<CheckboxSettingProps> = ({ label, checked, setChecked }) => {
|
||||
// Handler to toggle the checkbox state
|
||||
const handleCheckboxChange = () => {
|
||||
setChecked(!checked);
|
||||
setChecked(!checked); // Toggle the checked state
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="settings-option">
|
||||
<div className="settings-option"> {/* Container for the checkbox setting */}
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={handleCheckboxChange}
|
||||
type="checkbox" // Checkbox input type
|
||||
checked={checked} // Set the checked state based on the prop
|
||||
onChange={handleCheckboxChange} // Call the handler on change
|
||||
/>
|
||||
{label}
|
||||
{label} {/* Display the label next to the checkbox */}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,29 +1,32 @@
|
|||
import React from 'react';
|
||||
import React from 'react';
|
||||
|
||||
interface ColorSettingProps {
|
||||
// Define the props for the ColorSetting component
|
||||
interface ColorSettingProps {
|
||||
name: string; // The name to display in the <p> tag
|
||||
value: string; // The current color value
|
||||
setValue: (newColor: string) => void; // The method to update the state
|
||||
cssVariable: string; // The CSS variable name to set
|
||||
}
|
||||
}
|
||||
|
||||
const ColorSetting: React.FC<ColorSettingProps> = ({ name, value, setValue, cssVariable }) => {
|
||||
// Functional component definition
|
||||
const ColorSetting: React.FC<ColorSettingProps> = ({ name, value, setValue, cssVariable }) => {
|
||||
// Handler to change the color value
|
||||
const handleColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newColor = e.target.value;
|
||||
setValue(newColor);
|
||||
document.documentElement.style.setProperty(cssVariable, newColor);
|
||||
const newColor = e.target.value; // Get the new color from the input
|
||||
setValue(newColor); // Update the state with the new color
|
||||
document.documentElement.style.setProperty(cssVariable, newColor); // Set the CSS variable
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="settings-option">
|
||||
<p>{name}</p>
|
||||
<input
|
||||
type="color"
|
||||
value={value}
|
||||
onChange={handleColorChange}
|
||||
/>
|
||||
<div className="settings-option"> {/* Container for the color setting */}
|
||||
<p>{name}</p> {/* Display the name */}
|
||||
<input
|
||||
type="color" // Input type for color picker
|
||||
value={value} // Set the input value to the current color
|
||||
onChange={handleColorChange} // Call the handler on change
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ColorSetting;
|
||||
export default ColorSetting;
|
||||
|
|
|
@ -1,35 +1,39 @@
|
|||
import React from 'react';
|
||||
|
||||
// Define the structure of each option in the dropdown
|
||||
interface Option {
|
||||
value: string; // The actual value to be used
|
||||
label: string; // The label to display for the option
|
||||
value: string; // The actual value to be used
|
||||
label: string; // The label to display for the option
|
||||
}
|
||||
|
||||
// Define the props for the DropdownSetting component
|
||||
interface DropdownSettingProps {
|
||||
label: string; // The label to display
|
||||
value: string; // The current selected value
|
||||
setValue: (newValue: string) => void; // The method to update the state
|
||||
options: Option[]; // List of options for the dropdown
|
||||
label: string; // The label to display above the dropdown
|
||||
value: string; // The currently selected value
|
||||
setValue: (newValue: string) => void; // Method to update the state with the new value
|
||||
options: Option[]; // List of options for the dropdown
|
||||
}
|
||||
|
||||
// Functional component definition
|
||||
const DropdownSetting: React.FC<DropdownSettingProps> = ({ label, value, setValue, options }) => {
|
||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setValue(newValue);
|
||||
};
|
||||
// Handler to change the selected option
|
||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const newValue = e.target.value; // Get the new selected value
|
||||
setValue(newValue); // Update the state with the new value
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="settings-option">
|
||||
<label>{label}</label>
|
||||
<select value={value} onChange={handleSelectChange}>
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="settings-option"> {/* Container for the dropdown setting */}
|
||||
<label>{label}</label> {/* Display the label */}
|
||||
<select value={value} onChange={handleSelectChange}> {/* Dropdown selection */}
|
||||
{options.map((option) => ( // Map through options to create <option> elements
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label} {/* Display the label for the option */}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownSetting;
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
import React from 'react';
|
||||
|
||||
const ThemeDropdown: React.FC<{
|
||||
selectedTheme: string;
|
||||
setSelectedTheme: (theme: string) => void;
|
||||
selectedTheme: string; // Currently selected theme
|
||||
setSelectedTheme: (theme: string) => void; // Function to update the selected theme
|
||||
}> = ({ selectedTheme, setSelectedTheme }) => {
|
||||
// Define available theme options
|
||||
const themeOptions = [
|
||||
{ value: 'IOMARKET', label: 'IOMARKET' },
|
||||
{ value: 'WHITE', label: 'WHITE' },
|
||||
|
@ -14,22 +15,22 @@ const ThemeDropdown: React.FC<{
|
|||
];
|
||||
|
||||
return (
|
||||
<div className="settings-option">
|
||||
<p>Select Theme</p>
|
||||
<div className="settings-option"> {/* Container for the dropdown */}
|
||||
<p>Select Theme</p> {/* Label for the dropdown */}
|
||||
<select
|
||||
value={selectedTheme}
|
||||
onChange={(e) => {
|
||||
const theme = e.target.value;
|
||||
value={selectedTheme} // Current selected theme
|
||||
onChange={(e) => { // Handler for dropdown changes
|
||||
const theme = e.target.value; // Get the selected value
|
||||
if (theme !== 'default' && typeof localStorage !== 'undefined') {
|
||||
setSelectedTheme(theme);
|
||||
localStorage.setItem('selectedTheme', theme);
|
||||
setSelectedTheme(theme); // Update the selected theme state
|
||||
localStorage.setItem('selectedTheme', theme); // Save the theme to localStorage
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="default">Select your style...</option>
|
||||
{themeOptions.map((option) => (
|
||||
<option value="default">Select your style...</option> {/* Default option */}
|
||||
{themeOptions.map((option) => ( // Map through theme options to create <option> elements
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
{option.label} {/* Display the label for the option */}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
|
|
@ -2,28 +2,29 @@
|
|||
import React from 'react';
|
||||
|
||||
interface FontSizeSettingProps {
|
||||
fontSize: string; // The current font size
|
||||
fontSize: string; // The current font size as a string (e.g., "16px")
|
||||
setFontSize: (newSize: string) => void; // Function to update the font size
|
||||
}
|
||||
|
||||
const FontSizeSetting: React.FC<FontSizeSettingProps> = ({ fontSize, setFontSize }) => {
|
||||
// Handle changes to the font size input
|
||||
const handleFontSizeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newSize = `${e.target.value}px`;
|
||||
setFontSize(newSize);
|
||||
document.documentElement.style.setProperty('--font-size', newSize);
|
||||
const newSize = `${e.target.value}px`; // Create the new font size string
|
||||
setFontSize(newSize); // Update the font size state
|
||||
document.documentElement.style.setProperty('--font-size', newSize); // Update the CSS variable
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="settings-option">
|
||||
<p>Font Size</p>
|
||||
<div className="settings-option"> {/* Container for the font size setting */}
|
||||
<p>Font Size</p> {/* Label for the setting */}
|
||||
<input
|
||||
type="range"
|
||||
min="12"
|
||||
max="30"
|
||||
value={parseInt(fontSize, 10)} // Ensure value is a number
|
||||
onChange={handleFontSizeChange}
|
||||
type="range" // Range input for selecting font size
|
||||
min="12" // Minimum font size
|
||||
max="30" // Maximum font size
|
||||
value={parseInt(fontSize, 10)} // Ensure value is a number for the slider
|
||||
onChange={handleFontSizeChange} // Update font size on change
|
||||
/>
|
||||
<span>{fontSize}</span>
|
||||
<span>{fontSize}</span> {/* Display the current font size */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,25 +12,26 @@ const OpenSourceModeToggle: React.FC<OpenSourceModeToggleProps> = ({
|
|||
setOpenSourceMode,
|
||||
setSelectedOption
|
||||
}) => {
|
||||
// Handle toggle change event
|
||||
const handleToggleChange = () => {
|
||||
const newValue = !openSourceMode;
|
||||
setOpenSourceMode(newValue);
|
||||
const newValue = !openSourceMode; // Toggle the current state
|
||||
setOpenSourceMode(newValue); // Update the open source mode state
|
||||
|
||||
// Update radio selection based on the new openSourceMode value
|
||||
if (newValue) {
|
||||
setSelectedOption('FOSS'); // Set to FOSS if enabling open source mode
|
||||
} else {
|
||||
setSelectedOption('None'); // Or any other default value when disabling
|
||||
setSelectedOption('None'); // Set to a default value when disabling
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="settings-option">
|
||||
<div className="settings-option"> {/* Container for the toggle setting */}
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={openSourceMode}
|
||||
onChange={handleToggleChange}
|
||||
type="checkbox" // Checkbox for toggling open source mode
|
||||
checked={openSourceMode} // Check if the mode is currently enabled
|
||||
onChange={handleToggleChange} // Handle changes to the checkbox
|
||||
/>
|
||||
Enable Open Source Mode
|
||||
</label>
|
||||
|
|
|
@ -14,28 +14,28 @@ const PrivacySettings: React.FC<PrivacySettingsProps> = ({ selectedOption, handl
|
|||
<div className="settings-option">
|
||||
<p>Disable Options:</p>
|
||||
<div className="slider">
|
||||
{/* Offline */}
|
||||
{/* Offline Option */}
|
||||
<div
|
||||
className={`slider-option ${selectedOption === 'Offline' ? 'active' : ''}`}
|
||||
onClick={() => handleRadioChange('Offline')} // Allow selection only if not in open-source mode
|
||||
className={`slider-option ${selectedOption === 'Offline' ? 'active' : ''}`} // Active class based on selection
|
||||
onClick={() => handleRadioChange('Offline')} // Handle selection change
|
||||
>
|
||||
Offline tools{openSourceMode ? ' (FOSS)' : ''}
|
||||
Offline tools{openSourceMode ? ' (FOSS)' : ''} {/* Display FOSS label if applicable */}
|
||||
</div>
|
||||
|
||||
{/* Online */}
|
||||
{/* Online Option */}
|
||||
<div
|
||||
className={`slider-option ${selectedOption === 'Online' ? 'active' : ''}`}
|
||||
onClick={() => handleRadioChange('Online')}
|
||||
className={`slider-option ${selectedOption === 'Online' ? 'active' : ''}`} // Active class based on selection
|
||||
onClick={() => handleRadioChange('Online')} // Handle selection change
|
||||
>
|
||||
Online tools{openSourceMode ? ' (FOSS)' : ''}
|
||||
Online tools{openSourceMode ? ' (FOSS)' : ''} {/* Display FOSS label if applicable */}
|
||||
</div>
|
||||
|
||||
{/* None */}
|
||||
{/* None Option */}
|
||||
<div
|
||||
className={`slider-option ${selectedOption === 'None' ? 'active' : ''}`}
|
||||
onClick={() => handleRadioChange('None')}
|
||||
className={`slider-option ${selectedOption === 'None' ? 'active' : ''}`} // Active class based on selection
|
||||
onClick={() => handleRadioChange('None')} // Handle selection change
|
||||
>
|
||||
None{openSourceMode ? ' (FOSS)' : ''}
|
||||
None{openSourceMode ? ' (FOSS)' : ''} {/* Display FOSS label if applicable */}
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
|
|
@ -65,7 +65,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
// Measurement setting
|
||||
const [preferredMeasurement, setPreferredMeasurement] = useState(() => localStorage.getItem('preferredMeasurement') || 'Metric');
|
||||
|
||||
// Theme settings
|
||||
//#region Theme settings
|
||||
const [backgroundColor, setBackgroundColor] = useState(() => getComputedStyle(document.documentElement).getPropertyValue('--background-color').trim());
|
||||
const [headerBackground, setHeaderBackground] = useState(() => getComputedStyle(document.documentElement).getPropertyValue('--header-background-color').trim());
|
||||
const [textColor, setTextColor] = useState(() => getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim());
|
||||
|
@ -122,6 +122,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
const [google, setGoogle] = useState(localStorage.getItem('google') || "");
|
||||
const [myBoolean, setMyBoolean] = useState<boolean>(() => getItemFromLocalStorage('myBoolean'));
|
||||
|
||||
//#region Json
|
||||
const settings = {
|
||||
userPreferences: {
|
||||
activeSection,
|
||||
|
@ -185,6 +186,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
},
|
||||
};
|
||||
|
||||
//#region color settings
|
||||
const colorSettings = [
|
||||
{ name: "Background Color", value: backgroundColor, setValue: setBackgroundColor, cssVariable: "--background-color" },
|
||||
{ name: "Header Background Color", value: headerBackground, setValue: setHeaderBackground, cssVariable: "--header-background-color" },
|
||||
|
@ -219,7 +221,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
{ name: "Burger Menu Background Color", value: burgerMenuBackgroundColor, setValue: setBurgerMenuBackgroundColor, cssVariable: "--burger-menu-background-color" },
|
||||
];
|
||||
|
||||
|
||||
//#region time settings
|
||||
const timeZoneOptions = [
|
||||
{ value: 'GMT', label: 'GMT' },
|
||||
{ value: 'EST', label: 'EST' },
|
||||
|
@ -233,7 +235,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
{ value: 'JST', label: 'JST' },
|
||||
];
|
||||
|
||||
|
||||
//#region language settings
|
||||
const languageOptions = [
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'es', name: 'Spanish' },
|
||||
|
@ -246,7 +248,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
{ code: 'ru', name: 'Russian' },
|
||||
{ code: 'ar', name: 'Arabic' },
|
||||
];
|
||||
|
||||
//#region currency settings
|
||||
const currencyOptions = [
|
||||
{ code: 'usd', name: 'USD' },
|
||||
{ code: 'eur', name: 'EUR' },
|
||||
|
@ -258,7 +260,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
{ code: 'cny', name: 'CNY' },
|
||||
{ code: 'inr', name: 'INR' },
|
||||
];
|
||||
|
||||
//#region date settings
|
||||
const dateFormatOptions = [
|
||||
{ value: 'mm/dd/yyyy', label: 'MM/DD/YYYY' },
|
||||
{ value: 'dd/mm/yyyy', label: 'DD/MM/YYYY' },
|
||||
|
@ -276,7 +278,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
{ value: 'Metric', label: 'Metric' },
|
||||
{ value: 'Imperial', label: 'Imperial' },
|
||||
];
|
||||
|
||||
//#region text settings
|
||||
const fontOptions = [
|
||||
{ value: "'Poppins', sans-serif", label: 'Poppins' },
|
||||
{ value: "'Inconsolata', monospace", label: 'Inconsolata' },
|
||||
|
@ -294,7 +296,7 @@ const Settings: React.FC<{ closeSettings: () => void; accountName: string }> = (
|
|||
{ value: "'Zilla Slab Highlight', serif", label: 'Zilla Slab Highlight' },
|
||||
];
|
||||
|
||||
//#region Start of the code
|
||||
//#region Function
|
||||
const handleLogout = () => {
|
||||
localStorage.clear();
|
||||
alert('Successfully logged out!');
|
||||
|
|
|
@ -10,6 +10,7 @@ export function exportSettings(): string {
|
|||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key) {
|
||||
// Exclude sensitive information
|
||||
if (key !== "accountName" && key !== "accountPassword" && key !== "accountEmail") {
|
||||
settings[key] = localStorage.getItem(key) || "";
|
||||
}
|
||||
|
@ -39,25 +40,28 @@ export function importSettings(jsonData: string): void {
|
|||
}
|
||||
}
|
||||
|
||||
// Send current settings to the database
|
||||
export const sendToDatabase = async () => {
|
||||
const useName = localStorage.getItem("accountName")
|
||||
const usePassword = localStorage.getItem("accountPassword")
|
||||
const useName = localStorage.getItem("accountName");
|
||||
const usePassword = localStorage.getItem("accountPassword");
|
||||
|
||||
if (useName && usePassword) {
|
||||
const result = await changeSettings(useName, usePassword, JSON.parse(exportSettings()))
|
||||
if (result == true) {
|
||||
const result = await changeSettings(useName, usePassword, JSON.parse(exportSettings()));
|
||||
if (result === true) {
|
||||
// Only reload if the settings change was successful
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// Import settings from the database based on username and password
|
||||
export const importDatabase = async (useName: string, usePassword: string) => {
|
||||
const databaseSettings = await getSettings(useName, usePassword);
|
||||
|
||||
// Ensure user settings exist before flattening and storing
|
||||
if (typeof databaseSettings == 'object' && databaseSettings) {
|
||||
if (typeof databaseSettings === 'object' && databaseSettings) {
|
||||
importSettings(JSON.stringify(databaseSettings, null, 2)); // Pass only the current user's settings
|
||||
} else {
|
||||
console.error('Database settings are not in the expected format.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//#region IOMARKET
|
||||
export const applyIOMarketTheme = () => {
|
||||
document.documentElement.style.setProperty('--header-background-color', '#7e7e7e');
|
||||
document.documentElement.style.setProperty('--header-text-color', '#ffffff');
|
||||
|
@ -35,7 +36,7 @@ export const applyIOMarketTheme = () => {
|
|||
document.documentElement.style.setProperty('--font-family', "'Poppins', 'sans-serif'");
|
||||
document.documentElement.style.setProperty('--font-size', '16px');
|
||||
};
|
||||
|
||||
//#region WHITE
|
||||
export const applyWhiteTheme = () => {
|
||||
document.documentElement.style.setProperty('--header-background-color', '#f0f0f0'); // Lighter header background
|
||||
document.documentElement.style.setProperty('--header-text-color', '#333'); // Dark text for contrast
|
||||
|
@ -73,7 +74,7 @@ export const applyWhiteTheme = () => {
|
|||
document.documentElement.style.setProperty('--font-family', "'Poppins', 'sans-serif'"); // Same font family
|
||||
document.documentElement.style.setProperty('--font-size', '16px'); // Same font size
|
||||
};
|
||||
|
||||
//#region BLACK
|
||||
export const applyBlackTheme = () => {
|
||||
document.documentElement.style.setProperty('--header-background-color', '#1a1a1a'); // Dark header background
|
||||
document.documentElement.style.setProperty('--header-text-color', '#ffffff'); // White text for header
|
||||
|
@ -112,7 +113,7 @@ export const applyBlackTheme = () => {
|
|||
document.documentElement.style.setProperty('--font-size', '16px'); // Font size
|
||||
|
||||
};
|
||||
|
||||
//#region CUSTOM
|
||||
export const applyCustomTheme = () => {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
const themeVariables = {
|
||||
|
@ -191,6 +192,8 @@ export const applyCustomTheme = () => {
|
|||
};
|
||||
}
|
||||
|
||||
//#region BASIC-CUSTOM
|
||||
|
||||
// TypeScript types for color parameters
|
||||
type Color = string;
|
||||
|
||||
|
@ -277,6 +280,7 @@ const lightenColor = (color: Color, percent: number): Color => {
|
|||
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
};
|
||||
|
||||
//#region APPLY-THEME
|
||||
// This is the new function that calls the appropriate theme application
|
||||
export const applyTheme = (theme: string, primary: string, secondary: string, accent: string, background: string, text: string) => {
|
||||
switch (theme) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue