Compare commits

...

10 commits

67 changed files with 1882 additions and 1315 deletions

278
= filter.clone(); Normal file
View file

@ -0,0 +1,278 @@
.logo.vanilla:hover {
 filter: drop-shadow(0 0 2em #ffe21c);
}
:root {
 --main-bg-color: #2f2f2f;
 --highlight-color: #3f3f3f;
 --bright-highlight-color: #4f4f4f;
 --text-color: #f6f6f6;
 --accent-color: #5f5f5f;
 --sidebar-width: 3em;
 --filetree-width: 10em;
 font-family: Avenir, Helvetica, Arial, sans-serif;
 font-size: 30px;
 line-height: 24px;
 color: #f6f6f6;
 background-color: var(--main-bg-color);
 font-synthesis: none;
 text-rendering: optimizeLegibility;
 -webkit-font-smoothing: antialiased;
 -moz-osx-font-smoothing: grayscale;
 -webkit-text-size-adjust: 100%;
}
* {
 box-sizing: border-box;
 padding: 0;
 margin: 0;
}
.container {
 height: 100%;
 max-width: 100%;
 margin: 0;
 padding-top: 1vh;
 display: flex;
 flex-direction: row;
 text-align: left;
 align-items: stretch;
}
#main_editor {
 width: 100%;
 font-size: 16px;
 line-height: 18px;
 display: flex;
 height: 100%;
 width: 100%;
 color: var(--text-color);
 background-color: var(--main-bg-color);
 float: right;
 overflow: hidden;
}
.col {
 flex: 0 0 49%;
 width: 50%;
 padding: 0.5em;
 /* margin: .1em; */
 margin-left: 0.1em;
 border: solid;
 border-color: var(--highlight-color);
 /* border-radius: .5em; */
}
#markdown_input {
 grid-column: 1;
 grid-row: 1;
 text-align: left;
 color: var(--text-color);
 background: var(--main-bg-color);
 overflow-x: wrap;
 overflow-y: scroll;
}
#rendered_markdown {
 grid-column: 2;
 grid-row: 1;
 text-align: left;
 color: var(--text-color);
 background-color: var(--main-bg-color);
 img {
 width: 100%;
 }
 overflow-x: wrap;
 overflow-y: scroll;
}
h1 {
 text-align: left;
 line-height: 18px;
 margin-top: 0.067em;
 margin-bottom: 0.067em;
 color: red;
}
li {
 /* line-height: 0.1em; */
 margin: 0;
 margin-left: 1em;
 padding: 0;
}
.sidebar_button {
 width: 100%;
 background-color: var(--main-bg-color);
 border: none;
}
.sidebar_icon {
 width: 100%;
}
.top-bar {
 display: flex;
 flex-direction: row;
}
.topbar_button {
 height: 2em;
 background-color: transparent;
 border: none;
}
.topbar_icon {
 height: 100%;
 background-color: transparent;
}
@media (prefers-color-scheme: dark) {
 :root {
 color: #f6f6f6;
 background-color: var(--main-bg-color);
 }
}
.main {
 display: flex;
 height: 95vh;
 flex-direction: column;
}
#sidebar {
 visibility: visible;
 width: var(--sidebar-width);
 transition: all 0.1s ease-out;
 float: left;
}
.filetree {
 float: left;
 border: solid;
 border-color: var(--highlight-color);
 width: var(--filetree-width);
 overflow-x: scroll;
 /* overflow-y: scroll; */
 overflow-y: hidden;
 text-overflow: ellipsis;
 resize: horizontal;
}
.filetree-directory-button,
.filetree-file-button,
.file-search-button {
 background-color: transparent;
 border: none;
 width: 100%;
 height: inherit;
 text-align: left;
 transition: all 0.01s ease-out;
 white-space: nowrap;
 /* overflow-y: visible; */
 overflow-y: hidden;
 overflow: hidden;
 color: var(--text-color);
 text-overflow: ellipsis;
}
.filetree-node {
 margin-left: 1em;
 overflow-y: hidden;
}
.filetree-icon {
 height: 1em;
 background-color: transparent;
 margin-right: 5px;
}
.filetree_expand {
 background-color: transparent;
 border: none;
}
.filetree-file-button:hover,
.filetree-directory-button:hover {
 background-color: var(--highlight-color);
 /* Green */
}
dialog {
 position: absolute;
 top: 50%;
 left: 50%;
 transform: translate(-50%, -50%) !important;
 background-color: var(--highlight-color);
 border: none;
 border-radius: 10px;
 box-shadow:
 0 0 #0000,
 0 0 #0000,
 0 25px 50px -12px rgba(0, 0, 0, 0.25);
 width: 50%;
 height: 20%;
 margin: auto;
 /* display: flex; */
 /* flex-direction: column; */
 overflow: hidden;
 color: var(--text-color);
 font-size: 0.5cm;
}
.row {
 display: flex;
 justify-content: space-between;
}
.button {
 background-color: var(--accent-color);
 /* Green */
 color: white;
 border: solid;
 border-color: transparent;
 padding: 5px 3px;
 text-align: center;
 text-decoration: none;
 display: inline-block;
 font-size: 16px;
}
.button:hover {
 border: solid;
 border-color: var(--bright-highlight-color);
}
.button:active {
 border: solid;
 border-color: white;
}
.input-field {
 border: solid;
 border-color: var(--accent-color);
 color: var(--text-color);
}
#knowledgebase-button {
 width: 19%;
}
#knowledgebase-path {
 width: 80%;
}
#file-search-dialog-input {
 border-top-right-radius: 10px;
 border-top-left-radius: 10px;
}
#file-search-results {
 overflow: scroll;
 grid-column: 1;
 grid-row: 1;
}

