Skip to content

Commit

Permalink
Settings and toasts
Browse files Browse the repository at this point in the history
1.0.0 is just around the corner! Just need to fix the styling in the settings page, add syntax highlighting, and make the autocomplete a bit better!
  • Loading branch information
SekoiaTree committed Apr 9, 2023
1 parent a1e8c75 commit 796e215
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 29 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ Originally almost identical to the web version, but now has several added featur

### TODO
Ordered by probable priority
- [ ] Add extra commands like Ctrl-S, Ctrl-O
- [ ] Add options like config-defined functions
- [ ] Better autocomplete with variables
- [ ] Syntax highlighting

Expand All @@ -14,9 +12,12 @@ Ordered by probable priority
- Slight syntax highlighting!
- Input hints and autocompletion! (slightly minimal currently due to library constraints)
- Shortcuts!
- Ctrl-W quits the program (will be configurable; can also be Ctrl-D or both)
- Ctrl-C copies the current input (if nothing is selected)
- <kbd>Ctrl+W</kbd> or <kbd>Ctrl+D</kbd> quits the program
- <kbd>Ctrl+C</kbd> copies the current input (if nothing is selected)
- This will be configurable in the future, with options like previous result, hint or input
- <kbd>Ctrl+S</kbd> saves all the calculations you've run (can be limited to a certain amount) to a file. One line per calculation.
- <kbd>Ctrl+O</kbd> loads a file saved that way, and runs all the calculations in it.
- A nice UI! (mostly taken from the website)
- A load of configurable settings for all your function needs!

Attribution: Settings cog taken from: https://game-icons.net/1x1/lorc/cog.html
81 changes: 67 additions & 14 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ windows_subsystem = "windows"
)]

use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf;
use std::sync::Mutex;
use std::sync::{Mutex, MutexGuard};
use std::time::Instant;
use dialog::{DialogBox, FileSelectionMode};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -49,18 +50,38 @@ fn copy_to_clipboard(value: String, app_handle: AppHandle<Wry>) {
}

#[tauri::command]
fn save_to_file(_state: tauri::State<FendContext>) -> Result<(), String> {
fn load_from_file() -> Result<(Vec<String>, bool), String> {
let dialog = dialog::FileSelection::new("Select file to open")
.mode(FileSelectionMode::Open)
.title("Open a list of things to run")
.show().map_err(|x| x.to_string())?;

if let Some(x) = dialog {
let file = BufReader::new(File::open(x).map_err(|e| e.to_string())?);
return Ok((file.lines().filter_map(|x| x.ok()).collect(), false));
}

Ok((vec![], true))
}

#[tauri::command]
fn save_to_file(input: Vec<String>) -> Result<bool, String> {
let dialog = dialog::FileSelection::new("Select file to save to")
.mode(FileSelectionMode::Save)
.title("Saving variables")
.title("Saving input")
.show().map_err(|x| x.to_string())?;

// TODO: error reporting; toasts for error, cancelled, and success
if let Some(_x) = dialog {
todo!() // we need to fork fend to get access to variables and such.
}
if let Some(x) = dialog {
let mut file = File::create(x).map_err(|e| e.to_string())?;

Ok(())
for i in input {
writeln!(file, "{}", i).map_err(|e| e.to_string())?;
}

Ok(true)
} else {
Ok(false)
}
}

#[tauri::command]
Expand Down Expand Up @@ -115,18 +136,40 @@ async fn open_settings(handle: AppHandle<Wry>) {
})
}

const SETTINGS_CORRUPTED: &str = "The settings were corrupted. Should never happen, please report.";

#[tauri::command]
fn set_setting(id: String, value: serde_json::Value, settings: tauri::State<SettingsState>) {
settings.0.lock().expect("The settings were corrupted. Should never happen, please report.")[id] = value;
settings.0.lock().expect(SETTINGS_CORRUPTED)[id] = value;
}

