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