BIN
app-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

96
flake.lock generated Normal file
View file

@ -0,0 +1,96 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1746904237,
"narHash": "sha256-3e+AVBczosP5dCLQmMoMEogM57gmZ2qrVSrmq9aResQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d89fc19e405cb2d55ce7cc114356846a0ee5e956",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1747017456,
"narHash": "sha256-C/U12fcO+HEF071b5mK65lt4XtAIZyJSSJAg9hdlvTk=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "5b07506ae89b025b14de91f697eba23b48654c52",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

130
flake.nix Normal file
View file

@ -0,0 +1,130 @@
# { pkgs ? import <nixpkgs> {} }:
# let
# fenix = import "${
# fetchTarball "https://github.com/nix-community/fenix/archive/main.tar.gz"
# }/packages.nix";
# in
# pkgs.mkShell {
# shellHook = ''export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath [
# pkgs.alsaLib
# pkgs.udev
# pkgs.vulkan-loader
# ]}"'';
# buildInputs = with pkgs; [
# (
# with fenix;
# combine (
# with default; [
# cargo
# clippy-preview
# latest.rust-src
# rust-analyzer
# rust-std
# rustc
# rustfmt-preview
# ]
# )
# )
# cargo-edit
# cargo-watch
# lld
# clang
# # # bevy-specific deps (from https://github.com/bevyengine/bevy/blob/main/docs/linux_dependencies.md)
# pkgconfig
# udev
# alsaLib
# lutris
# x11
# xorg.libXcursor
# xorg.libXrandr
# xorg.libXi
# vulkan-tools
# vulkan-headers
# vulkan-loader
# vulkan-validation-layers
# ];
# }
{
description = "Minimal Rust development environment for Bevy project";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs { inherit system overlays; };
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" "clippy" ];
};
in {
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
pkg-config
gobject-introspection
cargo
cargo-tauri
nodejs
];
buildInputs = with pkgs; [
rustToolchain
clang
llvmPackages_latest.bintools
udev
alsa-lib
vulkan-loader
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
libxkbcommon
wayland
glibc.dev
glib.dev
dioxus-cli
at-spi2-atk
atkmm
cairo
gdk-pixbuf
glib
gtk3
harfbuzz
librsvg
libsoup_3
pango
webkitgtk_4_1
openssl
xdotool
];
shellHook = ''
export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
export LD_LIBRARY_PATH=${
pkgs.lib.makeLibraryPath [
pkgs.vulkan-loader
pkgs.libxkbcommon
pkgs.wayland
pkgs.alsa-lib
pkgs.udev
]
}:$LD_LIBRARY_PATH
export LIBCLANG_PATH="${pkgs.llvmPackages_latest.libclang.lib}/lib"
export BINDGEN_EXTRA_CLANG_ARGS="-I${pkgs.glibc.dev}/include -I${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include -I${pkgs.glib.dev}/include/glib-2.0 -I${pkgs.glib.out}/lib/glib-2.0/include/"
export RUSTFLAGS="-C link-arg=-fuse-ld=lld"
echo "Bevy development environment loaded!"
'';
};
});
}

