Fix UE launch permission, move it to context menu, refresh History after Submit
- Launch now goes through a backend launch_file command (shell `start`), so
it works for any path — fixes "Not allowed to open path" from the opener
plugin's scoped open-path.
- Removed the toolbar Launch-Unreal button; it's now a right-click item on the
Working folder ("Launch Unreal Engine"), shown only when a .uproject exists.
- Reload History after a successful Submit so the new changelist shows without
a manual F5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -610,6 +610,25 @@ async fn open_in_explorer(state: State<'_, AppState>, target: String) -> Result<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open a local file with its associated application (e.g. launch Unreal for a
|
||||||
|
/// `.uproject`). Uses the shell `start` so there are no path-scope restrictions.
|
||||||
|
#[tauri::command]
|
||||||
|
async fn launch_file(path: String) -> Result<(), String> {
|
||||||
|
if !std::path::Path::new(&path).exists() {
|
||||||
|
return Err(format!("File not found: {path}"));
|
||||||
|
}
|
||||||
|
let mut c = Command::new("cmd");
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
c.creation_flags(0x0800_0000);
|
||||||
|
}
|
||||||
|
c.args(["/C", "start", "", &path])
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("Could not launch: {e}"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Look for an Unreal `.uproject` inside the working-folder scope (resolved to a
|
/// Look for an Unreal `.uproject` inside the working-folder scope (resolved to a
|
||||||
/// local dir via `p4 where`). Checks the folder itself and one level of subdirs.
|
/// local dir via `p4 where`). Checks the folder itself and one level of subdirs.
|
||||||
/// Returns the local path of the first match, or "" if the scope isn't a UE project.
|
/// Returns the local path of the first match, or "" if the scope isn't a UE project.
|
||||||
@ -751,6 +770,7 @@ pub fn run() {
|
|||||||
p4_undo_change,
|
p4_undo_change,
|
||||||
open_in_explorer,
|
open_in_explorer,
|
||||||
find_uproject,
|
find_uproject,
|
||||||
|
launch_file,
|
||||||
p4_describe,
|
p4_describe,
|
||||||
read_file,
|
read_file,
|
||||||
read_depot,
|
read_depot,
|
||||||
|
|||||||
11
src/App.tsx
11
src/App.tsx
@ -3,7 +3,6 @@ import type { MouseEvent as ReactMouseEvent, CSSProperties, ReactNode } from "re
|
|||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { getCurrentWebview } from "@tauri-apps/api/webview";
|
import { getCurrentWebview } from "@tauri-apps/api/webview";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { openPath } from "@tauri-apps/plugin-opener";
|
|
||||||
import ModelViewer from "./ModelViewer";
|
import ModelViewer from "./ModelViewer";
|
||||||
import UpdateBanner from "./Updater";
|
import UpdateBanner from "./Updater";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
@ -504,7 +503,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
|||||||
// launch Unreal by opening the detected .uproject (file association starts UE)
|
// launch Unreal by opening the detected .uproject (file association starts UE)
|
||||||
async function launchUE() {
|
async function launchUE() {
|
||||||
if (!uproject) return;
|
if (!uproject) return;
|
||||||
try { await openPath(uproject); flash(t("Launching Unreal…")); }
|
try { await p4.launchFile(uproject); flash(t("Launching Unreal…")); }
|
||||||
catch (e) { flash(String(e), true); }
|
catch (e) { flash(String(e), true); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,6 +563,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
|||||||
if (errors.length) flash(errors.join(" · "), true);
|
if (errors.length) flash(errors.join(" · "), true);
|
||||||
else flash(t("Sent to the server: {n} changelist(s).", { n }));
|
else flash(t("Sent to the server: {n} changelist(s).", { n }));
|
||||||
await refresh();
|
await refresh();
|
||||||
|
await loadHistory(); // the submit created a new submitted changelist — refresh History
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doRevert(file: OpenedFile) {
|
async function doRevert(file: OpenedFile) {
|
||||||
@ -638,6 +638,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
|||||||
<div className="vhandle grouph" onMouseDown={(e) => startResize(e, "sync")} title={t("Drag to resize")} />
|
<div className="vhandle grouph" onMouseDown={(e) => startResize(e, "sync")} title={t("Drag to resize")} />
|
||||||
<div className="tzone" onClick={() => browseTo("")} title={t("Choose the project working folder · right-click for more")}
|
<div className="tzone" onClick={() => browseTo("")} title={t("Choose the project working folder · right-click for more")}
|
||||||
onContextMenu={(e) => openCtx(e, [
|
onContextMenu={(e) => openCtx(e, [
|
||||||
|
...(uproject ? [{ label: t("Launch Unreal Engine"), act: launchUE }] : []),
|
||||||
{ label: t("Open in Explorer"), act: () => reveal(activePath || String(info?.clientRoot || "")) },
|
{ label: t("Open in Explorer"), act: () => reveal(activePath || String(info?.clientRoot || "")) },
|
||||||
{ label: t("Choose another folder…"), act: () => browseTo("") },
|
{ label: t("Choose another folder…"), act: () => browseTo("") },
|
||||||
{ label: t("Copy path"), act: () => copyText(activePath || "//…", t("Path")) },
|
{ label: t("Copy path"), act: () => copyText(activePath || "//…", t("Path")) },
|
||||||
@ -646,12 +647,6 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
|||||||
<span className="tzmeta"><span className="lbl">{t("Working folder")}</span><span className="val">{activePath || t("whole workspace (//…)")}</span></span>
|
<span className="tzmeta"><span className="lbl">{t("Working folder")}</span><span className="val">{activePath || t("whole workspace (//…)")}</span></span>
|
||||||
<span className="chev">{I.chev}</span>
|
<span className="chev">{I.chev}</span>
|
||||||
</div>
|
</div>
|
||||||
{uproject && (
|
|
||||||
<button className="ue-btn" onClick={launchUE} title={t("Open this project in Unreal Engine")}>
|
|
||||||
<span className="ue-ic">{I.ue}</span>
|
|
||||||
<span className="ue-lbl"><span className="ue-a">{t("Launch")}</span><span className="ue-b">Unreal</span></span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{pending.length > 0 ? (
|
{pending.length > 0 ? (
|
||||||
<div className="tzone push" onClick={pushAll} title={t("Submit committed to the server (push)")}>
|
<div className="tzone push" onClick={pushAll} title={t("Submit committed to the server (push)")}>
|
||||||
<span className="ic"><svg viewBox="0 0 24 24" fill="none"><path d="M12 20V8M6 14l6-6 6 6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /></svg></span>
|
<span className="ic"><svg viewBox="0 0 24 24" fill="none"><path d="M12 20V8M6 14l6-6 6 6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /></svg></span>
|
||||||
|
|||||||
@ -68,6 +68,7 @@ const D: Record<string, Tr> = {
|
|||||||
"Working…": { ru: "Работаю…", de: "Arbeite…", fr: "En cours…", es: "Trabajando…" },
|
"Working…": { ru: "Работаю…", de: "Arbeite…", fr: "En cours…", es: "Trabajando…" },
|
||||||
"pull from server": { ru: "забрать с сервера (pull)", de: "vom Server holen (pull)", fr: "récupérer du serveur (pull)", es: "traer del servidor (pull)" },
|
"pull from server": { ru: "забрать с сервера (pull)", de: "vom Server holen (pull)", fr: "récupérer du serveur (pull)", es: "traer del servidor (pull)" },
|
||||||
"Launch": { ru: "Запустить", de: "Starten", fr: "Lancer", es: "Iniciar" },
|
"Launch": { ru: "Запустить", de: "Starten", fr: "Lancer", es: "Iniciar" },
|
||||||
|
"Launch Unreal Engine": { ru: "Запустить Unreal Engine", de: "Unreal Engine starten", fr: "Lancer Unreal Engine", es: "Iniciar Unreal Engine" },
|
||||||
"Open this project in Unreal Engine": { ru: "Открыть проект в Unreal Engine", de: "Projekt in Unreal Engine öffnen", fr: "Ouvrir le projet dans Unreal Engine", es: "Abrir el proyecto en Unreal Engine" },
|
"Open this project in Unreal Engine": { ru: "Открыть проект в Unreal Engine", de: "Projekt in Unreal Engine öffnen", fr: "Ouvrir le projet dans Unreal Engine", es: "Abrir el proyecto en Unreal Engine" },
|
||||||
"Launching Unreal…": { ru: "Запуск Unreal…", de: "Unreal wird gestartet…", fr: "Lancement d’Unreal…", es: "Iniciando Unreal…" },
|
"Launching Unreal…": { ru: "Запуск Unreal…", de: "Unreal wird gestartet…", fr: "Lancement d’Unreal…", es: "Iniciando Unreal…" },
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,7 @@ export const p4 = {
|
|||||||
undoChange: (change: string) => invoke<string>("p4_undo_change", { change }),
|
undoChange: (change: string) => invoke<string>("p4_undo_change", { change }),
|
||||||
openInExplorer: (target: string) => invoke<void>("open_in_explorer", { target }),
|
openInExplorer: (target: string) => invoke<void>("open_in_explorer", { target }),
|
||||||
findUproject: (scope: string) => invoke<string>("find_uproject", { scope }),
|
findUproject: (scope: string) => invoke<string>("find_uproject", { scope }),
|
||||||
|
launchFile: (path: string) => invoke<void>("launch_file", { path }),
|
||||||
readDepot: async (depot: string): Promise<ArrayBuffer> => {
|
readDepot: async (depot: string): Promise<ArrayBuffer> => {
|
||||||
const r: unknown = await invoke("read_depot", { depot });
|
const r: unknown = await invoke("read_depot", { depot });
|
||||||
if (r instanceof ArrayBuffer) return r;
|
if (r instanceof ArrayBuffer) return r;
|
||||||
|
|||||||
Reference in New Issue
Block a user