mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-20 07:41:22 +02:00
Beta channel updates
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#![cfg_attr(
|
#![cfg_attr(
|
||||||
all(not(debug_assertions), target_os = "windows"),
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -32,16 +32,16 @@ use window_ext::TrafficLightWindowExt;
|
|||||||
use crate::analytics::{AnalyticsAction, AnalyticsResource, track_event};
|
use crate::analytics::{AnalyticsAction, AnalyticsResource, track_event};
|
||||||
use crate::plugin::{ImportResources, ImportResult};
|
use crate::plugin::{ImportResources, ImportResult};
|
||||||
use crate::send::actually_send_request;
|
use crate::send::actually_send_request;
|
||||||
use crate::updates::YaakUpdater;
|
use crate::updates::{update_mode_from_str, UpdateMode, YaakUpdater};
|
||||||
|
|
||||||
mod analytics;
|
mod analytics;
|
||||||
mod models;
|
mod models;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod send;
|
||||||
|
mod updates;
|
||||||
mod window_ext;
|
mod window_ext;
|
||||||
mod window_menu;
|
mod window_menu;
|
||||||
mod updates;
|
|
||||||
mod send;
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct CustomResponse {
|
pub struct CustomResponse {
|
||||||
@@ -100,7 +100,7 @@ async fn import_data(
|
|||||||
plugin_name,
|
plugin_name,
|
||||||
file_paths.first().unwrap(),
|
file_paths.first().unwrap(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
result = Some(r);
|
result = Some(r);
|
||||||
break;
|
break;
|
||||||
@@ -196,8 +196,12 @@ async fn send_request(
|
|||||||
let pool2 = pool.clone();
|
let pool2 = pool.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = actually_send_request(req, &response2, &environment_id2, &app_handle2, &pool2).await {
|
if let Err(e) =
|
||||||
response_err(&response2, e, &app_handle2, &pool2).await.expect("Failed to update response");
|
actually_send_request(req, &response2, &environment_id2, &app_handle2, &pool2).await
|
||||||
|
{
|
||||||
|
response_err(&response2, e, &app_handle2, &pool2)
|
||||||
|
.await
|
||||||
|
.expect("Failed to update response");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -220,6 +224,15 @@ async fn response_err(
|
|||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn set_update_mode(
|
||||||
|
update_mode: &str,
|
||||||
|
window: Window<Wry>,
|
||||||
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
|
) -> Result<models::KeyValue, String> {
|
||||||
|
set_key_value("app", "update_mode", update_mode, window, db_instance).await
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_key_value(
|
async fn get_key_value(
|
||||||
namespace: &str,
|
namespace: &str,
|
||||||
@@ -263,8 +276,8 @@ async fn create_workspace(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create Workspace");
|
.expect("Failed to create Workspace");
|
||||||
|
|
||||||
emit_and_return(&window, "created_model", created_workspace)
|
emit_and_return(&window, "created_model", created_workspace)
|
||||||
}
|
}
|
||||||
@@ -287,8 +300,8 @@ async fn create_environment(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create environment");
|
.expect("Failed to create environment");
|
||||||
|
|
||||||
emit_and_return(&window, "created_model", created_environment)
|
emit_and_return(&window, "created_model", created_environment)
|
||||||
}
|
}
|
||||||
@@ -314,8 +327,8 @@ async fn create_request(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create request");
|
.expect("Failed to create request");
|
||||||
|
|
||||||
emit_and_return(&window, "created_model", created_request)
|
emit_and_return(&window, "created_model", created_request)
|
||||||
}
|
}
|
||||||
@@ -420,8 +433,8 @@ async fn create_folder(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create folder");
|
.expect("Failed to create folder");
|
||||||
|
|
||||||
emit_and_return(&window, "created_model", created_request)
|
emit_and_return(&window, "created_model", created_request)
|
||||||
}
|
}
|
||||||
@@ -586,8 +599,8 @@ async fn list_workspaces(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to create Workspace");
|
.expect("Failed to create Workspace");
|
||||||
Ok(vec![workspace])
|
Ok(vec![workspace])
|
||||||
} else {
|
} else {
|
||||||
Ok(workspaces)
|
Ok(workspaces)
|
||||||
@@ -614,9 +627,19 @@ async fn delete_workspace(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn check_for_updates(app_handle: AppHandle<Wry>, yaak_updater: State<'_, Mutex<YaakUpdater>>,
|
async fn check_for_updates(
|
||||||
|
app_handle: AppHandle<Wry>,
|
||||||
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
|
yaak_updater: State<'_, Mutex<YaakUpdater>>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
yaak_updater.lock().await.check(&app_handle).await.map_err(|e| e.to_string())
|
let pool = &*db_instance.lock().await;
|
||||||
|
let update_mode = get_update_mode(pool).await;
|
||||||
|
yaak_updater
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.force_check(&app_handle, update_mode)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -659,7 +682,6 @@ fn main() {
|
|||||||
.expect("Failed to migrate database");
|
.expect("Failed to migrate database");
|
||||||
app.manage(m);
|
app.manage(m);
|
||||||
|
|
||||||
|
|
||||||
let yaak_updater = YaakUpdater::new();
|
let yaak_updater = YaakUpdater::new();
|
||||||
app.manage(Mutex::new(yaak_updater));
|
app.manage(Mutex::new(yaak_updater));
|
||||||
|
|
||||||
@@ -697,6 +719,7 @@ fn main() {
|
|||||||
send_ephemeral_request,
|
send_ephemeral_request,
|
||||||
send_request,
|
send_request,
|
||||||
set_key_value,
|
set_key_value,
|
||||||
|
set_update_mode,
|
||||||
update_environment,
|
update_environment,
|
||||||
update_folder,
|
update_folder,
|
||||||
update_request,
|
update_request,
|
||||||
@@ -740,12 +763,19 @@ fn main() {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
RunEvent::WindowEvent { label: _label, event: WindowEvent::Focused(true), .. } => {
|
RunEvent::WindowEvent {
|
||||||
|
label: _label,
|
||||||
|
event: WindowEvent::Focused(true),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
let h = app_handle.clone();
|
let h = app_handle.clone();
|
||||||
// Run update check whenever window is focused
|
// Run update check whenever window is focused
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let val: State<'_, Mutex<YaakUpdater>> = h.state();
|
let val: State<'_, Mutex<YaakUpdater>> = h.state();
|
||||||
_ = val.lock().await.check(&h).await;
|
let db_instance: State<'_, Mutex<Pool<Sqlite>>> = h.state();
|
||||||
|
let pool = &*db_instance.lock().await;
|
||||||
|
let update_mode = get_update_mode(pool).await;
|
||||||
|
_ = val.lock().await.check(&h, update_mode).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -783,16 +813,16 @@ fn create_window(handle: &AppHandle<Wry>, url: Option<&str>) -> Window<Wry> {
|
|||||||
window_id,
|
window_id,
|
||||||
WindowUrl::App(url.unwrap_or_default().into()),
|
WindowUrl::App(url.unwrap_or_default().into()),
|
||||||
)
|
)
|
||||||
.menu(app_menu)
|
.menu(app_menu)
|
||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.inner_size(1100.0, 600.0)
|
.inner_size(1100.0, 600.0)
|
||||||
.position(
|
.position(
|
||||||
// Randomly offset so windows don't stack exactly
|
// Randomly offset so windows don't stack exactly
|
||||||
100.0 + random::<f64>() * 30.0,
|
100.0 + random::<f64>() * 30.0,
|
||||||
100.0 + random::<f64>() * 30.0,
|
100.0 + random::<f64>() * 30.0,
|
||||||
)
|
)
|
||||||
.title(handle.package_info().name.to_string());
|
.title(handle.package_info().name.to_string());
|
||||||
|
|
||||||
// Add macOS-only things
|
// Add macOS-only things
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -868,3 +898,12 @@ fn emit_and_return<S: Serialize + Clone, E>(
|
|||||||
fn emit_side_effect<S: Serialize + Clone>(app_handle: &AppHandle<Wry>, event: &str, payload: S) {
|
fn emit_side_effect<S: Serialize + Clone>(app_handle: &AppHandle<Wry>, event: &str, payload: S) {
|
||||||
app_handle.emit_all(event, &payload).unwrap();
|
app_handle.emit_all(event, &payload).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_update_mode(pool: &Pool<Sqlite>) -> UpdateMode {
|
||||||
|
let mode = models::get_key_value_string("app", "update_mode", pool)
|
||||||
|
.await;
|
||||||
|
match mode {
|
||||||
|
Some(mode) => update_mode_from_str(&mode),
|
||||||
|
None => UpdateMode::Stable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -193,6 +193,18 @@ pub async fn get_key_value(namespace: &str, key: &str, pool: &Pool<Sqlite>) -> O
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_key_value_string(namespace: &str, key: &str, pool: &Pool<Sqlite>) -> Option<String> {
|
||||||
|
let kv = get_key_value(namespace, key, pool).await?;
|
||||||
|
let result = serde_json::from_str(&kv.value);
|
||||||
|
match result {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to parse key value: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn find_workspaces(pool: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx::Error> {
|
pub async fn find_workspaces(pool: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Workspace,
|
Workspace,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use log::info;
|
||||||
use tauri::{AppHandle, updater, Window, Wry};
|
use tauri::{AppHandle, updater, Window, Wry};
|
||||||
use tauri::api::dialog;
|
use tauri::api::dialog;
|
||||||
|
|
||||||
@@ -11,19 +12,31 @@ pub struct YaakUpdater {
|
|||||||
last_update_check: SystemTime,
|
last_update_check: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum UpdateMode {
|
||||||
|
Stable,
|
||||||
|
Beta,
|
||||||
|
}
|
||||||
|
|
||||||
impl YaakUpdater {
|
impl YaakUpdater {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
last_update_check: SystemTime::UNIX_EPOCH,
|
last_update_check: SystemTime::UNIX_EPOCH,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn check(&mut self, app_handle: &AppHandle<Wry>) -> Result<(), updater::Error> {
|
pub async fn force_check(
|
||||||
if self.last_update_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS {
|
&mut self,
|
||||||
return Ok(());
|
app_handle: &AppHandle<Wry>,
|
||||||
}
|
mode: UpdateMode,
|
||||||
|
) -> Result<(), updater::Error> {
|
||||||
self.last_update_check = SystemTime::now();
|
self.last_update_check = SystemTime::now();
|
||||||
match app_handle.updater().check().await {
|
let update_mode = get_update_mode_str(mode);
|
||||||
|
info!("Checking for updates mode={}", update_mode);
|
||||||
|
match app_handle
|
||||||
|
.updater()
|
||||||
|
.header("X-Update-Mode", update_mode)?
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(update) => {
|
Ok(update) => {
|
||||||
if dialog::blocking::ask(
|
if dialog::blocking::ask(
|
||||||
None::<&Window>,
|
None::<&Window>,
|
||||||
@@ -38,4 +51,29 @@ impl YaakUpdater {
|
|||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub async fn check(
|
||||||
|
&mut self,
|
||||||
|
app_handle: &AppHandle<Wry>,
|
||||||
|
mode: UpdateMode,
|
||||||
|
) -> Result<(), updater::Error> {
|
||||||
|
if self.last_update_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.force_check(app_handle, mode).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_mode_from_str(mode: &str) -> UpdateMode {
|
||||||
|
match mode {
|
||||||
|
"beta" => UpdateMode::Beta,
|
||||||
|
_ => UpdateMode::Stable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_update_mode_str(mode: UpdateMode) -> &'static str {
|
||||||
|
match mode {
|
||||||
|
UpdateMode::Stable => "stable",
|
||||||
|
UpdateMode::Beta => "beta",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useImportData } from '../hooks/useImportData';
|
|||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
|
import { useUpdateMode } from '../hooks/useUpdateMode';
|
||||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||||
import type { ButtonProps } from './core/Button';
|
import type { ButtonProps } from './core/Button';
|
||||||
@@ -39,6 +40,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
|
const [updateMode, setUpdateMode] = useUpdateMode();
|
||||||
|
|
||||||
const items: DropdownItem[] = useMemo(() => {
|
const items: DropdownItem[] = useMemo(() => {
|
||||||
const workspaceItems: DropdownItem[] = workspaces.map((w) => ({
|
const workspaceItems: DropdownItem[] = workspaces.map((w) => ({
|
||||||
@@ -145,12 +147,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
createWorkspace.mutate({ name });
|
createWorkspace.mutate({ name });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'appearance',
|
|
||||||
label: 'Toggle Theme',
|
|
||||||
onSelect: toggleAppearance,
|
|
||||||
leftSlot: <Icon icon={appearance === 'dark' ? 'sun' : 'moon'} />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'import-data',
|
key: 'import-data',
|
||||||
label: 'Import Data',
|
label: 'Import Data',
|
||||||
@@ -163,6 +159,25 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
leftSlot: <Icon icon="upload" />,
|
leftSlot: <Icon icon="upload" />,
|
||||||
onSelect: () => exportData.mutate(),
|
onSelect: () => exportData.mutate(),
|
||||||
},
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
key: 'appearance',
|
||||||
|
label: 'Toggle Theme',
|
||||||
|
onSelect: toggleAppearance,
|
||||||
|
leftSlot: <Icon icon={appearance === 'dark' ? 'sun' : 'moon'} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'update-mode',
|
||||||
|
label: updateMode === 'stable' ? 'Enable Beta' : 'Disable Beta',
|
||||||
|
onSelect: () => setUpdateMode(updateMode === 'stable' ? 'beta' : 'stable'),
|
||||||
|
leftSlot: <Icon icon="camera" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'update-check',
|
||||||
|
label: 'Check for Updates',
|
||||||
|
onSelect: () => invoke('check_for_updates'),
|
||||||
|
leftSlot: <Icon icon="update" />,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}, [
|
}, [
|
||||||
activeWorkspace?.name,
|
activeWorkspace?.name,
|
||||||
@@ -175,7 +190,9 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
importData,
|
importData,
|
||||||
prompt,
|
prompt,
|
||||||
routes,
|
routes,
|
||||||
|
setUpdateMode,
|
||||||
toggleAppearance,
|
toggleAppearance,
|
||||||
|
updateMode,
|
||||||
updateWorkspace,
|
updateWorkspace,
|
||||||
workspaces,
|
workspaces,
|
||||||
]);
|
]);
|
||||||
|
|||||||
12
src-web/hooks/useUpdateMode.ts
Normal file
12
src-web/hooks/useUpdateMode.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { NAMESPACE_APP } from '../lib/keyValueStore';
|
||||||
|
import { useKeyValue } from './useKeyValue';
|
||||||
|
|
||||||
|
export function useUpdateMode() {
|
||||||
|
const kv = useKeyValue<'stable' | 'beta'>({
|
||||||
|
namespace: NAMESPACE_APP,
|
||||||
|
key: 'update_mode',
|
||||||
|
defaultValue: 'stable',
|
||||||
|
});
|
||||||
|
|
||||||
|
return [kv.value, kv.set] as const;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import type { KeyValue } from './models';
|
import type { KeyValue } from './models';
|
||||||
|
|
||||||
|
export const NAMESPACE_APP = 'app';
|
||||||
export const NAMESPACE_GLOBAL = 'global';
|
export const NAMESPACE_GLOBAL = 'global';
|
||||||
export const NAMESPACE_NO_SYNC = 'no_sync';
|
export const NAMESPACE_NO_SYNC = 'no_sync';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user