10
package-lock.json generated
View file

@ -6,6 +6,7 @@
"": { "": {
"dependencies": { "dependencies": {
"@tauri-apps/plugin-dialog": "^2.2.0", "@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-log": "^2.4.0",
"@tauri-apps/plugin-sql": "^2.2.0" "@tauri-apps/plugin-sql": "^2.2.0"
} }
}, },
@ -28,6 +29,15 @@
"@tauri-apps/api": "^2.0.0" "@tauri-apps/api": "^2.0.0"
} }
}, },
"node_modules/@tauri-apps/plugin-log": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-log/-/plugin-log-2.4.0.tgz",
"integrity": "sha512-j7yrDtLNmayCBOO2esl3aZv9jSXy2an8MDLry3Ys9ZXerwUg35n1Y2uD8HoCR+8Ng/EUgx215+qOUfJasjYrHw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.0.0"
}
},
"node_modules/@tauri-apps/plugin-sql": { "node_modules/@tauri-apps/plugin-sql": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-sql/-/plugin-sql-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-sql/-/plugin-sql-2.2.0.tgz",

View file

@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@tauri-apps/plugin-dialog": "^2.2.0", "@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-log": "^2.4.0",
"@tauri-apps/plugin-sql": "^2.2.0" "@tauri-apps/plugin-sql": "^2.2.0"
} }
} }

View file

@ -1,3 +1 @@
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

2258
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,3 @@
cargo-features = ["codegen-backend"]
[package] [package]
name = "apographe" name = "apographe"
version = "0.1.0" version = "0.1.0"
@ -20,20 +19,23 @@ tauri-build = { version = "2", features = [] }
[dependencies] [dependencies]
tauri = { version = "2", features = ["protocol-asset", "unstable"] } tauri = { version = "2", features = ["protocol-asset", "unstable"] }
tauri-plugin-shell = "2" tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] } tauri-plugin-dialog = "2"
serde_json = "1"
comrak = "0.29.0"
tauri-plugin-fs = "2" tauri-plugin-fs = "2"
tauri-plugin-log = "2" tauri-plugin-log = "2"
shellexpand = "3.1.0"
html_tag = "0.1.3" serde = { version = "1", features = ["derive"] }
serde_json = "1"
comrak = { version = "0.39.0", features = ["bon", "cli"], default-features = false }
shellexpand = "3"
html_tag = "0.1"
fuzzy-matcher = "0.3" fuzzy-matcher = "0.3"
sqlx = { version = "0.6.2", features = ["runtime-tokio-native-tls", "sqlite"] } sqlx = { version = "0.8", features = ["runtime-tokio-native-tls", "sqlite"] }
confy = "0.6.1" confy = "0.6"
tauri-plugin-dialog = "2" diff-match-patch-rs = "0.5"
diff-match-patch-rs = "0.4.1"
rand = "0.9" rand = "0.9"
futures = "0.3"
log = "0.4.27"
# [profile.dev] # [profile.dev]
# codegen-backend = "cranelift" # codegen-backend = "cranelift"

View file

@ -20,6 +20,7 @@
"fs:allow-home-read-recursive", "fs:allow-home-read-recursive",
"fs:scope-home-recursive", "fs:scope-home-recursive",
"fs:read-all", "fs:read-all",
"dialog:default" "dialog:default",
"log:default"
] ]
} }

View file

