refactor: update configuration files and styles, migrate to new CSS structure, and clean up imports
This commit is contained in:
parent
e8cd469ee4
commit
878ce97c54
25 changed files with 10850 additions and 7929 deletions
10853
package-lock.json
generated
10853
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
@ -4,7 +4,7 @@
|
|||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "ESBUILD_RUNNER=true vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
|
@ -12,7 +12,7 @@
|
|||
"dependencies": {
|
||||
"@babel/runtime": "7.26.0",
|
||||
"@hookform/resolvers": "3.9.1",
|
||||
"@tauri-apps/api": "2",
|
||||
"@tauri-apps/api": "2.4.0",
|
||||
"@tauri-apps/plugin-opener": "2",
|
||||
"@zitadel/react": "1.0.5",
|
||||
"autoprefixer": "10.4.17",
|
||||
|
@ -25,7 +25,8 @@
|
|||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hook-form": "7.53.2",
|
||||
"react-router-dom": "^7.4.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "7.4.1",
|
||||
"tailwindcss": "3.4.1",
|
||||
"uuid": "11.0.3",
|
||||
"zod": "3.21.4"
|
||||
|
@ -39,11 +40,13 @@
|
|||
"@types/react-test-renderer": "18.0.7",
|
||||
"@vitejs/plugin-react": "4.3.4",
|
||||
"copy-webpack-plugin": "12.0.2",
|
||||
"esbuild-runner": "^2.2.2",
|
||||
"jest": "29.2.1",
|
||||
"postcss": "8.4.23",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"tailwindcss": "3.1.8",
|
||||
"typescript": "5.6.2",
|
||||
"vite": "6.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
7635
pnpm-lock.yaml
generated
7635
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
],
|
||||
};
|
||||
// postcss.config.js
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
}
|
||||
}
|
130
src-rust/Cargo.lock
generated
130
src-rust/Cargo.lock
generated
|
@ -62,6 +62,24 @@ version = "1.0.97"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"rand 0.9.0",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"url",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
|
@ -750,6 +768,18 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.6.0",
|
||||
"libc",
|
||||
"objc2 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
@ -2055,6 +2085,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-opener",
|
||||
"tokio",
|
||||
]
|
||||
|
@ -2812,6 +2843,17 @@ dependencies = [
|
|||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
|
@ -2832,6 +2874,16 @@ dependencies = [
|
|||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
|
@ -2850,6 +2902,15 @@ dependencies = [
|
|||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
|
@ -2961,6 +3022,31 @@ dependencies = [
|
|||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d"
|
||||
dependencies = [
|
||||
"ashpd",
|
||||
"block2 0.6.0",
|
||||
"dispatch2",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"gtk-sys",
|
||||
"js-sys",
|
||||
"log",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
@ -3645,6 +3731,47 @@ dependencies = [
|
|||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b59fd750551b1066744ab956a1cd6b1ea3e1b3763b0b9153ac27a044d596426"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.12",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a1edf18000f02903a7c2e5997fb89aca455ecbc0acc15c6535afbb883be223"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.2.6"
|
||||
|
@ -3888,6 +4015,7 @@ dependencies = [
|
|||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
|
@ -5059,6 +5187,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_repr",
|
||||
"static_assertions",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.59.0",
|
||||
|
@ -5169,6 +5298,7 @@ dependencies = [
|
|||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"url",
|
||||
"winnow 0.7.4",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
|
|
|
@ -24,6 +24,7 @@ serde = { version = "1", features = ["derive"] }
|
|||
serde_json = "1"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
chrono = "0.4"
|
||||
tauri-plugin-dialog = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true # Enables Link-Time Optimization
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"frontendDist": "../dist"
|
||||
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { PersonSelector } from './selector/person-selector';
|
||||
import { ProjectorView } from './projector/projector-view';
|
||||
import { ChatWindow } from './chat/chat-window';
|
||||
import '../../styles/layout.css';
|
||||
import '../styles/layout.css';
|
||||
|
||||
export function ChatLayout() {
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { soundAssets } from '../../../public/sounds/manifest';
|
||||
import { cacheAssets } from '../lib/asset-loader';
|
||||
//import { cacheAssets } from '../lib/asset-loader';
|
||||
|
||||
export function SoundInitializer({ children }: { children: React.ReactNode }) {
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
@ -9,7 +9,7 @@ export function SoundInitializer({ children }: { children: React.ReactNode }) {
|
|||
useEffect(() => {
|
||||
const initializeSounds = async () => {
|
||||
try {
|
||||
await cacheAssets(Object.values(soundAssets));
|
||||
// await cacheAssets(Object.values(soundAssets));
|
||||
setIsReady(true);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to initialize sounds');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { core } from '@tauri-apps/api';
|
||||
import { User, ChatInstance } from '../types';
|
||||
|
||||
interface ChatContextType {
|
||||
|
@ -26,7 +26,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
|||
const initializeChat = async () => {
|
||||
try {
|
||||
const botId = window.location.pathname.split('/')[1] || 'default';
|
||||
const instanceData = await invoke('get_chat_instance', { botId });
|
||||
const instanceData = await core.invoke('get_chat_instance', { botId });
|
||||
setInstance(instanceData as ChatInstance);
|
||||
|
||||
// Initialize DirectLine or other chat service
|
||||
|
@ -45,7 +45,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
|||
|
||||
const sendActivity = async (activity: any) => {
|
||||
try {
|
||||
await invoke('send_chat_activity', {
|
||||
await core.invoke('send_chat_activity', {
|
||||
activity: {
|
||||
...activity,
|
||||
from: user,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { createContext, useContext, useCallback } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { core } from '@tauri-apps/api';
|
||||
|
||||
interface SoundContextType {
|
||||
playSound: (sound: string) => void;
|
||||
|
@ -14,7 +14,7 @@ export function SoundProvider({ children }: { children: React.ReactNode }) {
|
|||
const playSound = useCallback(async (sound: string) => {
|
||||
if (!enabled) return;
|
||||
try {
|
||||
await invoke('play_sound', { sound });
|
||||
await core.invoke('play_sound', { sound });
|
||||
} catch (error) {
|
||||
console.error('Failed to play sound:', error);
|
||||
}
|
||||
|
|
|
@ -1,50 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
const data = {
|
||||
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
datasets: [{
|
||||
label: 'Revenue',
|
||||
data: Array(12).fill(0).map(() => Math.random() * 100),
|
||||
backgroundColor: 'rgba(0, 119, 255, 0.5)',
|
||||
}]
|
||||
};
|
||||
|
||||
const options = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top' as const,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Monthly Revenue',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function Overview() {
|
||||
return (
|
||||
<div className="p-4 border rounded-lg">
|
||||
<Bar options={options} data={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { core } from '@tauri-apps/api';
|
||||
|
||||
interface FileItem {
|
||||
name: string;
|
||||
|
@ -20,7 +20,7 @@ export function FileBrowser({ path, onFileSelect }: FileBrowserProps) {
|
|||
const loadFiles = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await invoke<FileItem[]>('list_files', { path });
|
||||
const result = await core.invoke<FileItem[]>('list_files', { path });
|
||||
setFiles(result);
|
||||
} catch (error) {
|
||||
console.error('Error listing files:', error);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { core } from '@tauri-apps/api';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { api } from '@tauri-apps/api';
|
||||
|
||||
|
||||
|
||||
interface FileOperationsProps {
|
||||
currentPath: string;
|
||||
|
@ -19,7 +22,7 @@ export function FileOperations({ currentPath, onRefresh }: FileOperationsProps)
|
|||
|
||||
if (selected) {
|
||||
const filePath = Array.isArray(selected) ? selected[0] : selected;
|
||||
await invoke('upload_file', {
|
||||
await core.invoke('upload_file', {
|
||||
srcPath: filePath,
|
||||
destPath: currentPath,
|
||||
onProgress: (progress: number) => setUploadProgress(progress)
|
||||
|
@ -39,7 +42,7 @@ export function FileOperations({ currentPath, onRefresh }: FileOperationsProps)
|
|||
const folderName = prompt('Enter folder name:');
|
||||
if (folderName) {
|
||||
try {
|
||||
await invoke('create_folder', {
|
||||
await core.invoke('create_folder', {
|
||||
path: currentPath,
|
||||
name: folderName
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { core } from '@tauri-apps/api';
|
||||
|
||||
interface FileItem {
|
||||
name: string;
|
||||
|
@ -18,7 +18,7 @@ export function FileTree({ onSelect }: FileTreeProps) {
|
|||
useEffect(() => {
|
||||
const loadTree = async () => {
|
||||
try {
|
||||
const result = await invoke<FileItem[]>('list_files', { path: '' });
|
||||
const result = await core.invoke<FileItem[]>('list_files', { path: '' });
|
||||
setTree(result.filter(item => item.is_dir));
|
||||
} catch (error) {
|
||||
console.error('Error loading file tree:', error);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
import AuthenticationScreen from './authentication';
|
||||
import { Chat } from './chat';
|
||||
import MailPage from './mail';
|
||||
import {MailPage} from './mail';
|
||||
import DashboardPage from './dashboard';
|
||||
import TaskPage from './tasks';
|
||||
import TemplatesPage from './templates';
|
||||
import DriveScreen from './drive';
|
||||
import {DriveScreen} from './drive';
|
||||
import SyncPage from './sync/page';
|
||||
|
||||
const examples = [
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import "./App.css";
|
||||
|
||||
interface RcloneConfig {
|
||||
name: string;
|
||||
|
@ -66,7 +65,7 @@ function Page() {
|
|||
};
|
||||
|
||||
try {
|
||||
await invoke("save_config", { config });
|
||||
await core.invoke("save_config", { config });
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
status_text: "New sync saved!",
|
||||
|
@ -91,7 +90,7 @@ function Page() {
|
|||
};
|
||||
|
||||
try {
|
||||
await invoke("start_sync", { config });
|
||||
await core.invoke("start_sync", { config });
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
status_text: "Sync started!"
|
||||
|
@ -106,7 +105,7 @@ function Page() {
|
|||
|
||||
const stopSync = async () => {
|
||||
try {
|
||||
await invoke("stop_sync");
|
||||
await core.invoke("stop_sync");
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
status_text: "Sync stopped."
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
module.exports = {
|
||||
content: [
|
||||
"./src/index.tsx",
|
||||
"./constants/**/*.{js,jsx,ts,tsx}",
|
||||
"./components/**/*.{js,jsx,ts,tsx}"
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
// Add all files that use Tailwind classes
|
||||
"./src/**/*.css"
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
}
|
|
@ -2,11 +2,16 @@
|
|||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
|
||||
"types": [
|
||||
"@tauri-apps/api/tauri"
|
||||
],
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
|
@ -14,13 +19,18 @@
|
|||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -7,7 +7,11 @@ const host = process.env.TAURI_DEV_HOST;
|
|||
// https://vitejs.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react()],
|
||||
|
||||
css: {
|
||||
postcss: {
|
||||
config: false // Disable auto-loading of postcss.config.js
|
||||
} as any
|
||||
},
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent vite from obscuring rust errors
|
||||
|
@ -19,10 +23,10 @@ export default defineConfig(async () => ({
|
|||
host: host || false,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
// 3. tell vite to ignore watching `src-rust`
|
||||
|
|
Loading…
Add table
Reference in a new issue