mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 03:27:43 +01:00
fix(view extension): broken search bar UI when opening extensions via hotkey (#938)
* fix: open view extension via hotkey * refactor: update * refactor: update * chore: check error * chore: ci update * release notes --------- Co-authored-by: ayang <473033518@qq.com> Co-authored-by: rain9 <15911122312@163.com>
This commit is contained in:
3
.github/workflows/frontend-ci.yml
vendored
3
.github/workflows/frontend-ci.yml
vendored
@@ -19,6 +19,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -32,6 +32,7 @@ fix: resolve pinned window shortcut not working #917
|
||||
fix: WM ext does not work when operating focused win from another display #919
|
||||
fix(Window Management): Next/Previous Desktop do not work #926
|
||||
fix: fix page rapidly flickering issue #935
|
||||
fix(view extension): broken search bar UI when opening extensions via hotkey #938
|
||||
|
||||
### ✈️ Improvements
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
use crate::extension::built_in::window_management::actions::Action;
|
||||
use crate::extension::{ExtensionPermission, ExtensionSettings, ViewExtensionUISettings};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as Json;
|
||||
use std::collections::HashMap;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
@@ -133,7 +134,7 @@ impl OnOpened {
|
||||
pub(crate) async fn open(
|
||||
tauri_app_handle: AppHandle,
|
||||
on_opened: OnOpened,
|
||||
extra_args: Option<HashMap<String, String>>,
|
||||
extra_args: Option<HashMap<String, Json>>,
|
||||
) -> Result<(), String> {
|
||||
use crate::util::open as homemade_tauri_shell_open;
|
||||
use std::process::Command;
|
||||
@@ -244,10 +245,17 @@ pub(crate) async fn open(
|
||||
use serde_json::Value as Json;
|
||||
use serde_json::to_value;
|
||||
|
||||
let page_and_permission: [Json; 3] = [
|
||||
let mut extra_args =
|
||||
extra_args.expect("extra_args is needed to open() a view extension");
|
||||
let document = extra_args.remove("document").expect(
|
||||
"extra argument [document] should be provided to open a view extension",
|
||||
);
|
||||
|
||||
let page_and_permission: [Json; 4] = [
|
||||
Json::String(page),
|
||||
to_value(permission).unwrap(),
|
||||
to_value(ui).unwrap(),
|
||||
document,
|
||||
];
|
||||
tauri_app_handle
|
||||
.emit("open_view_extension", page_and_permission)
|
||||
|
||||
@@ -430,7 +430,7 @@ impl QuicklinkLink {
|
||||
/// if any.
|
||||
pub(crate) fn concatenate_url(
|
||||
&self,
|
||||
user_supplied_args: &Option<HashMap<String, String>>,
|
||||
user_supplied_args: &Option<HashMap<String, Json>>,
|
||||
) -> String {
|
||||
let mut out = String::new();
|
||||
for component in self.components.iter() {
|
||||
@@ -442,20 +442,23 @@ impl QuicklinkLink {
|
||||
argument_name,
|
||||
default,
|
||||
} => {
|
||||
let opt_argument_value = {
|
||||
let opt_argument_value: Option<&str> = {
|
||||
let user_supplied_arg = user_supplied_args
|
||||
.as_ref()
|
||||
.and_then(|map| map.get(argument_name.as_str()));
|
||||
|
||||
if user_supplied_arg.is_some() {
|
||||
user_supplied_arg
|
||||
user_supplied_arg.map(|json| {
|
||||
json.as_str()
|
||||
.expect("quicklink should provide string arguments")
|
||||
})
|
||||
} else {
|
||||
default.as_ref()
|
||||
default.as_deref()
|
||||
}
|
||||
};
|
||||
|
||||
let argument_value_str = match opt_argument_value {
|
||||
Some(str) => str.as_str(),
|
||||
Some(str) => str,
|
||||
// None => an empty string
|
||||
None => "",
|
||||
};
|
||||
@@ -1763,7 +1766,7 @@ mod tests {
|
||||
],
|
||||
};
|
||||
let mut user_args = HashMap::new();
|
||||
user_args.insert("other_param".to_string(), "value".to_string());
|
||||
user_args.insert("other_param".to_string(), Json::String("value".to_string()));
|
||||
let result = link.concatenate_url(&Some(user_args));
|
||||
assert_eq!(result, "https://www.google.com/search?q=");
|
||||
}
|
||||
@@ -1800,7 +1803,7 @@ mod tests {
|
||||
],
|
||||
};
|
||||
let mut user_args = HashMap::new();
|
||||
user_args.insert("other_param".to_string(), "value".to_string());
|
||||
user_args.insert("other_param".to_string(), Json::String("value".to_string()));
|
||||
let result = link.concatenate_url(&Some(user_args));
|
||||
assert_eq!(result, "https://www.google.com/search?q=rust");
|
||||
}
|
||||
@@ -1823,7 +1826,7 @@ mod tests {
|
||||
],
|
||||
};
|
||||
let mut user_args = HashMap::new();
|
||||
user_args.insert("query".to_string(), "python".to_string());
|
||||
user_args.insert("query".to_string(), Json::String("python".to_string()));
|
||||
let result = link.concatenate_url(&Some(user_args));
|
||||
assert_eq!(result, "https://www.google.com/search?q=python");
|
||||
}
|
||||
|
||||
28
src-tauri/src/extension/third_party/mod.rs
vendored
28
src-tauri/src/extension/third_party/mod.rs
vendored
@@ -23,6 +23,7 @@ use async_trait::async_trait;
|
||||
use borrowme::ToOwned;
|
||||
use check::general_check;
|
||||
use function_name::named;
|
||||
use std::collections::HashMap;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
@@ -524,6 +525,24 @@ impl ThirdPartyExtensionsSearchSource {
|
||||
let on_opened = extension.on_opened().unwrap_or_else(|| panic!(
|
||||
"setting hotkey for an extension that cannot be opened, extension ID [{:?}], extension type [{:?}]", bundle_id, extension.r#type,
|
||||
));
|
||||
let url = on_opened.url();
|
||||
let extension_type_string = extension.r#type.to_string();
|
||||
let document = Document {
|
||||
id: extension.id.clone(),
|
||||
title: Some(extension.name.clone()),
|
||||
icon: Some(extension.icon.clone()),
|
||||
on_opened: Some(on_opened.clone()),
|
||||
url: Some(url),
|
||||
category: Some(extension_type_string.clone()),
|
||||
source: Some(DataSourceReference {
|
||||
id: Some(extension_type_string.clone()),
|
||||
name: Some(extension_type_string.clone()),
|
||||
icon: None,
|
||||
r#type: Some(extension_type_string),
|
||||
}),
|
||||
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let bundle_id_owned = bundle_id.to_owned();
|
||||
tauri_app_handle
|
||||
@@ -533,9 +552,16 @@ impl ThirdPartyExtensionsSearchSource {
|
||||
let bundle_id_clone = bundle_id_owned.clone();
|
||||
let app_handle_clone = tauri_app_handle.clone();
|
||||
|
||||
let document_clone = document.clone();
|
||||
if event.state() == ShortcutState::Pressed {
|
||||
async_runtime::spawn(async move {
|
||||
let result = open(app_handle_clone, on_opened_clone, None).await;
|
||||
let mut args = HashMap::new();
|
||||
args.insert(
|
||||
String::from("document"),
|
||||
serde_json::to_value(&document_clone).unwrap(),
|
||||
);
|
||||
|
||||
let result = open(app_handle_clone, on_opened_clone, Some(args)).await;
|
||||
if let Err(msg) = result {
|
||||
log::warn!(
|
||||
"failed to open extension [{:?}], error [{}]",
|
||||
|
||||
@@ -185,18 +185,17 @@ export function useAssistantManager({
|
||||
const onOpened = selectedSearchContent?.on_opened;
|
||||
|
||||
if (onOpened?.Extension?.ty?.View) {
|
||||
const { setViewExtensionOpened, setViewExtensionData } =
|
||||
useSearchStore.getState();
|
||||
const { setViewExtensionOpened } = useSearchStore.getState();
|
||||
const viewData = onOpened.Extension.ty.View;
|
||||
const extensionPermission = onOpened.Extension.permission;
|
||||
|
||||
clearSearchValue();
|
||||
setViewExtensionOpened([
|
||||
return setViewExtensionOpened([
|
||||
viewData.page,
|
||||
extensionPermission,
|
||||
viewData.ui,
|
||||
selectedSearchContent as any,
|
||||
]);
|
||||
return setViewExtensionData(selectedSearchContent as any);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ export default function SearchIcons({
|
||||
visibleExtensionDetail,
|
||||
selectedExtension,
|
||||
viewExtensionOpened,
|
||||
viewExtensionData,
|
||||
} = useSearchStore();
|
||||
|
||||
if (isChatMode) {
|
||||
@@ -106,8 +105,8 @@ export default function SearchIcons({
|
||||
return <MultilevelWrapper title={name} icon={icon} />;
|
||||
}
|
||||
|
||||
if (viewExtensionOpened && viewExtensionData) {
|
||||
const { title, icon } = viewExtensionData;
|
||||
if (viewExtensionOpened) {
|
||||
const { title, icon } = viewExtensionOpened[3];
|
||||
|
||||
const iconPath = icon ? platformAdapter.convertFileSrc(icon) : void 0;
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ import {
|
||||
FileSystemAccess,
|
||||
} from "../Settings/Extensions";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||
|
||||
const ViewExtension: React.FC = () => {
|
||||
const { viewExtensionOpened } = useSearchStore();
|
||||
const [page, setPage] = useState<string>("");
|
||||
// Complete list of the backend APIs, grouped by their category.
|
||||
const [apis, setApis] = useState<Map<string, string[]> | null>(null);
|
||||
const { setModifierKeyPressed } = useShortcutsStore();
|
||||
|
||||
if (viewExtensionOpened == null) {
|
||||
// When this view gets loaded, this state should not be NULL.
|
||||
@@ -29,12 +31,11 @@ const ViewExtension: React.FC = () => {
|
||||
const page = viewExtensionOpened[0];
|
||||
|
||||
// Only convert to file source if it's a local file path, not a URL
|
||||
if (page.startsWith('http://') || page.startsWith('https://')) {
|
||||
if (page.startsWith("http://") || page.startsWith("https://")) {
|
||||
setPage(page);
|
||||
} else {
|
||||
setPage(platformAdapter.convertFileSrc(page));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
setupFileUrl();
|
||||
@@ -42,6 +43,8 @@ const ViewExtension: React.FC = () => {
|
||||
|
||||
// invoke `apis()` and set the state
|
||||
useEffect(() => {
|
||||
setModifierKeyPressed(false);
|
||||
|
||||
const fetchApis = async () => {
|
||||
try {
|
||||
const availableApis = (await platformAdapter.invokeBackend(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ExtensionId } from "@/components/Settings/Extensions";
|
||||
import { create } from "zustand";
|
||||
import { persist, subscribeWithSelector } from "zustand/middleware";
|
||||
|
||||
import { ExtensionId } from "@/components/Settings/Extensions";
|
||||
|
||||
export type IExtensionsStore = {
|
||||
quickAiAccessServer?: any;
|
||||
setQuickAiAccessServer: (quickAiAccessServer?: any) => void;
|
||||
|
||||
@@ -7,9 +7,12 @@ import { SearchDocument } from "@/types/search";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
export type ViewExtensionOpened =
|
||||
| [string, ExtensionPermission | null, ViewExtensionUISettings | null]
|
||||
| null;
|
||||
export type ViewExtensionOpened = [
|
||||
string,
|
||||
ExtensionPermission | null,
|
||||
ViewExtensionUISettings | null,
|
||||
SearchDocument
|
||||
];
|
||||
|
||||
export type ISearchStore = {
|
||||
sourceData: any;
|
||||
@@ -58,11 +61,8 @@ export type ISearchStore = {
|
||||
// The first array element is the path to the page that we should load
|
||||
// The second element is the permission that this extension requires.
|
||||
// The third argument is the UI Settings
|
||||
viewExtensionOpened: ViewExtensionOpened;
|
||||
setViewExtensionOpened: (showViewExtension: ViewExtensionOpened) => void;
|
||||
|
||||
viewExtensionData?: SearchDocument;
|
||||
setViewExtensionData: (viewExtensionData?: SearchDocument) => void;
|
||||
viewExtensionOpened?: ViewExtensionOpened;
|
||||
setViewExtensionOpened: (showViewExtension?: ViewExtensionOpened) => void;
|
||||
};
|
||||
|
||||
export const useSearchStore = create<ISearchStore>()(
|
||||
@@ -128,13 +128,9 @@ export const useSearchStore = create<ISearchStore>()(
|
||||
setVisibleExtensionDetail: (visibleExtensionDetail) => {
|
||||
return set({ visibleExtensionDetail });
|
||||
},
|
||||
viewExtensionOpened: null,
|
||||
setViewExtensionOpened: (viewExtensionOpened) => {
|
||||
return set({ viewExtensionOpened });
|
||||
},
|
||||
setViewExtensionData(viewExtensionData) {
|
||||
return set({ viewExtensionData });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "search-store",
|
||||
|
||||
@@ -240,13 +240,11 @@ export const navigateBack = () => {
|
||||
visibleExtensionStore,
|
||||
visibleExtensionDetail,
|
||||
viewExtensionOpened,
|
||||
viewExtensionData,
|
||||
setGoAskAi,
|
||||
setVisibleExtensionDetail,
|
||||
setVisibleExtensionStore,
|
||||
setSourceData,
|
||||
setViewExtensionOpened,
|
||||
setViewExtensionData,
|
||||
} = useSearchStore.getState();
|
||||
|
||||
if (goAskAi) {
|
||||
@@ -261,10 +259,8 @@ export const navigateBack = () => {
|
||||
return setVisibleExtensionStore(false);
|
||||
}
|
||||
|
||||
if (viewExtensionOpened || viewExtensionData) {
|
||||
setViewExtensionData(void 0);
|
||||
|
||||
return setViewExtensionOpened(null);
|
||||
if (viewExtensionOpened) {
|
||||
return setViewExtensionOpened(void 0);
|
||||
}
|
||||
|
||||
setSourceData(void 0);
|
||||
|
||||
Reference in New Issue
Block a user