svelte 5 components

This commit is contained in:
relaxed 2025-01-10 01:02:32 +05:00
parent 14f7b1231f
commit c6ef8ebe3a
27 changed files with 3826 additions and 3737 deletions

BIN
bun.lockb

Binary file not shown.

640
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,23 +13,23 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.1.1", "@tauri-apps/api": "2.2.0",
"@tauri-apps/plugin-opener": "^2.2.2", "@tauri-apps/plugin-fs": "2.0.4",
"@tauri-apps/plugin-fs": "~2.0.4", "@tauri-apps/plugin-opener": "2.2.3",
"@tauri-apps/plugin-shell": "^2.2.0", "@tauri-apps/plugin-shell": "2.2.0",
"@tauri-apps/plugin-sql": "~2.0.2" "@tauri-apps/plugin-sql": "2.0.2"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-static": "^3.0.8", "@sveltejs/adapter-static": "3.0.8",
"@sveltejs/kit": "^2.15.0", "@sveltejs/kit": "2.15.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "5.0.3",
"svelte": "^5.15.0", "svelte": "5.17.3",
"svelte-check": "^4.1.1", "svelte-check": "4.1.3",
"typescript": "~5.6.3", "typescript": "5.6.3",
"vite": "^6.0.5", "vite": "6.0.7",
"@tauri-apps/cli": "^2.1.0", "@tauri-apps/cli": "2.2.2",
"autoprefixer": "^10.4.20", "autoprefixer": "10.4.20",
"postcss": "^8.4.49", "postcss": "8.4.49",
"tailwindcss": "^3.4.17" "tailwindcss": "3.4.17"
} }
} }

View File

@ -1,6 +1,12 @@
<script> <script>
export let value; /**
export let is_required = true; * @typedef {Object} Props
* @property {any} value
* @property {boolean} [is_required]
*/
/** @type {Props} */
let { value = $bindable(), is_required = true } = $props();
</script> </script>
{#if is_required} {#if is_required}

View File

@ -1,6 +1,17 @@
<script> <script>
export let name = ""; /**
export let is_vertical = false; * @typedef {Object} Props
* @property {string} [name]
* @property {boolean} [is_vertical]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let {
name = $bindable(""),
is_vertical = false,
children
} = $props();
</script> </script>
<div <div
@ -9,5 +20,5 @@
flex justify-start" flex justify-start"
> >
<h1>{name}</h1> <h1>{name}</h1>
<slot /> {@render children?.()}
</div> </div>

View File

@ -2,25 +2,25 @@
import Select from "./Select.svelte"; import Select from "./Select.svelte";
import Button from "../ui/Button.svelte"; import Button from "../ui/Button.svelte";
import x from "$lib/components/ui/x.svg"; import x from "$lib/components/ui/x.svg";
/**
* @typedef {Object} Props
* @property {Array<number>} [value]
* @property {any} [on_change]
* @property {any} item_list_query
*/
/** @type {Array<number>} */ /** @type {Props} */
export let value = []; let { value = $bindable([]), on_change = () => {}, item_list_query } = $props();
export let on_change = () => {};
export let item_list_query;
</script> </script>
{#each value as _, item_index} {#each value as _, item_index}
<div class="w-full mb-2 space-x-2 flex justify-center items-center"> <div class="w-full mb-2 space-x-2 flex justify-center items-center">
<Select <Select bind:value={value[item_index]}
bind:value={value[item_index]} query={item_list_query} {on_change}>
query={item_list_query} {#snippet children({ vtype })}
{on_change} {vtype.name}
let:vtype {vtype.id}
> {/snippet}
{vtype.name}
{vtype.id}
</Select> </Select>
<Button <Button
type={"second"} type={"second"}
@ -29,8 +29,7 @@
value.splice(item_index, 1); value.splice(item_index, 1);
value = value; value = value;
await on_change(); await on_change();
}} }}>
>
<img src={x} class="w-[30px] invert" alt="x close img" /> <img src={x} class="w-[30px] invert" alt="x close img" />
</Button> </Button>
</div> </div>

View File