#[tauri::command]
fn save_settings(settings: tauri::State<SettingsState>) -> Result<(), String> {
if let Some(x) = tauri::api::path::config_dir() {
let settings : MutexGuard<serde_json::Value> = settings.0.lock().expect(SETTINGS_CORRUPTED);
fs::create_dir_all(x.join("fendesk")).map_err(|e| e.to_string())?;
let mut file = File::create(x.join("fendesk/settings.json")).map_err(|e| e.to_string())?;
file.write(serde_json::to_string(&*settings).map_err(|e| e.to_string())?.as_bytes()).map_err(|e| e.to_string())?;
Ok(())
} else {
Err("Configuration folder not found!".to_string())
}
}

#[tauri::command]
fn get_settings(settings: tauri::State<SettingsState>) -> serde_json::Value {
settings.0.lock().expect("The settings were corrupted. Should never happen, please report.").clone()
settings.0.lock().expect(SETTINGS_CORRUPTED).clone()
}

fn main() {
let context = create_context();
let default_settings = json!({
"ctrl_d_closes": true,
"ctrl_w_closes": false,
"save_back_count": -1,
"ctrl_c_behavior": "input",
"global_inputs": "",
});

tauri::Builder::default()
.setup(|app| {
Expand All @@ -141,16 +184,26 @@ fn main() {
Ok(())
})
.manage(FendContext(Mutex::new(context)))
.manage(SettingsState(Mutex::new(json!({}))))
.manage(SettingsState(Mutex::new(get_saved_settings().unwrap_or(default_settings))))
.invoke_handler(tauri::generate_handler![
setup_exchanges, fend_prompt, fend_preview_prompt, fend_completion, // core fend
quit, copy_to_clipboard, save_to_file, // ctrl- shortcuts
open_settings, set_setting, get_settings // settings
quit, copy_to_clipboard, save_to_file, load_from_file, // ctrl- shortcuts
open_settings, set_setting, get_settings, save_settings // settings
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

fn get_saved_settings() -> Result<serde_json::Value, ()> {
if let Some(x) = tauri::api::path::config_dir() {
fs::create_dir_all(x.join("fendesk")).map_err(|_| ())?;
let file = File::open(x.join("fendesk/settings.json")).map_err(|_| ())?;
serde_json::from_reader(file).map_err(|_| ())
} else {
Err(())
}
}

fn create_context() -> fend_core::Context {
let mut context = fend_core::Context::new();
let current_time = chrono::Local::now();
Expand Down
42 changes: 42 additions & 0 deletions ui/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,46 @@ b {
::-webkit-scrollbar-thumb:hover {
background: #606060;
border: 0;
}

#toast {
visibility: hidden;
min-width: 250px;
margin-left: -125px;
text-align: center;
border-radius: 1em;
padding: 8px;
position: fixed;
z-index: 1;
left: 50%;
bottom: 30px;

color: hsl(30, 25%, 10%);
}

#toast.ok {
background-color: hsl(90, 70%, 30%);
}

#toast.note {
background-color: hsl(30, 25%, 55%);
}

#toast.error {
background-color: hsl(0, 75%, 40%);
}

#toast.show {
visibility: visible;
animation: toast_fadein 0.5s, toast_fadeout 0.5s 2.5s;
}

@keyframes toast_fadein {
from {bottom: 0; opacity: 0;}
to {bottom: 30px; opacity: 1;}
}

@keyframes toast_fadeout {
from {bottom: 30px; opacity: 1;}
to {bottom: 0; opacity: 0;}
}
2 changes: 1 addition & 1 deletion ui/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ p, textarea {
display: grid;
grid-template-columns: 2ch 1fr;
grid-template-rows: auto auto;
min-height: 4.5em; /* just enough for two liens of text. Not super clean but should be robust enough with different font sizes? */
min-height: 4.5em; /* just enough for two lines of text. Not super clean but should be robust enough with different font sizes? */
}

#input p {
Expand Down
3 changes: 2 additions & 1 deletion ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
</div>
</div>
</main>
<script src="index.js"></script>
<div id="forest-ext-shadow-host"></div>
<div id="toast"></div>
<script src="index.js"></script>
</body>
</html>
72 changes: 65 additions & 7 deletions ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let inputHint = document.getElementById("input-hint");
let inputHighlighting = document.getElementById("highlighting");
let input = document.getElementById("input");
let settingsIcon = document.getElementById("settings-icon");
let toast = document.getElementById("toast");
let history = [];
let navigation = 0;

Expand All @@ -19,11 +20,19 @@ async function get_settings() {

let settings;

get_settings();
get_settings().then(async () => {
if (typeof settings["global_inputs"] === typeof "") {
let split = settings["global_inputs"].split("\n");
for (let i = 0; i < split.length; i++) {
await evaluateFendWithTimeout(split[i], 500);
}
}
});

window.__TAURI__.event.listen('settings-closed', () => {
settingsIcon.style.opacity = "";
get_settings();
invoke("save_settings").catch(x => set_toast("Error saving settings: " + x, "error"));
});

invoke("setup_exchanges");
Expand All @@ -45,6 +54,16 @@ function open_settings() {
invoke("open_settings");
}

function set_toast(text, type) {
toast.innerText = text;

// Add the "show" class to DIV
toast.className = "show " + type;

// After 3 seconds, remove the show class from DIV
setTimeout(function(){ toast.className = ""; }, 3000);
}

async function commands(event) {
if (!event.ctrlKey) {
return;
Expand All @@ -53,13 +72,52 @@ async function commands(event) {
if ((event.key === "w" && settings["ctrl_w_closes"]) || (event.key === "d" && settings["ctrl_d_closes"])) {
invoke("quit");
} else if (event.key === "c" && document.getSelection().isCollapsed) {
invoke("copy_to_clipboard", {"value": inputText.value})
// TODO; optionally make this copy other values, such as the hint or the previous output
// Also add a toast (https://www.w3schools.com/howto/howto_js_snackbar.asp)
let clipboard_text;
if (settings["ctrl_c_behavior"] === "prev_result") {
clipboard_text = output.lastChild.innerText;
if (clipboard_text === undefined) {
return;
}
} else if (settings["ctrl_c_behavior"] === "hint") {
clipboard_text = inputHint.innerText;
} else {
clipboard_text = inputText.value;
}
invoke("copy_to_clipboard", {"value": clipboard_text});
set_toast("Copied to clipboard!", "note");
} else if (event.key === "s") {
invoke("save_to_file")
let history_segment;
if (settings["save_back_count"] < 0) {
history_segment = history;
} else {
history_segment = history.slice(-settings["save_back_count"]);
}
invoke("save_to_file", {"input": history_segment}).then(x => {
if (x) {
set_toast("Successfully saved file!", "ok");
} else {
set_toast("Cancelled!", "note")
}
}).catch(x => set_toast("Error saving file: " + x, "error"));
} else if (event.key === "o") {
// TODO
let inputs = await invoke("load_from_file").then(x => {
console.log(x);
if (x[1]) {
set_toast("Cancelled!", "note");
}
return x[0];
}).catch(x => {
set_toast("Error loading file: " + x, "error");
return [];
});

if (inputs.length === 0) {
return;
}
for (let i = 0; i < inputs.length; i++) {
await evaluateFendWithTimeout(inputs[i], 500);
}
set_toast("Finished loading file!", "ok");
}
}

Expand Down Expand Up @@ -206,7 +264,7 @@ async function updateReplicatedText() {
+ hint;
}

function updateHint() {
async function updateHint() {
evaluateFendPreviewWithTimeout(inputText.value, 100).then(x => {
inputHint.className = "valid-hint";
setHintInnerText(x);
Expand Down
35 changes: 34 additions & 1 deletion ui/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,49 @@ body {
display: grid;
grid-template-columns: auto auto;
align-items: center;
padding: 10px 1em;
}

label {
grid-column: 1;
}

input {
input, textarea, select {
grid-column: 2;
}

textarea {
line-height: inherit;
font-family: inherit;
outline: none;
overflow: scroll;
resize: none;

border: 1px solid var(--highlight);
border-radius: 5px;
}

select {
appearance: none;

border: 1px solid var(--highlight);
border-radius: 5px;

background-color: var(--bg);
padding: 1px 0.75ch;
}

select:after {
content: 'v';
}

input[type="number"] {
appearance: none;
border: 1px solid var(--highlight);
border-radius: 5px;
padding: 1px 0.75ch;
}

input[type="checkbox"] {
appearance: none;
position: relative;
Expand Down
Loading

0 comments on commit 796e215

Please sign in to comment.