Skip to content

Commit

Permalink
Add recent workspaces (#261)
Browse files Browse the repository at this point in the history
* Show workspace open error states

* Run biome

* Move tauri utils out of generic utils

* Add recent workspaceS
  • Loading branch information
stephlow authored Sep 17, 2024
1 parent 7272f7f commit 7158ef9
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 5 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions fpx-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tauri = { version = "1.8.0", features = [
"http-all",
"config-toml",
] }
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
toml = "0.8.19"

[features]
Expand Down
66 changes: 63 additions & 3 deletions fpx-app/src/commands/workspace.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,84 @@
use crate::models::workspace::{Config, OpenWorkspaceByPathError, Workspace};
use crate::state::AppState;
use crate::STORE_PATH;
use serde_json::Value;
use std::fs::read_to_string;
use tauri::State;
use tauri::{AppHandle, Runtime, State};
use tauri_plugin_store::{with_store, StoreCollection};

const RECENT_WORKSPACES_STORE_KEY: &str = "recent_workspaces";

#[tauri::command]
pub fn list_recent_workspaces<R: Runtime>(
app: AppHandle<R>,
stores: State<'_, StoreCollection<R>>,
) -> Vec<String> {
if let Ok(recent_projects) = with_store(app, stores, STORE_PATH, |store| {
if let Some(Value::Array(arr)) = store.get(RECENT_WORKSPACES_STORE_KEY) {
let vec_of_strings: Vec<String> = arr
.iter()
.filter_map(|item| item.as_str().map(|s| s.to_string()))
.collect();

return Ok(vec_of_strings);
}

Ok(vec![])
}) {
return recent_projects;
}

vec![]
}

#[tauri::command]
pub fn get_current_workspace(state: State<'_, AppState>) -> Option<Workspace> {
state.get_workspace()
}

#[tauri::command]
pub fn open_workspace_by_path(
pub fn open_workspace_by_path<R: Runtime>(
path: String,
state: State<'_, AppState>,
app: AppHandle<R>,
stores: State<'_, StoreCollection<R>>,
) -> Result<Workspace, OpenWorkspaceByPathError> {
match read_to_string(format!("{}/fpx.toml", path)) {
Ok(content) => match toml::from_str::<Config>(&content) {
Ok(config) => {
let workspace = Workspace::new(path, config);
let workspace = Workspace::new(path.clone(), config);
state.set_workspace(workspace.clone());

with_store(app, stores, STORE_PATH, |store| {
let mut recents = match store.get(RECENT_WORKSPACES_STORE_KEY) {
Some(Value::Array(arr)) => {
let vec_of_strings: Vec<String> = arr
.iter()
.filter_map(|item| {
if let Some(s) = item.as_str() {
if s != path {
return Some(s.to_string());
}
}
None
})
.collect();

vec_of_strings
}
_ => vec![],
};

recents.insert(0, path);

store
.insert(RECENT_WORKSPACES_STORE_KEY.into(), recents.into())
.unwrap();

store.save()
})
.unwrap();

Ok(workspace)
}
Err(error) => Err(OpenWorkspaceByPathError::InvalidConfiguration {
Expand Down
7 changes: 7 additions & 0 deletions fpx-app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

use state::AppState;
use tauri::{CustomMenuItem, Menu, MenuItem, Submenu, WindowBuilder};
use tauri_plugin_store::StoreBuilder;

mod commands;
mod models;
mod state;

const STORE_PATH: &str = "fpx.bin";

fn main() {
let quit = CustomMenuItem::new("quit".to_string(), "Quit");
let open = CustomMenuItem::new("open".to_string(), "Open");
Expand All @@ -18,8 +21,11 @@ fn main() {
.add_submenu(submenu);

tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::default().build())
.manage(AppState::default())
.setup(|app| {
StoreBuilder::new(app.handle(), STORE_PATH.parse()?).build();

let window = WindowBuilder::new(
app,
"main-window".to_string(),
Expand All @@ -44,6 +50,7 @@ fn main() {
.invoke_handler(tauri::generate_handler![
commands::workspace::close_workspace,
commands::workspace::get_current_workspace,
commands::workspace::list_recent_workspaces,
commands::workspace::open_workspace_by_path,
])
.run(tauri::generate_context!())
Expand Down
8 changes: 8 additions & 0 deletions studio/src/tauri/RuntimeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { WorkspaceShell } from "./WorkspaceShell";
import {
closeWorkspace,
getCurrentWorkspace,
openWorkspace,
showOpenWorkspaceDialog,
} from "./utils";

Expand All @@ -28,6 +29,7 @@ type Runtime =
state: AppState;
requestCloseWorkspace: () => void;
requestOpenWorkspaceDialog: () => void;
requestOpenWorkspaceByPath: (path: string) => void;
}
| { type: "unknown" };

Expand All @@ -46,6 +48,11 @@ function TauriRuntime({ children }: RuntimeProviderProps) {
const [workspace, setWorkspace] = useState<Workspace | undefined>();
const [error, setError] = useState<OpenWorkspaceByPathError | undefined>();

const handleOpenWorkspaceByPath = useHandler(async (path: string) => {
const workspace = await openWorkspace(path);
setWorkspace(workspace);
});

const handleOpenDialogRequested = useHandler(() => {
showOpenWorkspaceDialog()
.then((workspace) => {
Expand Down Expand Up @@ -103,6 +110,7 @@ function TauriRuntime({ children }: RuntimeProviderProps) {
state: { workspace },
requestCloseWorkspace: handleCloseWorkspace,
requestOpenWorkspaceDialog: handleOpenDialogRequested,
requestOpenWorkspaceByPath: handleOpenWorkspaceByPath,
}}
>
{component}
Expand Down
35 changes: 33 additions & 2 deletions studio/src/tauri/WorkspaceSelector/WorkspaceSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,48 @@
import { Button } from "@/components/ui/button";
import { useContext } from "react";
import { Card } from "@/components/ui/card";
import { cn } from "@/utils";
import { useContext, useEffect, useState } from "react";
import { RuntimeContext } from "../RuntimeProvider";
import { listRecentWorkspaces } from "../utils";

export function WorkspaceSelector() {
const runtime = useContext(RuntimeContext);
const [paths, setPaths] = useState<Array<string>>([]);

useEffect(() => {
listRecentWorkspaces().then(setPaths);
}, []);

if (runtime?.type !== "tauri") {
return null;
}

return (
<div className="w-screen h-screen flex flex-col items-center justify-center">
<Button onClick={runtime.requestOpenWorkspaceDialog}>Open</Button>
<Card className="p-4 flex flex-col gap-8 w-full h-full">
<strong>Recent projects</strong>
<div className="flex flex-col gap-4">
{paths.map((path) => (
<button
key={path}
className={cn(
"flex items-center p-4",
"border-l-2 border-transparent",
"hover:bg-primary/10 hover:border-blue-500",
"transition-all",
"cursor-pointer",
)}
onClick={() => runtime.requestOpenWorkspaceByPath(path)}
type="button"
>
{path}
</button>
))}
</div>
<Button onClick={runtime.requestOpenWorkspaceDialog}>
Open workspace...
</Button>
</Card>
</div>
);
}
4 changes: 4 additions & 0 deletions studio/src/tauri/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { invoke } from "@tauri-apps/api";
import { open } from "@tauri-apps/api/dialog";
import { appDataDir } from "@tauri-apps/api/path";

export async function listRecentWorkspaces() {
return await invoke<Array<string>>("list_recent_workspaces");
}

export async function openWorkspace(path: string) {
return await invoke<Workspace | undefined>("open_workspace_by_path", {
path,
Expand Down

0 comments on commit 7158ef9

Please sign in to comment.