@ -1,10 +1,23 @@
<script> <script>
export let value = 0; /**
export let is_float = false; * @typedef {Object} Props
export let min = 0; * @property {number} [value]
export let max = 999999999; * @property {boolean} [is_float]
export let step = "0.1"; * @property {number} [min]
export let is_required = true; * @property {number} [max]
* @property {string} [step]
* @property {boolean} [is_required]
*/
/** @type {Props} */
let {
value = $bindable(),
is_float = false,
min = 0,
max = 999999999,
step = "0.1",
is_required = true
} = $props();
</script> </script>
{#if is_float && is_required} {#if is_float && is_required}
@ -15,7 +28,7 @@
{step} {step}
{value} {value}
required required
on:input={(e) => (value = parseFloat(e.target.value))} oninput={(e) => (value = parseFloat(e.target.value))}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
@ -28,7 +41,7 @@
{max} {max}
{step} {step}
{value} {value}
on:input={(e) => (value = parseFloat(e.target.value))} oninput={(e) => (value = parseFloat(e.target.value))}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
@ -43,7 +56,7 @@
{max} {max}
{value} {value}
required required
on:input={(e) => (value = Number(e.target.value))} oninput={(e) => (value = Number(e.target.value))}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
@ -55,7 +68,7 @@
{min} {min}
{max} {max}
{value} {value}
on:input={(e) => (value = Number(e.target.value))} oninput={(e) => (value = Number(e.target.value))}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black

View File

@ -72,8 +72,14 @@
}); });
const pattern = ".{22,}"; const pattern = ".{22,}";
export let phone = "+7 "; /**
export let is_required = true; * @typedef {Object} Props
* @property {string} [phone]
* @property {boolean} [is_required]
*/
/** @type {Props} */
let { phone = $bindable("+7 "), is_required = true } = $props();
</script> </script>
{#if is_required} {#if is_required}

View File

@ -1,32 +1,42 @@
<script> <script>
export let value; /**
* @typedef {Object} Props
* @property {any} value
* @property {any} query
* @property {boolean} [is_required]
* @property {any} [on_change]
* @property {import('svelte').Snippet<[any]>} [children]
*/
export let query; /** @type {Props} */
export let is_required = true; let {
value = $bindable(),
export let on_change = () => {}; query,
is_required = true,
on_change = () => {},
children
} = $props();
</script> </script>
{#if is_required} {#if is_required}
<select <select
required required
bind:value bind:value
on:change={on_change} onchange={on_change}
class="w-full h-min p-2 class="w-full h-min p-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
text-black dark:text-white text-black dark:text-white
appearance-none" appearance-none">
>
{#await query then vtypes} {#await query then vtypes}
{#each vtypes as vtype} {#each vtypes as vtype}
{#if vtype.id === value} {#if vtype.id === value}
<option selected value={vtype.id}> <option selected value={vtype.id}>
<slot {vtype} /> {@render children?.({ vtype, })}
</option> </option>
{:else} {:else}
<option value={vtype.id}> <option value={vtype.id}>
<slot {vtype} /> {@render children?.({ vtype, })}
</option> </option>
{/if} {/if}
{/each} {/each}
@ -39,17 +49,16 @@
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
text-black dark:text-white text-black dark:text-white
appearance-none" appearance-none">
>
{#await query then vtypes} {#await query then vtypes}
{#each vtypes as vtype} {#each vtypes as vtype}
{#if vtype.id === value} {#if vtype.id === value}
<option selected value={vtype.id}> <option selected value={vtype.id}>
<slot {vtype} /> {@render children?.({ vtype, })}
</option> </option>
{:else} {:else}
<option value={vtype.id}> <option value={vtype.id}>
<slot {vtype} /> {@render children?.({ vtype, })}
</option> </option>
{/if} {/if}
{/each} {/each}

View File

@ -1,9 +1,21 @@
<script> <script>
export let is_input = true; /**
export let value = ""; * @typedef {Object} Props
export let min_len = 0; * @property {boolean} [is_input]
export let max_len = 100; * @property {string} [value]
export let is_required = true; * @property {number} [min_len]
* @property {number} [max_len]
* @property {boolean} [is_required]
*/
/** @type {Props} */
let {
is_input = true,
value = $bindable(),
min_len = 0,
max_len = 100,
is_required = true
} = $props();
</script> </script>
{#if is_input && is_required} {#if is_input && is_required}
@ -13,7 +25,7 @@
maxlength={max_len} maxlength={max_len}
{value} {value}
required required
on:input={(e) => (value = e.target.value)} oninput={(e) => (value = e.target.value)}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
@ -25,7 +37,7 @@
minlength={min_len} minlength={min_len}
maxlength={max_len} maxlength={max_len}
{value} {value}
on:input={(e) => (value = e.target.value)} oninput={(e) => (value = e.target.value)}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
@ -37,7 +49,7 @@
<textarea <textarea
required required
{value} {value}
on:input={(e) => (value = e.target.value)} oninput={(e) => (value = e.target.value)}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black
@ -46,7 +58,7 @@
{:else if !is_input} {:else if !is_input}
<textarea <textarea
{value} {value}
on:input={(e) => (value = e.target.value)} oninput={(e) => (value = e.target.value)}
class="w-full p-2 mx-2 class="w-full p-2 mx-2
border-2 border-black dark:border-none border-2 border-black dark:border-none
bg-white dark:bg-black bg-white dark:bg-black

View File

@ -1,27 +1,40 @@
<script> <script>
import Button from "../ui/Button.svelte"; import Button from "../ui/Button.svelte";
import Search from "../ui/Search.svelte"; import Search from "../ui/Search.svelte";
export let is_searching = false;
/** @type {any} */
export let current_item;
export let search_query_result = "";
export let columns = ["id", "name"];
export let current_table;
export let on_add_click = () => {};
export let toggle_add_dialog = () => {};
export let joins = "";
/** @typedef SearchParam /** @typedef SearchParam
* @property {string} name * @property {string} name
* @property {boolean} is_number * @property {boolean} is_number
*/ */
/** @type {Array<SearchParam>}*/ /**
export let ex_params = []; * @typedef {Object} Props
* @property {boolean} [is_searching]
* @property {any} current_item
* @property {string} [search_query_result]
* @property {any} [columns]
* @property {any} current_table
* @property {any} [on_add_click]
* @property {any} [toggle_add_dialog]
* @property {string} [joins]
* @property {string} [search_names]
* @property {Array<SearchParam>} [ex_params]
* @property {import('svelte').Snippet<[any]>} [children]
*/
/** @type {Props} */
let {
is_searching = $bindable(false),
current_item = $bindable(),
search_query_result = $bindable(""),
columns = ["id", "name"],
current_table,
on_add_click = () => {},
toggle_add_dialog = () => {},
joins = "",
search_names = "*",
ex_params = [],
children
} = $props();
</script> </script>
<div class="m-2 flex space-x-2"> <div class="m-2 flex space-x-2">
@ -30,8 +43,7 @@
on_click={() => { on_click={() => {
on_add_click(); on_add_click();
toggle_add_dialog(); toggle_add_dialog();
}} }}>
>
Добавить Добавить
</Button> </Button>
<Search <Search
@ -46,4 +58,4 @@
></Search> ></Search>
</div> </div>
<slot {is_searching} {search_query_result} /> {@render children?.({ is_searching, search_query_result, })}

View File

@ -1,12 +1,22 @@
<script> <script>
import Button from "../ui/Button.svelte"; import Button from "../ui/Button.svelte";
/**
* @typedef {Object} Props
* @property {boolean} [is_dialog_item_add]
* @property {any} [on_add]
* @property {any} [on_save]
* @property {any} [on_delete]
* @property {any} [form]
*/
export let is_dialog_item_add = false; /** @type {Props} */
export let on_add = async () => {}; let {
export let on_save = async () => {}; is_dialog_item_add = $bindable(false),
export let on_delete = async () => {}; on_add = async () => {},
on_save = async () => {},
export let form = null; on_delete = async () => {},
form = null
} = $props();
</script> </script>
{#if is_dialog_item_add} {#if is_dialog_item_add}
@ -15,9 +25,8 @@
if (form === null || form.checkValidity()) { if (form === null || form.checkValidity()) {
await on_add(); await on_add();
} }
}} }}>
> Добавить
Добавить
</Button> </Button>
{:else} {:else}
<div class="flex w-max space-x-2"> <div class="flex w-max space-x-2">
@ -26,9 +35,8 @@
if (form === null || form.checkValidity()) { if (form === null || form.checkValidity()) {
await on_save(); await on_save();
} }
}} }}>
> Сохранить
Сохранить
</Button> </Button>
<Button on_click={on_delete}>Удалить</Button> <Button on_click={on_delete}>Удалить</Button>
</div> </div>

View File

@ -7,40 +7,55 @@
import TextInput from "../form/TextInput.svelte"; import TextInput from "../form/TextInput.svelte";
import Search from "../ui/Search.svelte"; import Search from "../ui/Search.svelte";
export let is_item_dialog_open = false; /**
export let is_dialog_item_add = false; * @typedef {Object} Props
* @property {boolean} [is_item_dialog_open]
* @property {boolean} [is_dialog_item_add]
* @property {string} [cur_dialog_name]
* @property {any} current_table
* @property {any} current_item
* @property {any} query
* @property {any} db
* @property {any} [toggle_add_dialog]
* @property {any} [open_item_edit]
* @property {any} [sql_insert_short]
* @property {any} [sql_update_short]
* @property {any} [sql_delete_short]
* @property {string} [second_col_name]
* @property {any} [columns]
* @property {any} [columns_display]
* @property {boolean} [is_searching] - export let is_searching = false;
* @property {import('svelte').Snippet} [second_value_input]
*/
export let cur_dialog_name = "__example__"; /** @type {Props} */
export let current_table; let {
is_item_dialog_open = $bindable(false),
/** @type {any} */ is_dialog_item_add = $bindable(false),
export let current_item; cur_dialog_name = "__example__",
current_table,
export let query; current_item = $bindable(),
query,
export let db; db,
toggle_add_dialog = () => {},
export let toggle_add_dialog = () => {}; open_item_edit = (/** @type {any} */ item) => {},
export let open_item_edit = (/** @type {any} */ item) => {}; sql_insert_short = async (
export let sql_insert_short = async (
/** @type {any} */ arg1, /** @type {any} */ arg1,
/** @type {any} */ arg2, /** @type {any} */ arg2,
) => {}; ) => {},
export let sql_update_short = async ( sql_update_short = async (
/** @type {any} */ arg1, /** @type {any} */ arg1,
/** @type {any} */ arg2, /** @type {any} */ arg2,
) => {}; ) => {},
export let sql_delete_short = () => {}; sql_delete_short = () => {},
second_col_name = "name",
columns = ["id", "name"],
columns_display = columns,
is_searching = $bindable(false),
second_value_input
} = $props();
export let second_col_name = "name"; let search_query_result = $state("");
export let columns = ["id", "name"];
export let columns_display = columns;
// export let is_searching = false;
export let is_searching = false;
let search_query_result = "";
</script> </script>
<Dialog <Dialog
@ -48,43 +63,38 @@
on_close={() => { on_close={() => {
is_dialog_item_add = false; is_dialog_item_add = false;
}} }}
name={cur_dialog_name} name={cur_dialog_name}>
let:form {#snippet children({ form })}
> {#if !is_dialog_item_add}
{#if !is_dialog_item_add} <h1>{columns_display[0]} {current_item.id}</h1>
<h1>{columns_display[0]} {current_item.id}</h1> {/if}
{/if}
<Field name={columns_display[1]}> <Field name={columns_display[1]}>
<slot name="second_value_input"> {#if second_value_input}{@render second_value_input()}{:else}
<TextInput bind:value={current_item[second_col_name]}></TextInput> <TextInput bind:value={current_item[second_col_name]}></TextInput>
</slot> {/if}
</Field> </Field>
<TableCudButtons <TableCudButtons {form} bind:is_dialog_item_add
{form} on_add={async () => {
bind:is_dialog_item_add await sql_insert_short(current_table, [second_col_name]);
on_add={async () => { }}
await sql_insert_short(current_table, [second_col_name]); on_save={async () => {
}} await sql_update_short(current_table, [second_col_name]);
on_save={async () => { }}
await sql_update_short(current_table, [second_col_name]); on_delete={sql_delete_short}
}} ></TableCudButtons>
on_delete={sql_delete_short} {/snippet}
></TableCudButtons>
</Dialog> </Dialog>
<div class="m-2 flex space-x-2"> <div class="m-2 flex space-x-2">
<Button <Button
is_visible={!is_searching} is_visible={!is_searching}
on_click={() => { on_click={() => {
current_item = { current_item = {id: 0};
id: 0,
};
current_item[second_col_name] = ""; current_item[second_col_name] = "";
toggle_add_dialog(); toggle_add_dialog();
}} }}>
>
Добавить Добавить
</Button> </Button>

View File

@ -1,20 +1,22 @@
<script> <script>
import Spinner from "../ui/Spinner.svelte";
import Table from "../table/Table.svelte"; import Table from "../table/Table.svelte";
import Tr from "../table/Tr.svelte"; import Tr from "../table/Tr.svelte";
import Atd from "../table/Atd.svelte"; import Atd from "../table/Atd.svelte";
/**
* @typedef {Object} Props
* @property {any} query
* @property {Array<string>} [columns]
* @property {Array<string>} [names]
*/
export let query; /** @type {Props} */
let { query, columns = [], names = [] } = $props();
/** @type {Array<string>} */
export let columns = [];
/** @type {Array<string>} */
export let names = [];
</script> </script>
<Table {columns} {query} let:item let:index> <Table {columns} {query} >
<Tr {index}> {#snippet children({ item, index })}
<Atd {item} {names}></Atd> <Tr {index}>
</Tr> <Atd {item} {names}></Atd>
</Tr>
{/snippet}
</Table> </Table>

View File

@ -1,14 +1,15 @@
<script> <script>
import Td from "./Td.svelte"; import Td from "./Td.svelte";
/** @type {any} */ /**
export let item = {}; * @typedef {Object} Props
* @property {any} [item]
* @property {Array<string>} [names]
*/
/** @type {Array<string>} */ /** @type {Props} */
export let names = []; let { item = {}, names = [] } = $props();
</script> </script>
{#each names as name} {#each names as name}
<Td> <Td>{item[name]}</Td>
{item[name]}
</Td>
{/each} {/each}

View File

@ -2,15 +2,30 @@
import Atd from "./Atd.svelte"; import Atd from "./Atd.svelte";
import Tr from "./Tr.svelte"; import Tr from "./Tr.svelte";
import Spinner from "../ui/Spinner.svelte"; import Spinner from "../ui/Spinner.svelte";
// export let data;
export let columns = ["__empty__"];
export let columns_display = columns;
export let class_name = "";
export let query;
export let open_item_edit = (/** @type {any} */ item) => {};
export let is_searching = false; /**
export let search_query; * @typedef {Object} Props
* @property {any} [columns] - export let data;
* @property {any} [columns_display]
* @property {string} [class_name]
* @property {any} query
* @property {any} [open_item_edit]
* @property {boolean} [is_searching]
* @property {any} search_query
* @property {import('svelte').Snippet<[any]>} [children]
*/
/** @type {Props} */
let {
columns = ["__empty__"],
columns_display = columns,
class_name = "",
query,
open_item_edit = (/** @type {any} */ item) => {},
is_searching = $bindable(),
search_query,
children
} = $props();
</script> </script>
{#if !is_searching} {#if !is_searching}
@ -21,9 +36,8 @@
<tbody> <tbody>
<tr <tr
class="w-full sticky class="w-full sticky
bg-black text-white" bg-black text-white"
style="inset-block-start: 0;" style="inset-block-start: 0;">
>
{#each columns_display as column_name} {#each columns_display as column_name}
<td class="w-max p-2 text-center"> <td class="w-max p-2 text-center">
{column_name} {column_name}
@ -31,17 +45,18 @@
{/each} {/each}
</tr> </tr>
{#each data as item, i} {#each data as item, i}
<slot {item} index={i}> {#if children}{@render children({ item, index: i, })}{:else}
<Tr on_click={() => open_item_edit(item)} index={i}> <Tr on_click={() => open_item_edit(item)} index={i}>
<Atd {item} names={columns}></Atd> <Atd {item} names={columns}></Atd>
</Tr> </Tr>
</slot> {/if}
{/each} {/each}
</tbody> </tbody>
</table> </table>
{:catch} {:catch}
<h1>Не удалось загрузить данные</h1> <h1>Не удалось загрузить данные</h1>
{/await} {/await}
{:else if search_query !== ""} {:else if search_query !== ""}
{#await search_query} {#await search_query}
<Spinner></Spinner> <Spinner></Spinner>
@ -50,9 +65,8 @@
<tbody> <tbody>
<tr <tr
class="w-full sticky class="w-full sticky
bg-black text-white" bg-black text-white"
style="inset-block-start: 0;" style="inset-block-start: 0;">
>
{#each columns_display as column_name} {#each columns_display as column_name}
<td class="w-max p-2 text-center"> <td class="w-max p-2 text-center">
{column_name} {column_name}
@ -60,11 +74,11 @@
{/each} {/each}
</tr> </tr>
{#each data as item, i} {#each data as item, i}
<slot {item} index={i}> {#if children}{@render children({ item, index: i, })}{:else}
<Tr on_click={() => open_item_edit(item)} index={i}> <Tr on_click={() => open_item_edit(item)} index={i}>
<Atd {item} names={columns}></Atd> <Atd {item} names={columns}></Atd>
</Tr> </Tr>
</slot> {/if}
{/each} {/each}
</tbody> </tbody>
</table> </table>

View File

@ -1,5 +1,13 @@
<td <script>
class="p-2 border-[1px] border-black overflow-hidden text-ellipsis break-words" /**
> * @typedef {Object} Props
<slot /> * @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props();
</script>
<td class="p-2 border-[1px] border-black overflow-hidden text-ellipsis break-words">
{@render children?.()}
</td> </td>

View File

@ -1,18 +1,19 @@
<script> <script>
export let index = 0; /**
export let on_click = () => {}; * @typedef {Object} Props
* @property {number} [index]
* @property {any} [on_click]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { index = 0, on_click = () => {}, children } = $props();
</script> </script>
<!-- class="bg-white dark:bg-gray-800" --> <tr onclick={on_click}
<!-- {index % 2 == 0 ? 'text-white' : 'text-black'} --> class="text-black dark:text-white
<!-- {index % 2 == 0 ? 'bg-[#444]' : 'bg-[#dcdcdc]'}" --> {index % 2 == 0
<tr ? 'bg-[#fff] dark:bg-gray-800'
on:click={on_click} : 'bg-[#e0e0e0] dark:bg-gray-700'}">
class=" {@render children?.()}
text-black dark:text-white
{index % 2 == 0
? 'bg-[#fff] dark:bg-gray-800'
: 'bg-[#e0e0e0] dark:bg-gray-700'}"
>
<slot />
</tr> </tr>

View File

@ -1,12 +1,23 @@
<script> <script>
export let is_visible = true; /**
export let on_click = () => {}; * @typedef {Object} Props
export let on_submit = () => {}; * @property {boolean} [is_visible]
* @property {any} [on_click]
* @property {any} [on_submit]
* @property {'primary' | 'regular' | 'second'} [type]
* @property {string} [class_name]
* @property {import('svelte').Snippet} [children]
*/
/** @type {'primary' | 'regular' | 'second'} */ /** @type {Props} */
export let type = "primary"; let {
is_visible = true,
export let class_name = ""; on_click = () => {},
on_submit = () => {},
type = "primary",
class_name = "",
children
} = $props();
let styles = { let styles = {
// bg-[#6664AF] dark:bg-[#6664AF] text-white text-2xl // bg-[#6664AF] dark:bg-[#6664AF] text-white text-2xl
@ -29,7 +40,7 @@ shadow-[-2px_-2px_#000] dark:shadow-[-2px_-2px_var(--b-border)] ${class_name}`,
</script> </script>
{#if is_visible} {#if is_visible}
<button type="submit" class={styles[type]} on:click={on_click}> <button type="submit" class={styles[type]} onclick={on_click}>
<slot /> {@render children?.()}
</button> </button>
{/if} {/if}

View File

@ -1,18 +1,31 @@
<script> <script>
// import ThemeSwitch from "./ThemeSwitch.svelte";
import x from "./x.svg"; import x from "./x.svg";
export let is_open = false; /**
export let is_name_editable = false; * @typedef {Object} Props
export let name = ""; * @property {boolean} [is_open]
export let class_name = ""; * @property {boolean} [is_name_editable]
export let on_close = () => { * @property {string} [name]
is_open = false; * @property {string} [class_name]
}; * @property {any} [on_close]
* @property {import('svelte').Snippet<[any]>} [children]
*/
let dialog; /** @type {Props} */
let {
is_open = $bindable(false),
is_name_editable = false,
name = $bindable(""),
class_name = "",
on_close = () => {
is_open = false;
},
children
} = $props();
let dialog = $state();
let local_open = false; let local_open = false;
function toggle_dialog(_) { function toggle_dialog(/** @type {any} */_) {
if (is_open && local_open) { if (is_open && local_open) {
dialog.showModal(); dialog.showModal();
document.addEventListener("keydown", (event) => { document.addEventListener("keydown", (event) => {
@ -36,9 +49,9 @@
} }
} }
$: _ = toggle_dialog(is_open); let _ = $derived(toggle_dialog(is_open));
let form; let form = $state();
</script> </script>
<dialog <dialog
@ -50,16 +63,14 @@
{is_open ? 'visible opacity-100' : 'invisible opacity-0'} {is_open ? 'visible opacity-100' : 'invisible opacity-0'}
backdrop-blur-sm backdrop-blur-sm
bg-[#000000aa] bg-[#000000aa]
transition-all duration-300" transition-all duration-300">
>
<div <div
class="w-[700px] h-[800px] mx-4 px-2 flex flex-col class="w-[700px] h-[800px] mx-4 px-2 flex flex-col
border-[var(--w-border)] dark:border-[var(--b-border)] border-2 rounded-xl border-[var(--w-border)] dark:border-[var(--b-border)] border-2 rounded-xl
bg-[var(--w-bg-second)] dark:bg-[var(--b-bg-second)] bg-[var(--w-bg-second)] dark:bg-[var(--b-bg-second)]
text-[var(--w-text)] dark:text-[var(--b-text)] text-2xl text-[var(--w-text)] dark:text-[var(--b-text)] text-2xl
overflow-hidden overflow-hidden
{class_name}" {class_name}">
>
<div class=" relative flex flex-initial justify-between items-center"> <div class=" relative flex flex-initial justify-between items-center">
{#if is_name_editable} {#if is_name_editable}
<input <input
@ -72,27 +83,16 @@
{name} {name}
</p> </p>
{/if} {/if}
<button <button class="size-[60px] m-2 pb-3">
on:click={() => { onclick={() => {
is_open = false; is_open = false;
on_close(); on_close();
}} }}
class="size-[60px] m-2 pb-3" <img src={x} class="size-[40px] dark:invert" alt="x close img"/>
>
<img
src={x}
class="size-[40px] dark:invert"
alt="x close img"
/>
</button> </button>
</div> </div>
<form <form bind:this={form} action="/" target="_self" class="overflow-y-auto">
bind:this={form} {@render children?.({ form, })}
action="/"
target="_self"
class="overflow-y-auto"
>
<slot {form}></slot>
</form> </form>
</div> </div>
</dialog> </dialog>

View File

@ -1,25 +1,28 @@
<script> <script>
import Button from "./Button.svelte"; import Button from "./Button.svelte";
let form; let form = $state();
let login = ""; let login = $state("");
let passoword = ""; let passoword = $state("");
export let is_logedin = false; /**
export let access_level = ""; * @typedef {Object} Props
export let check_login = async (l, p) => [false, ""]; * @property {boolean} [is_logedin]
let is_error = false; * @property {string} [access_level]
* @property {any} [check_login]
*/
/** @type {Props} */
let { is_logedin = $bindable(false), access_level = $bindable(""), check_login = async (l, p) => [false, ""] } = $props();
let is_error = $state(false);
</script> </script>
{#if !is_logedin} {#if !is_logedin}
<div <div
class="w-full h-full pr-4 overflow-y-auto class="w-full h-full pr-4 overflow-y-auto
flex justify-center items-center flex justify-center items-center
bg-[var(--w-bg-second)] dark:bg-[#111]" bg-[var(--w-bg-second)] dark:bg-[#111]">
> <form bind:this={form}
<form class="flex flex-col justify-center items-center">
bind:this={form}
class="flex flex-col justify-center items-center"
>
<h1 class="text-4xl">Вход</h1> <h1 class="text-4xl">Вход</h1>
<input <input
type="text" type="text"
@ -68,9 +71,7 @@
} }
} }
} }
}} }}>Войти
>
Войти
</Button> </Button>
</form> </form>
</div> </div>

View File

@ -1,35 +1,38 @@
<script> <script>
import ThemeSwitch from "./ThemeSwitch.svelte"; import ThemeSwitch from "./ThemeSwitch.svelte";
import Button from "./Button.svelte"; import Button from "./Button.svelte";
/**
* @typedef {Object} Props
* @property {'tables' | 'viewes'} [current_page]
* @property {any} [db_scheme]
* @property {boolean} [is_view_open]
* @property {any} [check_access]
* @property {boolean} [is_logedin]
* @property {boolean} [is_loaded]
* @property {any} [on_logout]
*/
// /** @type {'tables' | 'viewes'} */ /** @type {Props} */
export let current_page = ""; let {
current_page = $bindable(""),
/** @type {any} */ db_scheme = {},
export let db_scheme = {}; is_view_open = $bindable(false),
check_access = (name) => false,
export let is_view_open = false; is_logedin = false,
is_loaded = false,
export let check_access = (name) => false; on_logout = () => {}
} = $props();
export let is_logedin = false;
export let is_loaded = false;
export let on_logout = () => {};
</script> </script>
<div <div class="w-full h-[80px] mb-2 pr-4 text-xl flex-initial
class="w-full h-[80px] mb-2 pr-4 text-xl flex-initial flex justify-between items-center
flex justify-between items-center bg-[var(--w-bg-second)] dark:bg-[#111]">
bg-[var(--w-bg-second)] dark:bg-[#111]"
>
<div class="flex"> <div class="flex">
{#if is_logedin && is_loaded} {#if is_logedin && is_loaded}
{#each Object.entries(db_scheme) as page_name} {#each Object.entries(db_scheme) as page_name}
{#if check_access(page_name[0])} {#if check_access(page_name[0])}
<button <button
on:click={() => { onclick={() => {
is_view_open = false; is_view_open = false;
current_page = page_name[0]; current_page = page_name[0];
}} }}
@ -48,10 +51,7 @@
<div class="space-x-4 flex justify-center items-center"> <div class="space-x-4 flex justify-center items-center">
<ThemeSwitch></ThemeSwitch> <ThemeSwitch></ThemeSwitch>
{#if is_logedin && is_loaded} {#if is_logedin && is_loaded}
<Button <Button on_click={on_logout} class_name={"!text-black hover:!text-white dark:!text-white bg-[var(--w-red)] dark:bg-[var(--b-red)]"}>
on_click={on_logout}
class_name={"!text-black hover:!text-white dark:!text-white bg-[var(--w-red)] dark:bg-[var(--b-red)]"}
>
Выход Выход
</Button> </Button>
{/if} {/if}

View File

@ -26,7 +26,7 @@
} }
async function sql_delete_short() { async function sql_delete_short() {
await db.execute( await db?.execute(
`delete from ${current_table} where id = ${current_item.id}`, `delete from ${current_table} where id = ${current_item.id}`,
); );
is_item_dialog_open = false; is_item_dialog_open = false;
@ -56,15 +56,15 @@
.map((el) => format_type_insert(current_item[el])) .map((el) => format_type_insert(current_item[el]))
.filter((el) => el !== null) .filter((el) => el !== null)
.join(", ")}) ${ex ? ex : ""}`; .join(", ")}) ${ex ? ex : ""}`;
const res = await db.execute(q); const res = await db?.execute(q);
is_item_dialog_open = false; is_item_dialog_open = false;
toggle_update(); toggle_update();
return res; return res;
} }
/** /**
@param {string} name * @param {string} name
@param {any} an * @param {any} an
*/ */
function format_type_update(name, an) { function format_type_update(name, an) {
if (typeof an === "string") { if (typeof an === "string") {
return `${name} = "${an}"`; return `${name} = "${an}"`;
@ -81,57 +81,56 @@
* @param {Array<string>} columns * @param {Array<string>} columns
*/ */
async function sql_update_short(table_name, columns) { async function sql_update_short(table_name, columns) {
// let q = `UPDATE ${table_name} SET let q = `UPDATE ${table_name} SET ${columns
// ${columns.map((el) => (typeof current_item[el] === "string" ? `${el} = "${current_item[el]}"` : `${el} = ${current_item[el]}`)).join(", ")}
// WHERE id = ${current_item.id}`;
let q = `UPDATE ${table_name} SET
${columns
.map((el) => format_type_update(el, current_item[el])) .map((el) => format_type_update(el, current_item[el]))
.filter((el) => el !== null) .filter((el) => el !== null)
.join(", ")} .join(", ")}
WHERE id = ${current_item.id}`; WHERE id = ${current_item.id}`;
// console.log(q); await db?.execute(q);
await db.execute(q);
is_item_dialog_open = false; is_item_dialog_open = false;
toggle_update(); toggle_update();
} }
export let db_scheme; let current_table = $state("");
let current_view = $state("");
let is_view_open = $state(false);
/** @type {any} */ let is_logedin = $state(false);
export let current_item = {};
/** @type {Database} */ let access_level = $state("");
export let db;
let is_loaded = $state(false);
/**
* @typedef {Object} Props
* @property {any} db_scheme
* @property {any} [current_item]
* @property {Database | undefined} db
* @property {boolean} [is_item_dialog_open]
* @property {boolean} [is_dialog_item_add]
* @property {boolean} [is_login_requied]
* @property {any} [check_access]
* @property {any} [check_login]
* @property {any} [load_db]
* @property {import('svelte').Snippet<[any]>} [children]
*/
/** @type {Props} */
let {
db_scheme,
current_item = $bindable({}),
db,
is_item_dialog_open = $bindable(false),
is_dialog_item_add = $bindable(false),
is_login_requied = true,
check_access = $bindable((access_level, tab_name, name) => false),
check_login = $bindable(async (login, passowrd) => [false, ""]),
load_db = async () => {},
children
} = $props();
/** @type {string} */ /** @type {string} */
let current_page = Object.entries(db_scheme)[0][0]; let current_page = $state(Object.entries(db_scheme)[0][0]);
let current_table = "";
let current_view = "";
let is_view_open = false;
export let is_item_dialog_open = false;
export let is_dialog_item_add = false;
let is_logedin = false;
export let is_login_requied = true;
let access_level = "";
/**
* @param {string} access_level
* @param {string} tab_name
* @param {string | null} name
*/
export let check_access = (access_level, tab_name, name) => false;
/**
* @param {string} login
* @param {string} passowrd
*/
export let check_login = async (login, passowrd) => [false, ""];
let is_loaded = false;
export let load_db = async () => {};
onMount(async () => { onMount(async () => {
await load_db(); await load_db();
is_loaded = true; is_loaded = true;
@ -141,9 +140,10 @@ WHERE id = ${current_item.id}`;
check_login = async () => [true, ""]; check_login = async () => [true, ""];
} }
}); });
$: cur_dialog_name = is_dialog_item_add
let cur_dialog_name = $derived(is_dialog_item_add
? `Добавить в ${db_scheme[current_page].tables[current_table]}` ? `Добавить в ${db_scheme[current_page].tables[current_table]}`
: `Изменить ${db_scheme[current_page].tables[current_table]}`; : `Изменить ${db_scheme[current_page].tables[current_table]}`);
</script> </script>
<NavBar <NavBar
@ -175,28 +175,14 @@ WHERE id = ${current_item.id}`;
bind:current_view bind:current_view
bind:is_view_open bind:is_view_open
db_scheme={db_scheme[current_page]} db_scheme={db_scheme[current_page]}
check_access={(name) => check_access(access_level, current_page, name)} check_access={(/** @type {any} */name) => check_access(access_level, current_page, name)}
></SideMenu> ></SideMenu>
<div <div class="w-full pr-4 overflow-y-auto rounded-tl-xl
class="w-full pr-4 overflow-y-auto rounded-tl-xl bg-[var(--w-bg-second)] dark:bg-[#111]">
bg-[var(--w-bg-second)] dark:bg-[#111]"
>
<LoginForm bind:is_logedin bind:access_level {check_login}></LoginForm> <LoginForm bind:is_logedin bind:access_level {check_login}></LoginForm>
{#if is_loaded && is_logedin} {#if is_loaded && is_logedin}
<slot {@render children?.({ current_page, current_table, current_view, is_view_open, cur_dialog_name, toggle_update, toggle_add_dialog, open_item_edit, sql_update_short, sql_insert_short, sql_delete_short })}
{current_page}
{current_table}
{current_view}
{is_view_open}
{cur_dialog_name}
{toggle_update}
{toggle_add_dialog}
{open_item_edit}
{sql_update_short}
{sql_insert_short}
{sql_delete_short}
/>
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -1,29 +1,37 @@
<script> <script>
import Button from "./Button.svelte"; import Button from "./Button.svelte";
export let is_searching = false;
export let search_value = "";
export let search_query_result = "";
/** @type {any} */
export let current_item;
export let columns = ["id", "name"];
export let current_table = "";
export let joins = "";
/** @typedef SearchParam /** @typedef SearchParam
* @property {string} name * @property {string} name
* @property {boolean} is_number * @property {boolean} is_number
*/ */
/** @type {Array<SearchParam>}*/ /**
export let ex_params = []; * @typedef {Object} Props
* @property {boolean} [is_searching]
* @property {string} [search_value]
* @property {string} [search_query_result]
* @property {any} current_item
* @property {any} [columns]
* @property {string} [current_table]
* @property {string} [joins]
* @property {string} [search_names]
* @property {Array<SearchParam>} [ex_params]
* @property {boolean} [is_contains]
*/
export let is_contains = true; /** @type {Props} */
let {
is_searching = $bindable(false),
search_value = $bindable(""),
search_query_result = $bindable(""),
current_item = $bindable(),
columns = ["id", "name"],
current_table = "",
joins = "",
search_names = "*",
ex_params = [],
is_contains = $bindable(true)
} = $props();
/** /**
* @param {string} thing * @param {string} thing
@ -71,7 +79,7 @@
const search_splitted = search_purified.split(" "); const search_splitted = search_purified.split(" ");
columns.forEach((el) => { columns.forEach((/** @type {any} */ el) => {
const param = make_sql_param(el, current_item[el], search_purified); const param = make_sql_param(el, current_item[el], search_purified);
if (param !== null) { if (param !== null) {
@ -88,21 +96,13 @@
if (ex_params.length !== 0) { if (ex_params.length !== 0) {
ex_params.forEach((el) => { ex_params.forEach((el) => {
const param = make_sql_param( const param = make_sql_param(el.name, el.is_number ? null : "", search_purified,);
el.name,
el.is_number ? null : "",
search_purified,
);
if (param !== null) { if (param !== null) {
params.push(param); params.push(param);
} }
search_splitted.forEach((sp) => { search_splitted.forEach((sp) => {
const param = make_sql_param( const param = make_sql_param(el.name,el.is_number ? null : "",sp);
el.name,
el.is_number ? null : "",
sp,
);
if (param !== null && !params.includes(param)) { if (param !== null && !params.includes(param)) {
params.push(param); params.push(param);
} }
@ -113,21 +113,17 @@
if (params.length === 0) { if (params.length === 0) {
search_query_result = ""; search_query_result = "";
} else { } else {
search_query_result = `SELECT * FROM ${current_table} ${joins} WHERE ${params.join(" OR ")}`; search_query_result = `SELECT ${search_names} FROM ${current_table} ${joins} WHERE ${params.join(" OR ")}`;
} }
// console.log(`SELECT * FROM ${current_table} \n${joins} \nWHERE \n${params.join(" OR ")}`);
} }
</script> </script>
<div class=""> <div class="">
<div class=" flex justify-start items-center"> <div class=" flex justify-start items-center">
<Button <Button type={is_searching ? "second" : "primary"}
on_click={() => { on_click={() => {
is_searching = !is_searching; is_searching = !is_searching;
}} }}>
type={is_searching ? "second" : "primary"}
>
{#if is_searching} {#if is_searching}
{"<-"} {"<-"}
{:else} {:else}
@ -136,15 +132,12 @@
</Button> </Button>
{#if is_searching} {#if is_searching}
<input <input type="text"
type="text"
bind:value={search_value} bind:value={search_value}
on:input={on_input} oninput={on_input}
class=" w-full mx-4 p-2 text-xl border-black border-2 class=" w-full mx-4 p-2 text-xl border-black border-2
bg-[#fff] dark:bg-gray-800 bg-[#fff] dark:bg-gray-800
dark:text-white dark:text-white"/>
"
/>
{/if} {/if}
</div> </div>
{#if is_searching} {#if is_searching}
@ -153,11 +146,11 @@
type="checkbox" type="checkbox"
class="size-7 mr-4" class="size-7 mr-4"
checked checked
on:click={() => { onclick={() => {
is_contains = !is_contains; is_contains = !is_contains;
on_input(""); on_input("");
}} }}
/> похоже? />похоже?
</div> </div>
{/if} {/if}
</div> </div>

View File

@ -1,34 +1,41 @@
<script> <script>
export let current_table = ""; /**
export let current_view = ""; * @typedef {Object} Props
export let is_view_open = false; * @property {string} [current_table]
* @property {string} [current_view]
* @property {boolean} [is_view_open]
* @property {any} [db_scheme]
* @property {any} [check_access]
* @property {boolean} [is_logedin]
* @property {boolean} [is_loaded]
*/
export let db_scheme = { tables: {}, viewes: {} }; /** @type {Props} */
let {
export let check_access = (name) => false; current_table = $bindable(""),
current_view = $bindable(""),
export let is_logedin = false; is_view_open = $bindable(false),
db_scheme = { tables: {}, viewes: {} },
export let is_loaded = false; check_access = (/** @type {any} */ name) => false,
is_logedin = false,
is_loaded = false
} = $props();
</script> </script>
<div <div class="w-[240px] h-full text-xl p-3 mr-2 flex-initial rounded-tr-xl
class="w-[240px] h-full text-xl p-3 mr-2 flex-initial rounded-tr-xl bg-[var(--w-bg-second)] dark:bg-[var(--b-bg-second)]">
bg-[var(--w-bg-second)] dark:bg-[var(--b-bg-second)]"
>
{#if is_logedin && is_loaded} {#if is_logedin && is_loaded}
{#if Object.entries(db_scheme.viewes).length !== 0 && Object.entries(db_scheme.viewes).some( (el) => check_access(el[0]), )} {#if Object.entries(db_scheme.viewes).length !== 0 && Object.entries(db_scheme.viewes).some( (el) => check_access(el[0]), )}
<button <button
on:click={() => { onclick={() => {
is_view_open = !is_view_open; is_view_open = !is_view_open;
}} }}
class="py-2 my-1 w-full class="py-2 my-1 w-full
{is_view_open {is_view_open
? 'bg-[#d8efff] dark:bg-[#114040]' ? 'bg-[#d8efff] dark:bg-[#114040]'
: 'bg-[var(--w-button-bg)] dark:bg-black text-[var(--w-accent-text)] dark:text-[var(--b-accent-text)]'} : 'bg-[var(--w-button-bg)] dark:bg-black text-[var(--w-accent-text)] dark:text-[var(--b-accent-text)]'}
border-2 border-[var(--w-border)] dark:border-[var(--b-border)]" border-2 border-[var(--w-border)] dark:border-[var(--b-border)]">
> Отчеты
Отчеты
</button> </button>
<div class="my-3 border-b-4 rounded-xl border-cyan-500"></div> <div class="my-3 border-b-4 rounded-xl border-cyan-500"></div>
@ -36,11 +43,8 @@
{#each !is_view_open ? Object.entries(db_scheme.tables) : Object.entries(db_scheme.viewes) as item_name} {#each !is_view_open ? Object.entries(db_scheme.tables) : Object.entries(db_scheme.viewes) as item_name}
{#if check_access(item_name[0])} {#if check_access(item_name[0])}
<button <button
on:click={() => { onclick={() => {
if ( if (`${current_table}` === item_name[0] || current_view === item_name[0]) {
`${current_table}` === item_name[0] ||
current_view === item_name[0]
) {
current_table = ""; current_table = "";
current_view = ""; current_view = "";
} }

View File

@ -1,10 +1,10 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
let is_resize = false; let is_resize = $state(false);
let is_transparent = false; let is_transparent = $state(false);
let is_dark = true; let is_dark = $state(true);
let is_update = false; let is_update = $state(false);
function handleSwitchDarkMode() { function handleSwitchDarkMode() {
setTimeout(() => { setTimeout(() => {
@ -40,13 +40,13 @@
{#if !is_update} {#if !is_update}
<div class="min-w-[40px] min-h-[40px] flex justify-center items-center"> <div class="min-w-[40px] min-h-[40px] flex justify-center items-center">
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div <div
class="group min-h-[50px] min-w-[50px] rounded-full flex justify-center items-center class="group min-h-[50px] min-w-[50px] rounded-full flex justify-center items-center
{is_dark ? 'bg-[var(--w-bg)]' : 'bg-[var(--b-bg)]'} {is_dark ? 'bg-[var(--w-bg)]' : 'bg-[var(--b-bg)]'}
" "
on:click={handleSwitchDarkMode} onclick={handleSwitchDarkMode}
> >
<div <div
class=" rounded-full class=" rounded-full

View File

@ -4,15 +4,10 @@ import { sveltekit } from "@sveltejs/kit/vite";
// @ts-expect-error process is a nodejs global // @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST; const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
plugins: [sveltekit()], plugins: [sveltekit()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
clearScreen: false, clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: { server: {
port: 1420, port: 1420,
strictPort: true, strictPort: true,
@ -25,7 +20,6 @@ export default defineConfig(async () => ({
} }
: undefined, : undefined,
watch: { watch: {
// 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"], ignored: ["**/src-tauri/**"],
}, },
}, },