Initial commit

This commit is contained in:
2024-06-15 12:55:41 +07:00
commit 9939364128
35 changed files with 7941 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
name: Publish Release
on:
push:
tags:
- "*"
env:
APP_NAME: ToDo App
jobs:
publish-tauri:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- platform: ubuntu-latest
output_dir_one: src-tauri/target/release/bundle/appimage/**.AppImage
output_dir_two: src-tauri/target/release/bundle/deb/**.deb
- platform: windows-latest
output_dir_one: src-tauri/target/release/bundle/msi/**.msi
output_dir_two: src-tauri/target/release/bundle/nsis/**.exe
#- platform.os: macos-latest
# args: --target aarch64-apple-darwin
# output_dir_one: src-tauri/target/aarch64-apple-darwin/release/bundle/macos/**.app.tar.gz
# output_dir_two: src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/**.dmg
#- platform.os: macos-latest
# args: --target x86_64-apple-darwin
# output_dir_one: src-tauri/target/x86_64-apple-darwin/release/bundle/macos/**.app
# output_dir_two: src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/**.dmg
runs-on: ${{ matrix.platform }}
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
- name: Checkout repository
uses: actions/checkout@v4
with:
lfs: true
token: ${{secrets.RELEASE_TOKEN}}
- name: install Rust stable
shell: bash
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Install Ubuntu dependencies
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt update
sudo apt-get install libgtk-3-dev -y
sudo apt install libwebkit2gtk-4.0-dev \
build-essential \
curl \
wget \
file \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev -y
- name: Install frontend
run: |
npm install
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{secrets.RELEASE_TOKEN}}
with:
releaseName: ${{ env.APP_NAME }} v__VERSION__
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
- name: Publish Tauri App
uses: akkuman/gitea-release-action@v1
with:
tag_name: ${{gitea.ref_name}}
server_url: 'http://192.168.0.80:3000'
prerelease: false
draft: false
files: >-
${{ matrix.output_dir_one }}
${{ matrix.output_dir_two }}
token: ${{secrets.RELEASE_TOKEN}}
+2
View File
@@ -0,0 +1,2 @@
# ToDo Tauri
ToDo приложение созданное на Tauri
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ToDo App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+2957
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
{
"name": "to-do-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"tauri": "tauri",
"tauri dev": "tauri dev",
"tauri build": "tauri build"
},
"dependencies": {
"@tauri-apps/api": "^1",
"@vueuse/core": "^10.10.0",
"vue": "^3.3.4",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@iconify/vue": "^4.1.2",
"@tauri-apps/cli": "^1",
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4",
"vite": "^5.0.0"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
+6
View File
@@ -0,0 +1,6 @@
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+4119
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "to-do-app"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1", features = [] }
[dependencies]
tauri = { version = "1", features = [ "window-show", "window-unminimize", "window-minimize", "window-maximize", "window-start-dragging", "window-hide", "window-close", "window-unmaximize", "shell-open"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
magic-crypt = "3.1.13"
rust-fuzzy-search = "0.1.1"
[features]
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
[profile.release]
lto = true # Enables link to optimizations
opt-level = "z" # Optimize for binary size
strip = true # Remove debug symbols
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+20
View File
@@ -0,0 +1,20 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[path="tasks_functions.rs"] mod tasks;
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
tasks::check_or_create_tasks_file,
tasks::get_tasks,
tasks::search_tasks,
tasks::add_task,
tasks::edit_task,
tasks::delete_task,
tasks::set_task_field
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+232
View File
@@ -0,0 +1,232 @@
use magic_crypt::{MagicCryptTrait, new_magic_crypt};
use std::fs::{read_to_string, write};
use std::path::PathBuf;
use std::path::Path;
use std::fs::File;
use serde_json::{Value, json};
use rust_fuzzy_search::fuzzy_search_best_n;
pub fn encrypt_n_save_file(path: PathBuf, content: String){
let mc = new_magic_crypt!("7J?VKYJib`=NIOpapW+zP8wD_#lPjP77Z)Q:4QUlH4`0rpMz7]>EIC%$RV-9*iW9AZ>Qk!OmAuH`8GzJ93xR4`KTl*-#fuCKzDfrPe&-A0?^Mz<F8IJ((oe+X,73iG", 256);
let mut encrypted: String = String::new();
for line in content.lines() {
encrypted = encrypted.to_owned() + mc.encrypt_str_to_base64(line).as_str() + "\n";
}
let res = write(path, encrypted);
match res {
Ok(_) => {},
Err(_) => println!("File save error!")
}
}
pub fn decrypt_file(path: PathBuf) -> String {
let mc = new_magic_crypt!("7J?VKYJib`=NIOpapW+zP8wD_#lPjP77Z)Q:4QUlH4`0rpMz7]>EIC%$RV-9*iW9AZ>Qk!OmAuH`8GzJ93xR4`KTl*-#fuCKzDfrPe&-A0?^Mz<F8IJ((oe+X,73iG", 256);
let mut decrypted: String = String::new();
let lines = read_to_string(path);
match lines {
Ok(lines) => {
for line in lines.lines() {
let dec_line = mc.decrypt_base64_to_string(&line);
match dec_line {
Ok(line) => {
decrypted = decrypted.to_owned() + line.as_str() + "\n";
return decrypted;
},
Err(_) => {
println!("Что-то пошло не так!");
}
}
}
return String::new();
},
Err(_) => {
println!("Что-то не так...");
return String::new();
}
}
}
#[tauri::command]
pub fn check_or_create_tasks_file(app_handle: tauri::AppHandle) {
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
path.push("ToDo");
path.push("tasks");
path.set_extension("enc");
let exist: bool = Path::new(&path).exists();
if exist{
let content = read_to_string(path);
return match content {
Ok(_) => {},
Err(_) => {
println!("Не удалось открыть конфигурационный файл!");
}
}
} else{
let path = Path::new(&path);
let prefix = path.parent().unwrap();
let file = std::fs::create_dir_all(prefix);
match file{
Ok(_) => {},
Err(err) => println!("Не удалось создать директории! \n{}", err)
}
let file = File::create(path);
match file{
Ok(_) => {
let data = String::from("{\
\"id\": 0,\
\"tasks\": {}\
}");
encrypt_n_save_file(path.into(), data);
},
Err(err) => println!("Не удалось создать файл! \n{}", err)
}
}
}
#[tauri::command]
pub fn get_tasks(app_handle: tauri::AppHandle) -> Value {
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
path.push("ToDo");
path.push("tasks");
path.set_extension("enc");
let content = decrypt_file(path);
let data: Value = serde_json::from_str(content.as_str()).unwrap();
let res = &data["tasks"];
return res.clone();
}
#[tauri::command]
pub fn search_tasks(app_handle: tauri::AppHandle, value: String) -> Value {
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
path.push("ToDo");
path.push("tasks");
path.set_extension("enc");
let content = decrypt_file(path);
let data: Value = serde_json::from_str(content.as_str()).unwrap();
let mut tasks = vec![];
let binding = data.clone();
for (_key, value) in binding["tasks"].as_object().unwrap() {
tasks.push(value["name"].as_str().unwrap().to_lowercase());
}
let tasks_str: Vec<&str> = tasks.iter().map(|s| &**s).collect();
let n : usize = 5;
let binding = value.to_lowercase();
let res : Vec<(&str, f32)> = fuzzy_search_best_n(&binding, &tasks_str, n);
let mut result = json!({});
let res1 = res.clone();
let binding1 = data.clone();
for (name, score) in res1 {
if score != 0.0 {
for (key, value) in binding1["tasks"].as_object().unwrap() {
if value.get("name").unwrap().as_str().unwrap().to_lowercase().as_str() == name {
result[key] = binding1["tasks"].get(key).unwrap().clone();
break;
}
}
}
}
return result;
}
#[tauri::command]
pub fn add_task(app_handle: tauri::AppHandle, date: String, time: String, name: String, description: String, priority: i32) {
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
path.push("ToDo");
path.push("tasks");
path.set_extension("enc");
let content = decrypt_file(path.clone());
let mut data: Value = serde_json::from_str(content.as_str()).unwrap();
let pending = data.clone();
let id = pending["id"].as_u64().unwrap();
let task = json!({
"id": id.to_string(),
"date": date,
"time": time,
"name": name,
"description": description,
"priority": priority,
"completed": false
});
data["id"] = (id + 1).into();
data["tasks"][id.to_string()] = task;
encrypt_n_save_file(path.into(), data.to_string());
}
#[tauri::command]
pub fn edit_task(app_handle: tauri::AppHandle, id_task: String, name: String, description: String) {
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
path.push("ToDo");
path.push("tasks");
path.set_extension("enc");
let content = decrypt_file(path.clone());
let mut data: Value = serde_json::from_str(content.as_str()).unwrap();
data["tasks"][id_task.clone()]["name"] = json!(name);
data["tasks"][id_task]["description"] = json!(description);
encrypt_n_save_file(path.into(), data.to_string());
}
#[tauri::command]
pub fn delete_task(app_handle: tauri::AppHandle, id_task: String) {
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
path.push("ToDo");
path.push("tasks");
path.set_extension("enc");
let content = decrypt_file(path.clone());
let mut data: Value = serde_json::from_str(content.as_str()).unwrap();
let tasks = data["tasks"].as_object_mut().unwrap();
let binding = tasks.clone();
for (key, value) in binding{
if id_task == value["id"] {
tasks.remove(&key);
break;
}
}
data["tasks"] = json!(tasks);
encrypt_n_save_file(path.into(), data.to_string());
}
#[tauri::command]
pub fn set_task_field(app_handle: tauri::AppHandle, id_task: String, field: String, value: String) {
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
path.push("ToDo");
path.push("tasks");
path.set_extension("enc");
let content = decrypt_file(path.clone());
let mut data: Value = serde_json::from_str(content.as_str()).unwrap();
data["tasks"][id_task][field] = json!(value);
encrypt_n_save_file(path.into(), data.to_string());
}
+55
View File
@@ -0,0 +1,55 @@
{
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:1420",
"distDir": "../dist"
},
"package": {
"productName": "to-do-app",
"version": "1.0.0"
},
"tauri": {
"allowlist": {
"all": false,
"shell": {
"all": false,
"open": true
},
"window": {
"all": false,
"close": true,
"hide": true,
"show": true,
"maximize": true,
"minimize": true,
"unmaximize": true,
"unminimize": true,
"startDragging": true
}
},
"windows": [
{
"title": "To Do List",
"width": 800,
"height": 600,
"decorations": false
}
],
"security": {
"csp": null
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.a-dot.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
}
+195
View File
@@ -0,0 +1,195 @@
<script setup>
import {onBeforeMount, ref} from "vue";
import {invoke} from "@tauri-apps/api";
import {Icon} from "@iconify/vue";
import EditModal from "./components/modals/EditModal.vue";
import CreateModal from "./components/modals/CreateModal.vue";
import { appWindow } from '@tauri-apps/api/window';
import {useDark, useToggle} from "@vueuse/core";
const isDark = useDark();
const toggleDark = useToggle(isDark);
let tasks = ref({});
let pending = ref(true);
let create_modal = ref(false);
let edit_modal = ref(false);
let search_text = ref("");
let to_edit_id = ref("");
let to_edit_name = ref("");
let to_edit_description = ref("");
onBeforeMount(async () => {
await invoke('check_or_create_tasks_file');
await get_tasks();
pending.value = false;
document
.getElementById('titlebar-minimize')
.addEventListener('click', () => appWindow.minimize())
document
.getElementById('titlebar-maximize')
.addEventListener('click', () => appWindow.toggleMaximize())
document
.getElementById('titlebar-close')
.addEventListener('click', () => appWindow.close())
});
async function get_tasks(){
await invoke('get_tasks').then((res) => {
tasks.value = res;
});
}
async function search_tasks(event){
if (event.target.value) {
await invoke('search_tasks', {
value: event.target.value
}).then((res) => {
tasks.value = res;
});
} else{
await get_tasks();
}
}
async function set_task_field(id_task, completed){
let field = "completed"
id_task = id_task.toString();
completed = completed.toString()
if (completed === "false"){
completed = "true"
} else{
completed = "false"
}
await invoke('set_task_field', {
idTask: id_task,
field: field,
value: completed
}).then(async () => {
tasks.value[id_task][field] = completed;
});
}
async function delete_task(id_task){
id_task = id_task.toString();
await invoke('delete_task', {
idTask: id_task
}).then(async () => {
await get_tasks();
});
}
async function edit_task(id_task, name, description){
to_edit_id = id_task;
to_edit_name.value = name;
to_edit_description.value = description;
edit_modal.value = true
}
</script>
<template>
<div data-tauri-drag-region class="titlebar h-[30px] select-none fixed flex justify-end top-0 right-0 left-0 bg-gray-100 dark:bg-zinc-800">
<div class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-minimize">
<Icon class="" icon="mdi:window-minimize" width="20" height="20"/>
</div>
<div class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-maximize">
<Icon class="" icon="mdi:window-maximize" width="20" height="20"/>
</div>
<div class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-close">
<Icon class="" icon="mdi:close" width="26" height="26"/>
</div>
</div>
<div v-if="pending"></div>
<div v-else>
<button class="absolute top-10 right-2" @click="toggleDark()">
<Icon v-if="!isDark" class="group-hover:invert text-yellow-400" icon="flowbite:sun-solid" width="38" height="38"/>
<Icon v-else class="group-hover:invert text-blue-400" icon="ri:moon-fill" width="38" height="38"/>
</button>
<div id="search" class="relative flex h-10 mx-[15vw] lg:mx-[25vw] mt-12">
<div class="!absolute right-1 top-1 z-10">
<button
v-if="search_text !== ''"
@click="search_text = ''; get_tasks()"
class="p-[1px] pt-[1px] pb-[2px] mr-0.5 rounded-lg group text-gray-400 hover:text-gray-500 transition-colors"
type="button"
data-ripple-light="true"
>
<Icon icon="ic:baseline-close" width="26" height="26"/>
</button>
<button
@click="create_modal = !create_modal"
class="p-[1px] pt-[1px] pb-[2px] border-green-400 border-2 rounded-lg group hover:bg-green-400 transition-colors"
type="button"
data-ripple-light="true"
>
<Icon class="group-hover:invert dark:invert" icon="ic:baseline-plus" width="26" height="26"/>
</button>
</div>
<input
v-model="search_text"
type="text"
class="peer h-full w-full rounded-lg border border-blue-gray-200 bg-transparent px-3 py-2.5 pr-20 font-sans text-sm font-normal text-blue-gray-700 dark:text-white dark:border-white outline outline-0 transition-all placeholder-shown:border placeholder-shown:border-blue-gray-200 placeholder-shown:border-t-blue-gray-200 focus:border-2 focus:border-green-400 focus:border-t-transparent focus:outline-0 disabled:border-0 disabled:bg-blue-gray-50"
:class="{'dark:border-t-[rgb(30,30,30)]': search_text.length}"
placeholder=" "
@input="search_tasks"
/>
<label class="text-gray-400 dark:text-gray-300 focus:text-black before:content[' '] after:content[' '] pointer-events-none absolute left-0 -top-1.5 flex h-full w-full select-none text-[11px] font-normal leading-tight text-blue-gray-400 transition-all before:pointer-events-none before:mt-[6.5px] before:mr-1 before:box-border before:block before:h-1.5 before:w-2.5 before:rounded-tl-md before:border-t before:border-l before:border-blue-gray-200 before:transition-all after:pointer-events-none after:mt-[6.5px] after:ml-1 after:box-border after:block after:h-1.5 after:w-2.5 after:flex-grow after:rounded-tr-lg after:border-t after:border-r after:border-blue-gray-200 after:transition-all peer-placeholder-shown:text-sm peer-placeholder-shown:leading-[3.75] peer-placeholder-shown:text-blue-gray-500 peer-placeholder-shown:before:border-transparent peer-placeholder-shown:after:border-transparent peer-focus:text-[11px] peer-focus:leading-tight peer-focus:text-green-400 peer-focus:before:border-t-2 peer-focus:before:border-l-2 peer-focus:before:!border-green-400 peer-focus:after:border-t-2 peer-focus:after:border-r-2 peer-focus:after:!border-green-400 peer-disabled:text-transparent peer-disabled:before:border-transparent peer-disabled:after:border-transparent peer-disabled:peer-placeholder-shown:text-blue-gray-500">
Поиск...
</label>
</div>
<div id="list" class="relative mx-[14vw] lg:mx-[22vw] mt-5">
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-500">
<li v-for="task in tasks" :id="task.id" class="flex justify-between gap-x-6 py-5 group/task">
<div @click="set_task_field(task.id, task.completed)" class="inline-flex items-center space-x-2 min-w-0 gap-x-2 group-hover/task:cursor-pointer">
<p class="text-sm flex-none border rounded-full" :class="[ task.completed === 'true' ? 'border-green-500 group-hover:border-green-400' : 'border-gray-300 group-hover:border-gray-500' ]"><Icon class="m-0.5" :class="[ task.completed === 'true' ? 'text-green-500 group-hover:text-green-400' : 'text-gray-50 group-hover:text-gray-500 dark:text-[rgb(30,30,30)]' ]" icon="material-symbols:check" width="20" height="20"/></p>
<div class="min-w-0 flex-auto">
<p class="truncate sm:text-[2vw] lg:text-[1.5vw] xl:text-[1vw] font-semibold leading-6 decoration-2" :class="[ task.completed === 'true' ? 'line-through text-gray-400' : 'text-gray-900 dark:text-gray-300' ]" >{{task.name}}</p>
<p class="truncate text-xs leading-5 text-gray-500 dark:text-gray-400">{{task.description}}</p>
</div>
</div>
<div class="opacity-0 shrink-0 group-hover/task:opacity-100 transition-all">
<div class="inline-flex items-center gap-x-1">
<p class="mt-0.5 text-xs text-gray-400 mr-2 text-center">{{task.date}}<br>{{task.time}}</p>
<button @click="edit_task(task.id, task.name, task.description)" class="group/button border-2 rounded-lg p-1.5 border-green-400/70 hover:bg-green-400 hover:border-green-400 transition-colors"><Icon class="group-hover/button:invert dark:invert" icon="mdi:pencil" width="24" height="24" style="color: black"/></button>
<button @click="delete_task(task.id)" class="group/button border-2 rounded-lg p-1.5 border-red-400/70 hover:bg-red-500 hover:border-red-500 transition-colors"><Icon class="group-hover/button:invert dark:invert" icon="ic:baseline-delete" width="24" height="24" style="color: black"/></button>
</div>
</div>
</li>
</ul>
</div>
<CreateModal v-if="create_modal" @close="create_modal = !create_modal"/>
<EditModal v-if="edit_modal" :task_id="to_edit_id" :task_name="to_edit_name" :task_description="to_edit_description" @close="edit_modal = !edit_modal"/>
</div>
</template>
<style>
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
background-color: rgb(249 250 251);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
:root.dark {
background-color: rgb(30, 30, 30);
}
</style>
+89
View File
@@ -0,0 +1,89 @@
<script setup>
import {invoke} from "@tauri-apps/api";
import {ref} from "vue";
let name = "";
let description = "";
let priority = "0";
let error = ref(" ");
async function create_task(){
if (name.replace(/ /g,'') === ""){
error.value = "Название задачи не может быть пустым!";
} else{
let nowDateTime = new Date();
let date = nowDateTime.getFullYear() + "." + ('0' + (nowDateTime.getMonth()+1)).slice(-2) + '.'
+ ('0' + nowDateTime.getDate()).slice(-2);
let time = ('0' + nowDateTime.getHours()).slice(-2) + ":" + ('0' + nowDateTime.getMinutes()).slice(-2);
await invoke('add_task', {
date: date,
time: time,
name: name,
description: description,
priority: priority
});
location.reload();
}
}
</script>
<template>
<div id="create-task-modal" tabindex="-1" aria-hidden="true" class="px-[5vw] md:px-[10vw] lg:px-[15vw] absolute overflow-y-auto overflow-x-hidden w-full top-20 lg:top-24 z-50">
<div class="relative p-4 max-h-full">
<!-- Modal content -->
<form class="relative bg-white rounded-lg shadow dark:bg-[rgb(50,50,50)]" @submit.prevent="create_task">
<!-- Modal header -->
<div class="flex items-center justify-between px-5 py-2 md:py-3 border-b rounded-t">
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-100">
Новая задача
</h3>
<button type="button" @click="$emit('close')" class="transition-colors text-gray-400 dark:text-gray-300 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center" data-modal-hide="default-modal">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
</svg>
</button>
</div>
<!-- Modal body -->
<div class="p-4 md:p-5 space-y-4">
<div class="space-y-6">
<div>
<label for="name" class="block text-sm font-medium leading-6 text-gray-900 dark:text-gray-200">Задача</label>
<div class="mt-2">
<input @input="error=' '" v-model="name" id="name" name="name" type="text" required class="px-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-inset sm:text-sm sm:leading-6" />
</div>
</div>
<div>
<div class="flex items-center justify-between">
<label for="description" class="block text-sm font-medium leading-6 text-gray-900 dark:text-gray-200">Описание</label>
</div>
<div class="mt-2">
<textarea v-model="description" id="description" name="description" class="px-2 py-1.5 block w-full rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-inset sm:text-sm sm:leading-6" />
</div>
</div>
<div>
<div class="flex items-center justify-between">
<label for="priority" class="block text-sm font-medium leading-6 text-gray-900 dark:text-gray-200">Приоритет</label>
</div>
<div class="mt-2">
<input v-model="priority" type="number" required min="0" max="10" step="1" id="priority" name="priority" class="px-2 py-1.5 block w-48 rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-inset sm:text-sm sm:leading-6" />
</div>
</div>
</div>
</div>
<!-- Modal footer -->
<div class="flex items-center py-3 px-5 md:p-3 border-t border-gray-200 rounded-b">
<input type="submit" class="text-white bg-green-500 hover:bg-green-600 transition-colors font-medium rounded-lg text-sm px-5 py-2.5 text-center" value="Создать">
<p class="ml-3 text-red-500">{{error}}</p>
</div>
</form>
</div>
</div>
</template>
<style scoped>
</style>
+69
View File
@@ -0,0 +1,69 @@
<script setup>
import {invoke} from "@tauri-apps/api";
const props = defineProps({
task_id: String,
task_name: String,
task_description: String
})
let name = props.task_name
let description = props.task_description
async function send_edited_task(){
await invoke('edit_task', {
idTask: props.task_id,
name: name,
description: description
}).then(() => location.reload());
}
</script>
<template>
<div id="create-task-modal" tabindex="-1" aria-hidden="true" class="px-[5vw] md:px-[10vw] lg:px-[15vw] absolute overflow-y-auto overflow-x-hidden w-full top-20 lg:top-24 z-50">
<div class="relative p-4 max-h-full">
<!-- Modal content -->
<form class="relative bg-white rounded-lg shadow dark:bg-[rgb(50,50,50)]" @submit.prevent="send_edited_task">
<!-- Modal header -->
<div class="flex items-center justify-between px-5 py-2 md:py-3 border-b rounded-t">
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-100">
Новая задача
</h3>
<button type="button" @click="$emit('close')" class="transition-colors text-gray-400 dark:text-gray-300 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center" data-modal-hide="default-modal">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
</svg>
</button>
</div>
<!-- Modal body -->
<div class="p-4 md:p-5 space-y-4">
<div class="space-y-6">
<div>
<label for="name" class="block text-sm font-medium leading-6 text-gray-900 dark:text-gray-200">Задача</label>
<div class="mt-2">
<input v-model="name" id="name" name="name" type="text" required class="px-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-inset sm:text-sm sm:leading-6" />
</div>
</div>
<div>
<div class="flex items-center justify-between">
<label for="description" class="block text-sm font-medium leading-6 text-gray-900 dark:text-gray-200">Описание</label>
</div>
<div class="mt-2">
<textarea v-model="description" id="description" name="description" class="px-2 py-1.5 block w-full rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-inset sm:text-sm sm:leading-6" />
</div>
</div>
</div>
</div>
<!-- Modal footer -->
<div class="flex items-center py-3 px-5 md:p-3 border-t border-gray-200 rounded-b">
<input type="submit" class="text-white bg-green-500 hover:bg-green-600 transition-colors font-medium rounded-lg text-sm px-5 py-2.5 text-center" value="Создать">
</div>
</form>
</div>
</div>
</template>
<style scoped>
</style>
+4
View File
@@ -0,0 +1,4 @@
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
+13
View File
@@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'selector',
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
+21
View File
@@ -0,0 +1,21 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [vue()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true,
watch: {
// 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"],
},
},
}));