@ -39,8 +39,6 @@ android {
storePassword = keystoreProperties["password"] as String storePassword = keystoreProperties["password"] as String
} }
} }
buildTypes { buildTypes {
getByName("debug") { getByName("debug") {
manifestPlaceholders["usesCleartextTraffic"] = "true" manifestPlaceholders["usesCleartextTraffic"] = "true"
@ -54,7 +52,6 @@ android {
} }
} }
getByName("release") { getByName("release") {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true isMinifyEnabled = true
proguardFiles( proguardFiles(
*fileTree(".") { include("**/*.pro") } *fileTree(".") { include("**/*.pro") }
@ -62,6 +59,9 @@ android {
.toList().toTypedArray() .toList().toTypedArray()
) )
} }
getByName("release") {
signingConfig = signingConfigs.getByName("release")
}
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"

View file

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- AndroidTV support --> <!-- AndroidTV support -->
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -35,11 +35,11 @@ impl Default for CoreCFG {
pub fn load_config(path: Option<PathBuf>) -> Config { pub fn load_config(path: Option<PathBuf>) -> Config {
match path { match path {
Some(p) => confy::load_path(p).expect("Configuration could not be loaded"), Some(p) => confy::load_path(p).expect("Configuration could not be loaded"),
None => confy::load("apographe", Some("config")) None => confy::load("apograph", Some("config"))
.ok() .ok()
.unwrap_or_else(|| { .unwrap_or_else(|| {
let config: Config = Config::default(); let config: Config = Config::default();
let _ = confy::store("aphorme", Some("config"), Config::default()); let _ = confy::store("apograph", Some("config"), Config::default());
config config
}), }),
} }
@ -55,6 +55,14 @@ pub fn get_basepath(app_handle: tauri::AppHandle) -> Option<String> {
return base_path.map(|p| p.to_string_lossy().into_owned()); return base_path.map(|p| p.to_string_lossy().into_owned());
} }
pub fn _get_client_id(app_handle: &tauri::AppHandle) -> String {
let config = app_handle.state::<Mutex<Config>>();
// Lock the mutex to get mutable access:
let config = config.lock().unwrap();
let client_id = config.core_cfg.client_id.clone();
return client_id;
}
#[tauri::command] #[tauri::command]
pub fn set_basepath(app_handle: tauri::AppHandle, path: String) -> bool { pub fn set_basepath(app_handle: tauri::AppHandle, path: String) -> bool {
if let Ok(resolved_path) = shellexpand::full(&path) { if let Ok(resolved_path) = shellexpand::full(&path) {
@ -68,6 +76,7 @@ pub fn set_basepath(app_handle: tauri::AppHandle, path: String) -> bool {
// Lock the mutex to get mutable access: // Lock the mutex to get mutable access:
let mut config = config.lock().unwrap(); let mut config = config.lock().unwrap();
config.core_cfg.home_path = Some(basepath); config.core_cfg.home_path = Some(basepath);
let _ = confy::store("apograph", Some("config"), config.clone());
return true; return true;
} else { } else {
return false; return false;
@ -82,7 +91,7 @@ pub fn get_open_file_path(app_handle: tauri::AppHandle) -> Option<String> {
let open_file_path = config.core_cfg.current_file.clone(); let open_file_path = config.core_cfg.current_file.clone();
return open_file_path.map(|p| p.to_string_lossy().into_owned()); return open_file_path.map(|p| p.to_string_lossy().into_owned());
} }
pub fn set_open_file_path(app_handle: tauri::AppHandle, path: &String) { pub fn set_open_file_path(app_handle: tauri::AppHandle, path: &str) {
let mut config = app_handle.state::<Mutex<Config>>(); let mut config = app_handle.state::<Mutex<Config>>();
// Lock the mutex to get mutable access: // Lock the mutex to get mutable access:

View file

@ -1,24 +1,32 @@
use std::{ use std::{
fs::File, fs::File,
io::{Error, Read, Result}, io::{Error, Read},
path::Path, path::Path,
str::FromStr,
}; };
use crate::get_basepath; use crate::{config::_get_client_id, get_basepath};
use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool}; use futures::FutureExt;
use serde::{Deserialize, Serialize};
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteConnectOptions, Sqlite, SqlitePool};
use tauri::{ipc::RuntimeCapability, App, AssetResolver, Manager, Url}; use tauri::{ipc::RuntimeCapability, App, AssetResolver, Manager, Url};
async fn populate_file_db(db: &SqlitePool) {}
#[tauri::command] struct DBFileEntry {
pub async fn initialize_database( id: u64,
app_handle: tauri::AppHandle, relative_file_path: String,
basepath: String, file_name: String,
pathtemplate: String, }
) {
#[derive(Debug, Serialize, Deserialize)]
pub enum DBError {
DatabaseLocationError(String),
DatabaseConnectionError(String),
DatabaseQueryError(String),
}
fn get_database_path(app_handle: &tauri::AppHandle) -> Result<String, DBError> {
if let Some(basepath) = get_basepath(app_handle.clone()) { if let Some(basepath) = get_basepath(app_handle.clone()) {
let db_path = Path::new("Documents") let db_path = Path::new(&basepath).join("db.sqlite");
.join(Path::new("Knowledgebase"))
.join("db.sqlite");
let resolved_db_path = match app_handle let resolved_db_path = match app_handle
.path() .path()
@ -26,50 +34,205 @@ pub async fn initialize_database(
{ {
Ok(resolved_knowledgebase_path) => resolved_knowledgebase_path, Ok(resolved_knowledgebase_path) => resolved_knowledgebase_path,
Err(_) => return, Err(e) => return Err(DBError::DatabaseLocationError(e.to_string())),
}; };
let unicode_db_path = &resolved_db_path.to_string_lossy().to_owned(); let unicode_db_path = resolved_db_path.to_string_lossy().as_ref().to_string();
if !Sqlite::database_exists(&unicode_db_path) return Ok(unicode_db_path);
.await }
.unwrap_or(false) Err(DBError::DatabaseLocationError(
{ "No knowledgebase defined".to_string(),
match Sqlite::create_database(&unicode_db_path).await { ))
Ok(_) => println!("Create db success"), }
Err(error) => panic!("error: {}", error), #[tauri::command]
} pub async fn initialize_database(app_handle: tauri::AppHandle) -> Result<(), DBError> {
let unicode_db_path = get_database_path(&app_handle)?;
if !Sqlite::database_exists(&unicode_db_path)
.await
.unwrap_or(false)
{
match Sqlite::create_database(&unicode_db_path).await {
Ok(_) => println!("Create db success"),
Err(error) => panic!("error: {}", error),
} }
let db = SqlitePool::connect(unicode_db_path).await.unwrap(); }
let result = sqlx::query( let db = SqlitePool::connect(&unicode_db_path)
"CREATE TABLE IF NOT EXISTS files .await
.map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;
let _ = sqlx::query(
"CREATE TABLE IF NOT EXISTS files
( (
id INTEGER PRIMARY KEY NOT NULL, id INTEGER PRIMARY KEY NOT NULL,
relative_path TEXT NOT NULL, relative_path TEXT NOT NULL,
file_name TEXT NOT NULL file_name TEXT NOT NULL
);", );",
) )
.execute(&db) .execute(&db)
.await .await
.unwrap(); .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
let result = sqlx::query( let _ = sqlx::query(
"CREATE TABLE IF NOT EXISTS file_diffs "CREATE TABLE IF NOT EXISTS file_diffs
( (
id INTEGER NOT NULL, id INTEGER NOT NULL,
client_id INTEGER NOT NULL, client_id TEXT NOT NULL,
diff_text TEXT NOT NULL, diff_text TEXT NOT NULL,
FOREIGN KEY (id) current_diff BOOLEAN,
REFERENCES files (file_id) parent_diff_id INTEGER,
file_id INTEGER,
FOREIGN KEY (parent_diff_id) REFERENCES file_diffs(id),
FOREIGN KEY (file_id) REFERENCES files(id),
PRIMARY KEY (id, client_id) PRIMARY KEY (id, client_id)
) )
;", ;",
) )
.execute(&db) .execute(&db)
.await .await
.unwrap(); .unwrap();
} // .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
db.close().await;
Ok(())
} }
#[tauri::command] async fn get_file_id(
async fn store_diff(diff: String) {} app_handle: &tauri::AppHandle,
relative_file_path: &str,
file_name: &str,
) -> Result<i32, DBError> {
let unicode_db_path = get_database_path(&app_handle)?;
let db = SqlitePool::connect(&unicode_db_path)
.await
.map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;
async fn add_file(file_path: String, file_name: String) {} let (id, _, _): (i32, String, String) = sqlx::query_as(&format!(
"
SELECT *
FROM files
WHERE relative_path IS '{relative_file_path}'
AND file_name IS '{file_name}'
"
))
.fetch_one(&db)
.await
.map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
db.close().await;
Ok(id)
}
pub async fn add_file(
app_handle: &tauri::AppHandle,
relative_file_path: &str,
file_name: &str,
) -> Result<(), DBError> {
if get_file_id(app_handle, relative_file_path, file_name)
.await
.is_ok()
{
return Ok(());
}
let unicode_db_path = get_database_path(&app_handle)?;
let db = SqlitePool::connect(&unicode_db_path)
.await
.map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;
let id = sqlx::query(&format!(
"
INSERT INTO
files (relative_path, file_name)
VALUES
('{relative_file_path}', '{file_name}');"
))
.execute(&db)
.await
.map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
db.close().await;
Ok(())
}
async fn get_last_file_diff(
app_handle: &tauri::AppHandle,
relative_file_path: &str,
file_name: &str,
) -> Result<i32, DBError> {
let unicode_db_path = get_database_path(&app_handle)?;
let db = SqlitePool::connect(&unicode_db_path)
.await
.map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;
let (parent_diff_id, _, _, _, _, _): (i32, String, String, bool, i32, i32) =
sqlx::query_as(&format!(
"
SELECT *
FROM file_diffs
WHERE relative_path IS '{relative_file_path}'
AND file_name IS '{file_name}'
AND working_diff is 1
"
))
.fetch_one(&db)
.await
.map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
db.close().await;
Ok(parent_diff_id)
}
#[tauri::command]
pub async fn store_diff(
app_handle: &tauri::AppHandle,
relative_file_path: &str,
file_name: &str,
diff: String,
) -> Result<(), DBError> {
let unicode_db_path = get_database_path(&app_handle)?;
let client_id = _get_client_id(&app_handle);
let db = SqlitePool::connect(&unicode_db_path)
.await
.map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?;
// 1 insert into file_diffs with parent
// 2 make last diff not currennt diff
// id INTEGER NOT NULL,
// client_id TEXT NOT NULL,
// diff_text TEXT NOT NULL,
// current_diff BOOLEAN,
// parent_diff_id INTEGER,
// file_id INTEGER,
// FOREIGN KEY (parent_diff_id) REFERENCES file_diffs(id),
// FOREIGN KEY (file_id) REFERENCES files(id),
// PRIMARY KEY (id, client_id)
let file_id = get_file_id(app_handle, relative_file_path, file_name).await?;
let parent_diff_id: i32 = sqlx::query_scalar(&format!(
"
SELECT id from file_diffs
WHERE relative_path IS '{relative_file_path}'
AND file_name IS '{file_name}'
AND working_diff is 1
"
))
.fetch_one(&db)
.await
.unwrap_or(0);
let _ = sqlx::query(&format!(
"
UPDATE file_diffs
SET current_diff = 0
WHERE relative_path IS '{relative_file_path}'
AND file_name IS '{file_name}'
AND working_diff is 1
"
))
.execute(&db)
.await
.map_err(|e| DBError::DatabaseQueryError(e.to_string()));
let _ = sqlx::query(&format!(
"
INSERT INTO
file_diffs (client_id, diff_text, current_diff, file_id)
VALUES
('{client_id}', '{diff}', 1, {parent_diff_id});"
))
.execute(&db)
.await
.map_err(|e| DBError::DatabaseQueryError(e.to_string()))?;
db.close().await;
Ok(())
}

View file

@ -1,10 +1,11 @@
use crate::{ use crate::{
config::{get_open_file_path, set_open_file_path}, config::{get_open_file_path, set_open_file_path},
database::store_diff,
get_basepath, get_basepath,
}; };
use diff_match_patch_rs::{Compat, DiffMatchPatch, Error, PatchInput}; use diff_match_patch_rs::{Compat, DiffMatchPatch, Error, PatchInput};
use std::fs::read_to_string;
use std::path::Path; use std::path::Path;
use std::{fs::read_to_string, path::PathBuf};
use tauri::{ipc::RuntimeCapability, App, AssetResolver, Manager, Url}; use tauri::{ipc::RuntimeCapability, App, AssetResolver, Manager, Url};
fn compare_content(old_content: &str, new_content: &str) -> Result<String, Error> { fn compare_content(old_content: &str, new_content: &str) -> Result<String, Error> {
@ -21,16 +22,27 @@ fn compare_content(old_content: &str, new_content: &str) -> Result<String, Error
} }
#[tauri::command] #[tauri::command]
pub async fn save_file(app_handle: tauri::AppHandle, content: String) { pub async fn save_file(app_handle: tauri::AppHandle, content: String) -> Result<(), String> {
if let Some(path) = get_open_file_path(app_handle.clone()) { if let Some(path) = get_open_file_path(app_handle.clone()) {
if let Ok(file_content) = load_file(app_handle, path).await { if let Ok(file_content) = load_file(app_handle.clone(), &path).await {
println!(
"{:?}",
store_diff(
&app_handle,
&path.clone(),
&PathBuf::from(path).file_name().unwrap().to_string_lossy(),
compare_content(&file_content, &content).map_err(|e| format!("{e:?}"))?,
)
.await
);
println!("{:?}", compare_content(&file_content, &content)); println!("{:?}", compare_content(&file_content, &content));
} }
} }
Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn load_file(app_handle: tauri::AppHandle, path: String) -> Result<String, String> { pub async fn load_file(app_handle: tauri::AppHandle, path: &str) -> Result<String, String> {
set_open_file_path(app_handle, &path); set_open_file_path(app_handle, path);
read_to_string(path).map_err(|e| e.to_string()) read_to_string(path).map_err(|e| e.to_string())
} }

View file

@ -1,19 +1,33 @@
use crate::get_basepath; use crate::{database::add_file, get_basepath};
use futures::future::{BoxFuture, FutureExt};
use html_tag::HtmlTag; use html_tag::HtmlTag;
use shellexpand; use shellexpand;
use std::{ use std::{
fs::{self, DirEntry}, fs::{self, DirEntry},
path::Path, ops::Deref,
path::{Path, PathBuf},
}; };
#[tauri::command] #[tauri::command]
pub fn dir_tree_html(app_handle: tauri::AppHandle, filter: Vec<String>) -> String { pub async fn dir_tree_html(app_handle: tauri::AppHandle, filter: Vec<String>) -> String {
if let Some(basepath) = get_basepath(app_handle) { if let Some(basepath) = get_basepath(app_handle.clone()) {
add_dir_tree_node(&Path::new(&basepath), &filter).to_html() add_dir_tree_node(
&app_handle,
&Path::new(&basepath),
&filter,
&Path::new(&basepath).parent().unwrap_or(Path::new("/")),
)
.await
.to_html()
} else { } else {
String::new() String::new()
} }
} }
fn add_dir_tree_node(path: &Path, filter: &Vec<String>) -> HtmlTag { async fn add_dir_tree_node(
app_handle: &tauri::AppHandle,
path: &Path,
filter: &Vec<String>,
parent_path: &Path,
) -> HtmlTag {
let mut html = HtmlTag::new("div") let mut html = HtmlTag::new("div")
.with_class("filetree-node") .with_class("filetree-node")
.with_id(&format!( .with_id(&format!(
@ -41,17 +55,36 @@ fn add_dir_tree_node(path: &Path, filter: &Vec<String>) -> HtmlTag {
), ),
), ),
); );
if let Ok(entries) = fs::read_dir(path) { if let Ok(entries) = fs::read_dir(path) {
for dir_entry_res in entries { for dir_entry_res in entries {
if let Ok(dir_entry) = dir_entry_res { if let Ok(dir_entry) = dir_entry_res {
if let Ok(metadata) = fs::metadata(dir_entry.path()) { if let Ok(metadata) = fs::metadata(dir_entry.path()) {
let absolute_path = dir_entry.path();
let relative_path =
absolute_path.strip_prefix(parent_path).unwrap_or_else(|_| {
log::error!("{:?}:{:?}", path, parent_path);
Path::new("/")
});
if metadata.is_file() { if metadata.is_file() {
html.add_child(div_from_dir_entry(&dir_entry)) add_file(
app_handle,
relative_path.to_string_lossy().as_ref(),
dir_entry.file_name().to_string_lossy().as_ref(),
)
.await;
html.add_child(div_from_dir_entry(&absolute_path, &relative_path))
} else if metadata.is_dir() { } else if metadata.is_dir() {
html.add_child( html.add_child(
add_dir_tree_node(&dir_entry.path(), &filter) Box::pin(add_dir_tree_node(
.with_attribute("style", "visibility: hidden; height: 0px;"), // .with_style("visibility", "hidden") app_handle,
// .with_style("height", "0px"), &dir_entry.path(),
&filter,
parent_path,
))
.await
.with_attribute("style", "visibility: hidden; height: 0px;"), // .with_style("visibility", "hidden")
// .with_style("height", "0px"),
); );
} }
} }
@ -61,14 +94,13 @@ fn add_dir_tree_node(path: &Path, filter: &Vec<String>) -> HtmlTag {
return html; return html;
} }
fn div_from_dir_entry(dir_entry: &DirEntry) -> HtmlTag { fn div_from_dir_entry(absolute_path: &Path, relative_path: &Path) -> HtmlTag {
let mut file_div = HtmlTag::new("div") let mut file_div = HtmlTag::new("div")
.with_class("filetree-node") .with_class("filetree-node")
.with_id(&format!("{}", dir_entry.path().to_string_lossy())) .with_id(&format!("{}", absolute_path.to_string_lossy()))
.with_attribute("style", "visibility: hidden; height: 0px;"); .with_attribute("style", "visibility: hidden; height: 0px;");
let mut file_button = HtmlTag::new("button").with_class("filetree-file-button"); let mut file_button = HtmlTag::new("button").with_class("filetree-file-button");
match dir_entry match relative_path
.path()
.extension() .extension()
.unwrap_or_default() .unwrap_or_default()
.to_string_lossy() .to_string_lossy()
@ -88,7 +120,14 @@ fn div_from_dir_entry(dir_entry: &DirEntry) -> HtmlTag {
), ),
}; };
file_button.add_child(HtmlTag::new("a").with_body(&dir_entry.file_name().to_string_lossy())); file_button.add_child(
HtmlTag::new("a").with_body(
&relative_path
.file_name()
.unwrap_or_default()
.to_string_lossy(),
),
);
file_div.add_child(file_button); file_div.add_child(file_button);
return file_div; return file_div;
} }

View file

@ -23,6 +23,7 @@ pub fn run() {
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_log::Builder::new().build()) .plugin(tauri_plugin_log::Builder::new().build())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_log::Builder::new().build())
.setup(|app| { .setup(|app| {
// allowed the given directory // allowed the given directory
let scope = app.fs_scope(); let scope = app.fs_scope();

View file

@ -3,18 +3,19 @@
} }
:root { :root {
--main-bg-color: #2f2f2f; --main-bg-color: #1f1f1f;
--highlight-color: #3f3f3f; --highlight-color: #3f3f3f;
--bright-highlight-color: #4f4f4f; --bright-highlight-color: #4f4f4f;
--text-color: #f6f6f6; --text-color: #f6f6f6;
--accent-color: #5f5f5f; --accent-color: #006f00;
--bright-accent-color: #009f00;
--sidebar-width: 3em; --sidebar-width: 3em;
--filetree-width: 10em; --filetree-width: 10em;
font-family: Avenir, Helvetica, Arial, sans-serif; font-family: Avenir, Helvetica, Arial, sans-serif;
font-size: 30px; font-size: 30px;
line-height: 24px; line-height: 24px;
color: #f6f6f6; color: var(--main-bg-color);
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
font-synthesis: none; font-synthesis: none;
@ -95,7 +96,25 @@ h1 {
line-height: 18px; line-height: 18px;
margin-top: 0.067em; margin-top: 0.067em;
margin-bottom: 0.067em; margin-bottom: 0.067em;
color: red; /* color: red; */
}
a {
}
a:link {
color: var(--accent-color);
}
a:visited {
}
a:focus {
}
a:hover {
}
a:active {
} }
li { li {