feat: add update check functionality and improve menu options for macOS
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Report a reproducible problem
|
||||||
|
title: "bug: "
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to report a bug.
|
||||||
|
|
||||||
|
Please include description/screenshots if possible.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: What did you expect, what happened instead?
|
||||||
|
placeholder: Steps + expected vs actual
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: repro
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: Numbered steps help a lot
|
||||||
|
placeholder: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: runtime
|
||||||
|
attributes:
|
||||||
|
label: Runtime
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Desktop MacOS app
|
||||||
|
- Mobile (Web/PWA)
|
||||||
|
- Desktop Web
|
||||||
|
- VS Code extension
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: OpenChamber version
|
||||||
|
placeholder: "e.g. 1.2.3"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Logs / screenshots
|
||||||
|
description: Paste relevant logs or attach screenshots
|
||||||
|
render: shell
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: GitHub Discussions
|
||||||
|
url: https://github.com/btriapitsyn/openchamber/discussions
|
||||||
|
about: Ask questions and share ideas
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an improvement or new capability
|
||||||
|
title: "feat: "
|
||||||
|
labels: [enhancement]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for proposing an idea.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Problem / use case
|
||||||
|
description: What are you trying to do?
|
||||||
|
placeholder: "As a user, I want..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: proposal
|
||||||
|
attributes:
|
||||||
|
label: Proposed solution
|
||||||
|
description: What would you like to see happen?
|
||||||
|
placeholder: UI, behavior, config, etc.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives considered
|
||||||
|
placeholder: "Other approaches you've tried/considered"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Links, screenshots, mockups, etc.
|
||||||
Generated
+1
-1
@@ -2847,7 +2847,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openchamber-desktop"
|
name = "openchamber-desktop"
|
||||||
version = "1.2.2"
|
version = "1.2.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
|||||||
@@ -79,6 +79,18 @@ const MODELS_DEV_API_URL: &str = "https://models.dev/api.json";
|
|||||||
const MODELS_METADATA_CACHE_TTL: Duration = Duration::from_secs(5 * 60);
|
const MODELS_METADATA_CACHE_TTL: Duration = Duration::from_secs(5 * 60);
|
||||||
const MODELS_METADATA_REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
|
const MODELS_METADATA_REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
|
||||||
|
|
||||||
|
const CHECK_FOR_UPDATES_EVENT: &str = "openchamber:check-for-updates";
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const MENU_ITEM_CHECK_FOR_UPDATES_ID: &str = "openchamber_check_for_updates";
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const MENU_ITEM_REPORT_BUG_ID: &str = "openchamber_report_bug";
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
const MENU_ITEM_REQUEST_FEATURE_ID: &str = "openchamber_request_feature";
|
||||||
|
|
||||||
|
const GITHUB_BUG_REPORT_URL: &str = "https://github.com/btriapitsyn/openchamber/issues/new?template=bug_report.yml";
|
||||||
|
const GITHUB_FEATURE_REQUEST_URL: &str = "https://github.com/btriapitsyn/openchamber/issues/new?template=feature_request.yml";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct DesktopRuntime {
|
pub(crate) struct DesktopRuntime {
|
||||||
server_port: u16,
|
server_port: u16,
|
||||||
@@ -277,6 +289,116 @@ fn prevent_app_nap() {
|
|||||||
info!("[macos] App Nap prevention enabled via objc2");
|
info!("[macos] App Nap prevention enabled via objc2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn build_macos_menu<R: tauri::Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<tauri::menu::Menu<R>> {
|
||||||
|
use tauri::menu::{AboutMetadata, Menu, MenuItem, PredefinedMenuItem, Submenu, HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
|
||||||
|
|
||||||
|
let pkg_info = app.package_info();
|
||||||
|
let config = app.config();
|
||||||
|
let about_metadata = AboutMetadata {
|
||||||
|
name: Some(pkg_info.name.clone()),
|
||||||
|
version: Some(pkg_info.version.to_string()),
|
||||||
|
copyright: config.bundle.copyright.clone(),
|
||||||
|
authors: config.bundle.publisher.clone().map(|p| vec![p]),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let check_for_updates = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
MENU_ITEM_CHECK_FOR_UPDATES_ID,
|
||||||
|
"Check for Updates…",
|
||||||
|
true,
|
||||||
|
None::<&str>,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let report_bug = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
MENU_ITEM_REPORT_BUG_ID,
|
||||||
|
"Report a Bug…",
|
||||||
|
true,
|
||||||
|
None::<&str>,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let request_feature = MenuItem::with_id(
|
||||||
|
app,
|
||||||
|
MENU_ITEM_REQUEST_FEATURE_ID,
|
||||||
|
"Request a Feature…",
|
||||||
|
true,
|
||||||
|
None::<&str>,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let window_menu = Submenu::with_id_and_items(
|
||||||
|
app,
|
||||||
|
WINDOW_SUBMENU_ID,
|
||||||
|
"Window",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&PredefinedMenuItem::minimize(app, None)?,
|
||||||
|
&PredefinedMenuItem::maximize(app, None)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&PredefinedMenuItem::close_window(app, None)?,
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let help_menu = Submenu::with_id_and_items(
|
||||||
|
app,
|
||||||
|
HELP_SUBMENU_ID,
|
||||||
|
"Help",
|
||||||
|
true,
|
||||||
|
&[&report_bug, &request_feature],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Menu::with_items(
|
||||||
|
app,
|
||||||
|
&[
|
||||||
|
&Submenu::with_items(
|
||||||
|
app,
|
||||||
|
pkg_info.name.clone(),
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&PredefinedMenuItem::about(app, None, Some(about_metadata))?,
|
||||||
|
&check_for_updates,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&PredefinedMenuItem::services(app, None)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&PredefinedMenuItem::hide(app, None)?,
|
||||||
|
&PredefinedMenuItem::hide_others(app, None)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&PredefinedMenuItem::quit(app, None)?,
|
||||||
|
],
|
||||||
|
)?,
|
||||||
|
&Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"File",
|
||||||
|
true,
|
||||||
|
&[&PredefinedMenuItem::close_window(app, None)?],
|
||||||
|
)?,
|
||||||
|
&Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"Edit",
|
||||||
|
true,
|
||||||
|
&[
|
||||||
|
&PredefinedMenuItem::undo(app, None)?,
|
||||||
|
&PredefinedMenuItem::redo(app, None)?,
|
||||||
|
&PredefinedMenuItem::separator(app)?,
|
||||||
|
&PredefinedMenuItem::cut(app, None)?,
|
||||||
|
&PredefinedMenuItem::copy(app, None)?,
|
||||||
|
&PredefinedMenuItem::paste(app, None)?,
|
||||||
|
&PredefinedMenuItem::select_all(app, None)?,
|
||||||
|
],
|
||||||
|
)?,
|
||||||
|
&Submenu::with_items(
|
||||||
|
app,
|
||||||
|
"View",
|
||||||
|
true,
|
||||||
|
&[&PredefinedMenuItem::fullscreen(app, None)?],
|
||||||
|
)?,
|
||||||
|
&window_menu,
|
||||||
|
&help_menu,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut log_builder = tauri_plugin_log::Builder::default()
|
let mut log_builder = tauri_plugin_log::Builder::default()
|
||||||
.level(log::LevelFilter::Info)
|
.level(log::LevelFilter::Info)
|
||||||
@@ -299,6 +421,16 @@ fn main() {
|
|||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(log_builder.build())
|
.plugin(log_builder.build())
|
||||||
|
.menu(|app| {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
return build_macos_menu(app);
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
return tauri::menu::Menu::default(app);
|
||||||
|
}
|
||||||
|
})
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
prevent_app_nap();
|
prevent_app_nap();
|
||||||
@@ -418,6 +550,33 @@ fn main() {
|
|||||||
fetch_desktop_logs,
|
fetch_desktop_logs,
|
||||||
desktop_notify,
|
desktop_notify,
|
||||||
])
|
])
|
||||||
|
.on_menu_event(|app, event| {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if event.id() == MENU_ITEM_CHECK_FOR_UPDATES_ID {
|
||||||
|
let _ = app.emit(CHECK_FOR_UPDATES_EVENT, ());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.id() == MENU_ITEM_REPORT_BUG_ID {
|
||||||
|
use tauri_plugin_shell::ShellExt;
|
||||||
|
#[allow(deprecated)]
|
||||||
|
{
|
||||||
|
let _ = app.shell().open(GITHUB_BUG_REPORT_URL, None);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.id() == MENU_ITEM_REQUEST_FEATURE_ID {
|
||||||
|
use tauri_plugin_shell::ShellExt;
|
||||||
|
#[allow(deprecated)]
|
||||||
|
{
|
||||||
|
let _ = app.shell().open(GITHUB_FEATURE_REQUEST_URL, None);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.on_window_event(|window, event| {
|
.on_window_event(|window, event| {
|
||||||
let window_state_manager = window.state::<WindowStateManager>().inner().clone();
|
let window_state_manager = window.state::<WindowStateManager>().inner().clone();
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CHECK_FOR_UPDATES_EVENT = 'openchamber:check-for-updates';
|
||||||
|
|
||||||
const cleanupFunctions: Array<() => void | Promise<void>> = [];
|
const cleanupFunctions: Array<() => void | Promise<void>> = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -55,6 +57,11 @@ try {
|
|||||||
});
|
});
|
||||||
cleanupFunctions.push(() => activityUnlisten());
|
cleanupFunctions.push(() => activityUnlisten());
|
||||||
|
|
||||||
|
const updateCheckUnlisten = await listen(CHECK_FOR_UPDATES_EVENT, () => {
|
||||||
|
window.dispatchEvent(new CustomEvent(CHECK_FOR_UPDATES_EVENT));
|
||||||
|
});
|
||||||
|
cleanupFunctions.push(() => updateCheckUnlisten());
|
||||||
|
|
||||||
requestInitialNotificationPermission().catch(err => {
|
requestInitialNotificationPermission().catch(err => {
|
||||||
console.error('[main] Failed to request notification permission:', err);
|
console.error('[main] Failed to request notification permission:', err);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const SIDEBAR_CONTENT_WIDTH = 264;
|
|||||||
const SIDEBAR_MIN_WIDTH = 200;
|
const SIDEBAR_MIN_WIDTH = 200;
|
||||||
const SIDEBAR_MAX_WIDTH = 500;
|
const SIDEBAR_MAX_WIDTH = 500;
|
||||||
const MAC_TITLEBAR_SAFE_AREA = 40;
|
const MAC_TITLEBAR_SAFE_AREA = 40;
|
||||||
|
const CHECK_FOR_UPDATES_EVENT = 'openchamber:check-for-updates';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -25,6 +26,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children })
|
|||||||
const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false);
|
const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false);
|
||||||
|
|
||||||
const update = useUpdateCheck();
|
const update = useUpdateCheck();
|
||||||
|
const pendingMenuUpdateCheckRef = React.useRef(false);
|
||||||
|
|
||||||
|
const checkForUpdates = update.checkForUpdates;
|
||||||
|
const { available, downloaded, checking } = update;
|
||||||
|
|
||||||
const [isDesktopApp, setIsDesktopApp] = React.useState<boolean>(() => {
|
const [isDesktopApp, setIsDesktopApp] = React.useState<boolean>(() => {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
@@ -48,6 +53,41 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children })
|
|||||||
setIsDesktopApp(detected);
|
setIsDesktopApp(detected);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMenuUpdateCheck = () => {
|
||||||
|
const hasDesktopApi =
|
||||||
|
typeof (window as typeof window & { opencodeDesktop?: unknown }).opencodeDesktop !== 'undefined';
|
||||||
|
if (!hasDesktopApi) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingMenuUpdateCheckRef.current = true;
|
||||||
|
void checkForUpdates();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener(CHECK_FOR_UPDATES_EVENT, handleMenuUpdateCheck as EventListener);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(CHECK_FOR_UPDATES_EVENT, handleMenuUpdateCheck as EventListener);
|
||||||
|
};
|
||||||
|
}, [checkForUpdates]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!pendingMenuUpdateCheckRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (checking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (available || downloaded) {
|
||||||
|
setUpdateDialogOpen(true);
|
||||||
|
}
|
||||||
|
pendingMenuUpdateCheckRef.current = false;
|
||||||
|
}, [available, downloaded, checking]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isMobile || !isResizing) {
|
if (isMobile || !isResizing) {
|
||||||
return;
|
return;
|
||||||
@@ -184,7 +224,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children })
|
|||||||
<RiSettings3Line className="h-4 w-4" />
|
<RiSettings3Line className="h-4 w-4" />
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
</button>
|
</button>
|
||||||
{(update.available || update.downloaded) && (
|
{(available || downloaded) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setUpdateDialogOpen(true)}
|
onClick={() => setUpdateDialogOpen(true)}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
Reference in New Issue
Block a user