use qframe::prelude::*;
use qframe::widgets::Toast;
use crate::requests::identities;
use crate::store::{Session, SetupStep, WorkspaceFile, WorkspaceId};
use crate::ui::home::Entry;
use crate::ui::profiles::Profiles;
use crate::ui::providers::Providers as ProvidersScreen;
use crate::ui::workspace::{OpenWorkspace, WorkspaceScreen};
use crate::ui::workspaces::Workspaces;
use crate::{Msg, Opening, Page, QCode, WorkspaceContents, provider, ui};
impl QCode {
pub(super) fn open(&mut self, entry: Entry) -> Command<Msg> {
match entry {
Entry::Quit => self.ask_quit(),
Entry::Workspaces => self.show_workspaces(false),
Entry::NewWorkspace => self.show_workspaces(true),
Entry::Profiles => self.show_profiles(),
Entry::Providers => self.show_providers(),
Entry::Settings => self.show_settings(),
Entry::Continue => self.resume(),
}
}
fn resume(&mut self) -> Command<Msg> {
if let Some(screen) = self.workspace.as_mut().filter(|screen| !screen.workspaces().is_empty()) {
let entered = ui::workspace::opened(screen).map(Msg::Workspace);
self.router.push(Page::Workspace);
return Command::batch([entered, self.take_focus(), self.reread_profiles()]);
}
if let Some(saved) = self.saved.clone().filter(|saved| !saved.is_empty()) {
let ids = saved.workspaces.iter().map(|workspace| workspace.id.clone()).collect();
return self.read_workspaces(ids, Opening::Session(saved));
}
match self.config.recent_workspaces().first().cloned() {
Some(id) => self.read_workspaces(vec![id.clone()], Opening::One(id)),
None => self.show_workspaces(false),
}
}
pub(super) fn show_workspaces(&mut self, new: bool) -> Command<Msg> {
let Some(store) = self.store() else { return self.repair(SetupStep::Location) };
if self.workspaces.is_none() {
let recent = self.config.recent_workspaces().iter().map(|id| id.as_str().to_owned()).collect();
self.workspaces = Some(Workspaces::new(store, self.found.clone(), recent));
}
let Some(screen) = self.workspaces.as_mut() else { return Command::none() };
self.router.push(Page::Workspaces);
let (refresh, _) = ui::workspaces::update(screen, ui::workspaces::Msg::Refresh);
if !new {
return refresh.map(Msg::Workspaces);
}
let (start, _) = ui::workspaces::update(screen, ui::workspaces::Msg::Start);
Command::batch([refresh, start]).map(Msg::Workspaces)
}
pub(super) fn open_workspaces(&self) -> Vec<WorkspaceId> {
self.workspace.as_ref().map_or_else(Vec::new, |screen| {
screen.workspaces().iter().filter_map(|workspace| WorkspaceId::parse(workspace.id()).ok()).collect()
})
}
pub(super) fn show_profiles(&mut self) -> Command<Msg> {
let Some(store) = self.store() else { return self.repair(SetupStep::Location) };
if self.profiles.is_none() {
let providers = match &self.providers {
Some(screen) => screen.path().map(std::path::Path::to_path_buf),
None => crate::provider::Providers::file(),
};
self.profiles = Some(
Profiles::new(Some(store.root().to_path_buf()), self.found.clone()).with_providers_file(providers),
);
}
let Some(screen) = self.profiles.as_mut() else { return Command::none() };
self.router.push(Page::Profiles);
ui::profiles::update(screen, ui::profiles::Msg::Reload).map(Msg::Profiles)
}
pub(super) fn show_providers(&mut self) -> Command<Msg> {
if self.providers.is_none() {
self.providers = Some(ProvidersScreen::new(crate::provider::Providers::file(), provider::Web::network()));
}
let Some(screen) = self.providers.as_mut() else { return Command::none() };
self.router.push(Page::Providers);
ui::providers::update(screen, ui::providers::Msg::Reload).map(|message| Msg::Providers(Box::new(message)))
}
fn show_settings(&mut self) -> Command<Msg> {
self.router.push(Page::Settings);
let profiles = self.store().map(|store| store.profiles().value).unwrap_or_default();
let found = self.found.clone();
let read =
Command::perform(move || Msg::Settings(ui::settings::Msg::Profiles(identities(found.as_ref(), &profiles))));
Command::batch([read, self.take_focus()])
}
pub(super) fn read_workspaces(&mut self, ids: Vec<WorkspaceId>, opening: Opening) -> Command<Msg> {
let Some(store) = self.store() else { return self.repair(SetupStep::Location) };
Command::perform(move || {
let known = store.profiles().value;
let mut files: Vec<WorkspaceFile> =
store.workspaces().value.into_iter().filter_map(|entry| entry.file).collect();
let contents = ids
.iter()
.filter_map(|id| {
let file = files.remove(files.iter().position(|file| file.id == *id)?);
let paths = store.paths_of(&file);
let folder_gone = file.folder.is_some() && !paths.code.is_dir();
Some(WorkspaceContents { file, paths, profiles: known.clone(), folder_gone })
})
.collect();
Msg::Read(contents, opening)
})
}
pub(super) fn read(&mut self, contents: Vec<WorkspaceContents>, opening: Opening) -> Command<Msg> {
let (gone, contents): (Vec<_>, Vec<_>) = contents.into_iter().partition(|one| one.folder_gone);
let mut told: Vec<Command<Msg>> = gone
.iter()
.map(|one| {
let toast = Toast::warning(t!("app.folder-gone", name = one.file.name.as_str()))
.body(t!("app.folder-gone-body", path = one.paths.code.display().to_string()));
Command::toast(toast)
})
.collect();
match opening {
Opening::One(id) if gone.iter().any(|one| one.file.id == id) => told.push(self.show_workspaces(false)),
Opening::One(id) => told.push(self.show_workspace(contents, &id)),
Opening::Session(mut session) => {
session.workspaces.retain(|record| !gone.iter().any(|one| one.file.id == record.id));
told.push(self.restore(contents, &session));
}
}
Command::batch(told)
}
fn show_workspace(&mut self, contents: Vec<WorkspaceContents>, id: &WorkspaceId) -> Command<Msg> {
let Some(one) = contents.into_iter().find(|workspace| workspace.file.id == *id) else {
self.config.forget_workspace(id);
self.refresh_home();
return Command::batch([self.save(), self.show_workspaces(false)]);
};
let workspace = OpenWorkspace::new(&one.file, one.paths, one.profiles);
let entered = match self.workspace.as_mut() {
Some(screen) => ui::workspace::add(screen, workspace),
None => {
let mut screen = self.workspace_screen(vec![workspace]);
let entered = ui::workspace::opened(&mut screen);
self.workspace = Some(screen);
entered
}
};
self.enter_workspace();
self.config.remember_workspace(id);
Command::batch([entered.map(Msg::Workspace), self.take_focus(), self.save(), self.keep_session()])
}
fn workspace_screen(&self, workspaces: Vec<OpenWorkspace>) -> WorkspaceScreen {
let mut screen = WorkspaceScreen::new(self.found.clone(), self.user, workspaces)
.with_registry(self.registry.clone())
.backing_up(self.config.backup_every())
.watching(self.live_files)
.bridging(self.live_files);
screen.set_editor(self.config.editor());
screen.set_sound(self.config.sound());
screen.set_ask_first(self.config.ask_first());
screen.set_freeze_idle(self.config.freeze_idle());
screen
}
fn restore(&mut self, contents: Vec<WorkspaceContents>, session: &Session) -> Command<Msg> {
let gone: Vec<&str> = session
.workspaces
.iter()
.filter(|record| !contents.iter().any(|one| one.file.id == record.id))
.map(|record| record.id.as_str())
.collect();
let mut told = Vec::new();
if !gone.is_empty() {
let toast = Toast::warning(t!("app.session-gone", n = gone.len())).body(gone.join(", "));
told.push(Command::toast(toast));
}
if let Some(problem) = self.saved_problems.first() {
told.push(Command::toast(Toast::warning(t!("app.session-broken")).body(problem.to_string())));
self.saved_problems.clear();
}
if contents.is_empty() {
told.push(self.show_workspaces(false));
return Command::batch(told);
}
let ids: Vec<WorkspaceId> = contents.iter().map(|one| one.file.id.clone()).collect();
let workspaces: Vec<OpenWorkspace> =
contents.into_iter().map(|one| OpenWorkspace::new(&one.file, one.paths, one.profiles)).collect();
let mut screen = self.workspace_screen(workspaces);
for (index, id) in ids.iter().enumerate() {
if let Some(record) = session.workspaces.iter().find(|record| record.id == *id) {
screen.restore_tabs(index, record);
}
}
let active = session.active.as_ref().and_then(|active| ids.iter().position(|id| id == active));
screen.set_active(active.unwrap_or(0));
told.push(ui::workspace::opened(&mut screen).map(Msg::Workspace));
self.workspace = Some(screen);
self.enter_workspace();
told.extend([self.take_focus(), self.keep_session()]);
Command::batch(told)
}
pub(super) fn enter_workspace(&mut self) {
let history = self.router.history();
let under = history.len().checked_sub(2).and_then(|place| history.get(place));
if self.page() != Page::Workspace && under == Some(&Page::Workspace) {
self.router.back();
} else {
self.router.push(Page::Workspace);
}
}
pub(super) fn keep_session(&mut self) -> Command<Msg> {
let Some(screen) = &self.workspace else { return Command::none() };
let snapshot = screen.session();
self.refresh_home();
if self.saving || self.saved.as_ref() == Some(&snapshot) {
return Command::none();
}
self.saved = Some(snapshot.clone());
let Some(path) = self.session_file.clone() else { return Command::none() };
self.saving = true;
Command::perform(move || Msg::SessionSaved(snapshot.save(&path).map_err(|error| error.to_string())))
}
pub(super) fn session_saved(&mut self, result: Result<(), String>) -> Command<Msg> {
self.saving = false;
let told = match result {
Err(reason) if !self.save_failed => {
self.save_failed = true;
Command::toast(Toast::warning(t!("app.session-unsaved")).body(reason))
}
Err(_) => Command::none(),
Ok(()) => {
self.save_failed = false;
Command::none()
}
};
Command::batch([told, self.keep_session()])
}
}