Add Build Solution (MSBuild) with live output
find_sln locates a .sln in the working folder; build_sln locates MSBuild via vswhere and builds the solution's default config (compiles the UE game module for Unreal projects), streaming output as build-log events. A build overlay shows the live log with error/warning coloring and a status. Wired into Tools (Ctrl+B) and the Working-folder right-click menu when a .sln is found. New strings translated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -651,52 +651,141 @@ async fn launch_file(path: String) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Returns the local path of the first match, or "" if the scope isn't a UE project.
|
||||
#[tauri::command]
|
||||
async fn find_uproject(state: State<'_, AppState>, scope: String) -> Result<String, String> {
|
||||
/// Resolve a working-folder scope to a local directory via `p4 where`.
|
||||
fn scope_local_dir(conn: &Conn, scope: &str) -> Option<String> {
|
||||
if scope.is_empty() {
|
||||
return Ok(String::new());
|
||||
return None;
|
||||
}
|
||||
let conn = current(&state)?;
|
||||
let spec = format!("{}/...", scope.trim_end_matches(['/', '.']));
|
||||
let dir = match run_json(&conn, &["where", &spec])?
|
||||
run_json(conn, &["where", &spec])
|
||||
.ok()?
|
||||
.into_iter()
|
||||
.next()
|
||||
.and_then(|v| v.get("path").and_then(|p| p.as_str()).map(String::from))
|
||||
{
|
||||
Some(p) => p.trim_end_matches(['.', '/', '\\']).to_string(),
|
||||
None => return Ok(String::new()),
|
||||
};
|
||||
let is_uproject = |p: &std::path::Path| {
|
||||
p.extension().map_or(false, |x| x.eq_ignore_ascii_case("uproject"))
|
||||
};
|
||||
// the scope folder itself
|
||||
if let Ok(entries) = std::fs::read_dir(&dir) {
|
||||
.map(|p| p.trim_end_matches(['.', '/', '\\']).to_string())
|
||||
}
|
||||
|
||||
/// First file with the given extension in `dir` (checked, then one level of subdirs).
|
||||
fn find_ext(dir: &str, ext: &str) -> String {
|
||||
let matches = |p: &std::path::Path| p.extension().map_or(false, |x| x.eq_ignore_ascii_case(ext));
|
||||
if let Ok(entries) = std::fs::read_dir(dir) {
|
||||
let mut subdirs = Vec::new();
|
||||
for e in entries.flatten() {
|
||||
let p = e.path();
|
||||
if is_uproject(&p) {
|
||||
return Ok(p.to_string_lossy().to_string());
|
||||
if matches(&p) {
|
||||
return p.to_string_lossy().to_string();
|
||||
}
|
||||
if p.is_dir() {
|
||||
subdirs.push(p);
|
||||
}
|
||||
}
|
||||
// one level down
|
||||
for sd in subdirs {
|
||||
if let Ok(inner) = std::fs::read_dir(&sd) {
|
||||
for e in inner.flatten() {
|
||||
let p = e.path();
|
||||
if is_uproject(&p) {
|
||||
return Ok(p.to_string_lossy().to_string());
|
||||
if matches(&p) {
|
||||
return p.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(String::new())
|
||||
String::new()
|
||||
}
|
||||
|
||||
/// Local path of a `.uproject` in the scope (or "" if not a UE project).
|
||||
#[tauri::command]
|
||||
async fn find_uproject(state: State<'_, AppState>, scope: String) -> Result<String, String> {
|
||||
let conn = current(&state)?;
|
||||
Ok(scope_local_dir(&conn, &scope).map(|d| find_ext(&d, "uproject")).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Local path of a Visual Studio `.sln` in the scope (or "" if none).
|
||||
#[tauri::command]
|
||||
async fn find_sln(state: State<'_, AppState>, scope: String) -> Result<String, String> {
|
||||
let conn = current(&state)?;
|
||||
Ok(scope_local_dir(&conn, &scope).map(|d| find_ext(&d, "sln")).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Build a Visual Studio solution with MSBuild (located via vswhere), streaming
|
||||
/// its output line-by-line as `build-log` events. Uses the solution's default
|
||||
/// configuration — for an Unreal project that compiles the game module.
|
||||
#[tauri::command]
|
||||
async fn build_sln(path: String) -> Result<String, String> {
|
||||
if !std::path::Path::new(&path).exists() {
|
||||
return Err(format!("Solution not found: {path}"));
|
||||
}
|
||||
let emit = |line: String, done: bool, ok: bool| {
|
||||
if let Some(app) = APP.get() {
|
||||
let _ = app.emit(
|
||||
"build-log",
|
||||
serde_json::json!({ "line": line, "done": done, "ok": ok }),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// locate MSBuild.exe via vswhere (falls back to PATH)
|
||||
let pf86 = std::env::var("ProgramFiles(x86)").unwrap_or_else(|_| "C:\\Program Files (x86)".into());
|
||||
let vswhere = format!("{pf86}\\Microsoft Visual Studio\\Installer\\vswhere.exe");
|
||||
let msbuild = {
|
||||
let mut vc = Command::new(&vswhere);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
vc.creation_flags(0x0800_0000);
|
||||
}
|
||||
vc.args(["-latest", "-requires", "Microsoft.Component.MSBuild", "-find", "MSBuild\\**\\Bin\\MSBuild.exe"]);
|
||||
match vc.output() {
|
||||
Ok(o) => String::from_utf8_lossy(&o.stdout).lines().next().unwrap_or("").trim().to_string(),
|
||||
Err(_) => String::new(),
|
||||
}
|
||||
};
|
||||
let msbuild = if msbuild.is_empty() { "MSBuild.exe".to_string() } else { msbuild };
|
||||
emit(format!("MSBuild: {msbuild}"), false, false);
|
||||
emit(format!("Building {path} …"), false, false);
|
||||
|
||||
let mut cmd = Command::new(&msbuild);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
cmd.creation_flags(0x0800_0000);
|
||||
}
|
||||
cmd.args([&path, "/m", "/nologo", "/v:minimal", "/clp:NoSummary"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
let mut child = match cmd.spawn() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let m = format!("Could not start MSBuild (Visual Studio C++ tools installed?): {e}");
|
||||
emit(m.clone(), true, false);
|
||||
return Err(m);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(out) = child.stdout.take() {
|
||||
for line in BufReader::new(out).lines().map_while(Result::ok) {
|
||||
if !line.trim().is_empty() {
|
||||
emit(line, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
let status = child.wait().map_err(|e| e.to_string())?;
|
||||
if let Some(mut se) = child.stderr.take() {
|
||||
let mut buf = String::new();
|
||||
let _ = std::io::Read::read_to_string(&mut se, &mut buf);
|
||||
for line in buf.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
emit(line.to_string(), false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
let ok = status.success();
|
||||
emit(if ok { "✓ Build succeeded.".into() } else { "✗ Build FAILED.".into() }, true, ok);
|
||||
if ok {
|
||||
Ok("ok".into())
|
||||
} else {
|
||||
Err("Build failed".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Files and metadata of a submitted changelist (History detail).
|
||||
@ -792,6 +881,8 @@ pub fn run() {
|
||||
p4_undo_change,
|
||||
open_in_explorer,
|
||||
find_uproject,
|
||||
find_sln,
|
||||
build_sln,
|
||||
launch_file,
|
||||
open_in_vscode,
|
||||
p4_describe,
|
||||
|
||||
21
src/App.css
21
src/App.css
@ -362,6 +362,27 @@ body.resizing{cursor:col-resize!important;user-select:none}
|
||||
.xfer-bar{margin-top:12px;height:6px;border-radius:4px;background:var(--panel-3);overflow:hidden;position:relative}
|
||||
.xfer-bar span{position:absolute;top:0;left:-35%;width:35%;height:100%;border-radius:4px;
|
||||
background:linear-gradient(90deg,transparent,var(--accent),transparent);animation:shim 1.1s ease-in-out infinite}
|
||||
|
||||
/* MSBuild output overlay */
|
||||
.build{width:660px;max-width:calc(100vw - 40px);height:min(70vh,560px);display:flex;flex-direction:column;
|
||||
background:var(--elevated);border:1px solid var(--border);border-radius:16px;box-shadow:var(--shadow);overflow:hidden;animation:updin .3s ease}
|
||||
.build-head{flex:0 0 auto;display:flex;align-items:center;gap:12px;padding:13px 18px;border-bottom:1px solid var(--border-soft)}
|
||||
.build-ic{width:34px;height:34px;flex:0 0 auto;border-radius:10px;display:grid;place-items:center;font-weight:700;font-size:16px;background:var(--panel-3);color:var(--muted)}
|
||||
.build-ic.run{color:var(--accent-2)}
|
||||
.build-ic.ok{background:rgba(62,207,142,.15);color:var(--add)}
|
||||
.build-ic.fail{background:rgba(242,99,126,.15);color:var(--del)}
|
||||
.build-ic svg{width:18px;height:18px}
|
||||
.build-ttl{display:flex;flex-direction:column;gap:1px;min-width:0}
|
||||
.build-ttl b{font-size:14px}
|
||||
.build-ttl span{font-size:11.5px;color:var(--faint);font-family:var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.build-x{margin-left:auto;width:28px;height:28px;flex:0 0 auto;border:1px solid var(--border);background:var(--panel);color:var(--muted);border-radius:8px;cursor:pointer;display:grid;place-items:center}
|
||||
.build-x:hover:not(:disabled){color:var(--txt);border-color:var(--accent)}
|
||||
.build-x:disabled{opacity:.4;cursor:not-allowed}
|
||||
.build-body{flex:1;overflow:auto;min-height:0;padding:10px 14px;font-family:var(--mono);font-size:11.5px;line-height:1.6;background:var(--stage-bg)}
|
||||
.build-ln{white-space:pre-wrap;word-break:break-word;color:var(--muted)}
|
||||
.build-ln.err{color:var(--del)}
|
||||
.build-ln.warn{color:var(--edit)}
|
||||
.build-ln.ok{color:var(--add);font-weight:600}
|
||||
.tzone.push .ic{color:var(--add)}
|
||||
.tzone.push .val{color:var(--add)}
|
||||
.commit .row{display:flex;gap:10px;align-items:center}
|
||||
|
||||
66
src/App.tsx
66
src/App.tsx
@ -40,6 +40,7 @@ const I = {
|
||||
clock: <svg viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="8.5" stroke="currentColor" strokeWidth="1.6"/><path d="M12 8v4l2.5 2.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>,
|
||||
log: <svg viewBox="0 0 24 24" fill="none"><rect x="4" y="3" width="16" height="18" rx="2" stroke="currentColor" strokeWidth="1.6"/><path d="M8 8h8M8 12h8M8 16h5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>,
|
||||
vscode: <svg viewBox="0 0 24 24" fill="none"><path d="m9 8-5 4 5 4M15 8l5 4-5 4" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg>,
|
||||
hammer: <svg viewBox="0 0 24 24" fill="none"><path d="M14 6l4 4M17 3l4 4-3 3-4-4 3-3ZM13 7 4 16a2 2 0 0 0 0 3l1 1a2 2 0 0 0 3 0l9-9" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>,
|
||||
ue: <svg viewBox="0 0 1280 1280"><path fillRule="evenodd" clipRule="evenodd" fill="currentColor" d="M640,1280c353.46,0,640-286.54,640-640S993.46,0,640,0S0,286.54,0,640S286.54,1280,640,1280z M803.7,995.81c156.5-73.92,205.56-210.43,216.6-263.61c-57.22,58.6-120.53,118-163.11,76.88c0,0-2.33-219.45-2.33-309.43c0-121,114.75-211.18,114.75-211.18c-63.11,11.24-138.89,33.71-219.33,112.65c-7.26,7.2-14.14,14.76-20.62,22.67c-34.47-26.39-79.14-18.48-79.14-18.48c24.14,13.26,48.23,51.88,48.23,83.85v314.26c0,0-52.63,46.3-93.19,46.3c-9.14,0.07-18.17-2.05-26.33-6.18c-8.16-4.13-15.21-10.15-20.56-17.56c-3.21-4.19-5.87-8.78-7.91-13.65V424.07c-11.99,9.89-52.51,18.04-52.51-49.22c0-41.79,30.11-91.6,83.73-122.15c-73.63,11.23-142.59,43.04-198.92,91.76c-42.8,36.98-77.03,82.85-100.31,134.4c-23.28,51.55-35.06,107.55-34.51,164.12c0,0,39.21-122.51,88.32-133.83c7.15-1.88,14.65-2.07,21.89-0.54c7.24,1.53,14.02,4.72,19.81,9.34c5.79,4.61,10.41,10.51,13.51,17.23c3.1,6.72,4.59,14.07,4.34,21.46V844.3c0,29.16-18.8,35.53-36.17,35.22c-11.77-0.83-23.4-3.02-34.66-6.53c35.86,48.53,82.46,88.12,136.15,115.66c53.69,27.54,113.03,42.29,173.37,43.1l106.05-106.6L803.7,995.81z"/></svg>,
|
||||
};
|
||||
|
||||
@ -292,6 +293,11 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
||||
const [transfer, setTransfer] = useState<Transfer | null>(null); // live sync/submit file transfer
|
||||
const [showXfer, setShowXfer] = useState(false); // delayed so fast ops don't flash a dialog
|
||||
const [uproject, setUproject] = useState(""); // local .uproject path when the scope is a UE project
|
||||
const [slnPath, setSlnPath] = useState(""); // local .sln path in the scope (for building)
|
||||
const [buildLog, setBuildLog] = useState<string[]>([]); // live MSBuild output
|
||||
const [building, setBuilding] = useState(false);
|
||||
const [buildOk, setBuildOk] = useState<boolean | null>(null);
|
||||
const [buildOpen, setBuildOpen] = useState(false);
|
||||
const [ctx, setCtx] = useState<null | { x: number; y: number; items: CtxItem[] }>(null); // right-click menu
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]); // live p4 command log (P4V-style)
|
||||
const [logOpen, setLogOpen] = useState(false);
|
||||
@ -385,13 +391,24 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
||||
const id = setTimeout(() => setShowXfer(true), 400);
|
||||
return () => clearTimeout(id);
|
||||
}, [xferActive]);
|
||||
// detect a UE project in the current working folder → show the Launch button
|
||||
// detect a UE project / .sln in the current working folder (Launch / Build actions)
|
||||
useEffect(() => {
|
||||
if (!activePath) { setUproject(""); return; }
|
||||
if (!activePath) { setUproject(""); setSlnPath(""); return; }
|
||||
let live = true;
|
||||
p4.findUproject(activePath).then((p) => live && setUproject(p)).catch(() => live && setUproject(""));
|
||||
p4.findSln(activePath).then((p) => live && setSlnPath(p)).catch(() => live && setSlnPath(""));
|
||||
return () => { live = false; };
|
||||
}, [activePath]);
|
||||
// live MSBuild output
|
||||
useEffect(() => {
|
||||
let un: (() => void) | undefined;
|
||||
listen<{ line: string; done: boolean; ok: boolean }>("build-log", (e) => {
|
||||
const p = e.payload;
|
||||
setBuildLog((L) => (L.length > 4000 ? [...L.slice(-4000), p.line] : [...L, p.line]));
|
||||
if (p.done) { setBuilding(false); setBuildOk(p.ok); }
|
||||
}).then((u) => (un = u));
|
||||
return () => un?.();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const close = () => setOpenMenu(null);
|
||||
document.addEventListener("click", close);
|
||||
@ -511,6 +528,14 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
||||
catch (e) { flash(String(e), true); }
|
||||
}
|
||||
|
||||
// build the solution found in the working folder (MSBuild) with live output
|
||||
async function buildSln() {
|
||||
if (!slnPath) { flash(t("No .sln found in the working folder. Choose the project folder first."), true); return; }
|
||||
setBuildLog([]); setBuildOk(null); setBuilding(true); setBuildOpen(true);
|
||||
try { await p4.buildSln(slnPath); }
|
||||
catch { /* the overlay already shows the failure line */ }
|
||||
}
|
||||
|
||||
// reveal a depot path / workspace root in Windows Explorer
|
||||
async function reveal(target: string) {
|
||||
if (!target) { flash(t("Path not set"), true); return; }
|
||||
@ -647,7 +672,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
||||
Connection: [{ label: t("Refresh"), kb: "F5", act: () => refresh() }, { label: t("Choose working folder…"), act: () => browseTo("") }],
|
||||
Actions: [{ label: t("Rescan changes"), kb: "Ctrl+R", act: rescan }, { label: t("Get Latest"), kb: "Ctrl+G", act: getLatest }, { label: t("Commit (local)"), act: doCommit }, { label: t("Submit to server"), kb: "Ctrl+S", act: pushAll }, { label: t("Revert All…"), act: revertAll }, { label: t("Refresh"), kb: "F5", act: () => refresh() }],
|
||||
Window: [{ label: (logOpen ? "✓ " : "") + t("Log"), kb: "Ctrl+L", act: () => setLogOpen((o) => !o) }],
|
||||
Tools: [{ label: t("Settings…"), act: () => setModal({ kind: "settings" }) }],
|
||||
Tools: [{ label: slnPath ? t("Build Solution (.sln)") : t("Build Solution — no .sln"), kb: "Ctrl+B", act: buildSln }, { label: t("Settings…"), act: () => setModal({ kind: "settings" }) }],
|
||||
Help: [{ label: t("About"), act: () => setModal({ kind: "about" }) }],
|
||||
};
|
||||
|
||||
@ -690,6 +715,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
||||
<div className="tzone" onClick={() => browseTo("")} title={t("Choose the project working folder · right-click for more")}
|
||||
onContextMenu={(e) => openCtx(e, [
|
||||
...(uproject ? [{ label: t("Launch Unreal Engine"), icon: I.ue, act: launchUE }] : []),
|
||||
...(slnPath ? [{ label: t("Build Solution (.sln)"), icon: I.hammer, act: buildSln }] : []),
|
||||
{ label: t("Open in Explorer"), act: () => reveal(activePath || String(info?.clientRoot || "")) },
|
||||
{ label: t("Choose another folder…"), act: () => browseTo("") },
|
||||
{ label: t("Copy path"), act: () => copyText(activePath || "//…", t("Path")) },
|
||||
@ -811,6 +837,7 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
|
||||
onClose={() => setModal(null)} onPickWorkspace={switchWorkspace} onBrowse={browseTo} onChoose={chooseScope} onDisconnect={onDisconnect} />}
|
||||
{ask && <ConfirmModal {...ask} onClose={(v) => { ask.resolve(v); setAsk(null); }} />}
|
||||
{showXfer && transfer && <TransferDialog transfer={transfer} />}
|
||||
{buildOpen && <BuildOverlay lines={buildLog} building={building} ok={buildOk} sln={slnPath} onClose={() => setBuildOpen(false)} />}
|
||||
{ctx && <ContextMenu x={ctx.x} y={ctx.y} items={ctx.items} onClose={() => setCtx(null)} />}
|
||||
{toast && <div className={"toast" + (toast.err ? " err" : "")}>{toast.text}</div>}
|
||||
</div>
|
||||
@ -1005,6 +1032,39 @@ function TransferDialog({ transfer }: { transfer: Transfer }) {
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------------- build output (MSBuild) ---------------- */
|
||||
function BuildOverlay({ lines, building, ok, sln, onClose }: { lines: string[]; building: boolean; ok: boolean | null; sln: string; onClose: () => void }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => { if (ref.current) ref.current.scrollTop = ref.current.scrollHeight; }, [lines]);
|
||||
const name = sln.replace(/\\/g, "/").split("/").pop() || "solution";
|
||||
return (
|
||||
<div className="modal-back">
|
||||
<div className="build">
|
||||
<div className="build-head">
|
||||
<span className={"build-ic" + (building ? " run" : ok ? " ok" : ok === false ? " fail" : "")}>
|
||||
{building ? <span className="ldr sm" /> : ok ? "✓" : ok === false ? "✗" : I.hammer}
|
||||
</span>
|
||||
<div className="build-ttl">
|
||||
<b>{building ? t("Building…") : ok ? t("Build succeeded") : ok === false ? t("Build failed") : t("Build")}</b>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
<button className="build-x" onClick={onClose} disabled={building} title={building ? t("Building…") : t("Close")}>
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="build-body" ref={ref}>
|
||||
{lines.map((ln, i) => {
|
||||
const low = ln.toLowerCase();
|
||||
const cls = /\berror\b|\bfailed\b|✗/.test(low) ? " err" : /\bwarning\b/.test(low) ? " warn" : /succeeded|✓/.test(low) ? " ok" : "";
|
||||
return <div key={i} className={"build-ln" + cls}>{ln}</div>;
|
||||
})}
|
||||
{building && lines.length === 0 && <div className="build-ln">{t("Starting MSBuild…")}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------------- confirm modal ---------------- */
|
||||
function ConfirmModal({ title, body, confirm, danger, onClose }: { title: string; body: string; confirm: string; danger?: boolean; onClose: (v: boolean) => void }) {
|
||||
useEffect(() => {
|
||||
|
||||
@ -69,6 +69,14 @@ const D: Record<string, Tr> = {
|
||||
"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 Unreal Engine": { ru: "Запустить Unreal Engine", de: "Unreal Engine starten", fr: "Lancer Unreal Engine", es: "Iniciar Unreal Engine" },
|
||||
"Build Solution (.sln)": { ru: "Собрать решение (.sln)", de: "Projektmappe bauen (.sln)", fr: "Compiler la solution (.sln)", es: "Compilar solución (.sln)" },
|
||||
"Build Solution — no .sln": { ru: "Собрать решение — нет .sln", de: "Projektmappe bauen — keine .sln", fr: "Compiler — aucune .sln", es: "Compilar — sin .sln" },
|
||||
"No .sln found in the working folder. Choose the project folder first.": { ru: "В рабочей папке нет .sln. Сначала выбери папку проекта.", de: "Keine .sln im Arbeitsordner. Wähle zuerst den Projektordner.", fr: "Aucune .sln dans le dossier de travail. Choisissez d’abord le dossier du projet.", es: "No hay .sln en la carpeta de trabajo. Elige primero la carpeta del proyecto." },
|
||||
"Build": { ru: "Сборка", de: "Build", fr: "Compilation", es: "Compilación" },
|
||||
"Building…": { ru: "Сборка…", de: "Wird gebaut…", fr: "Compilation…", es: "Compilando…" },
|
||||
"Build succeeded": { ru: "Сборка успешна", de: "Build erfolgreich", fr: "Compilation réussie", es: "Compilación correcta" },
|
||||
"Build failed": { ru: "Сборка провалена", de: "Build fehlgeschlagen", fr: "Échec de la compilation", es: "Compilación fallida" },
|
||||
"Starting MSBuild…": { ru: "Запуск MSBuild…", de: "MSBuild wird gestartet…", fr: "Démarrage de MSBuild…", es: "Iniciando MSBuild…" },
|
||||
"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…" },
|
||||
|
||||
|
||||
@ -73,6 +73,8 @@ export const p4 = {
|
||||
undoChange: (change: string) => invoke<string>("p4_undo_change", { change }),
|
||||
openInExplorer: (target: string) => invoke<void>("open_in_explorer", { target }),
|
||||
findUproject: (scope: string) => invoke<string>("find_uproject", { scope }),
|
||||
findSln: (scope: string) => invoke<string>("find_sln", { scope }),
|
||||
buildSln: (path: string) => invoke<string>("build_sln", { path }),
|
||||
launchFile: (path: string) => invoke<void>("launch_file", { path }),
|
||||
openInVscode: (depot: string) => invoke<void>("open_in_vscode", { depot }),
|
||||
readDepot: async (depot: string): Promise<ArrayBuffer> => {
|
||||
|
||||
Reference in New Issue
Block a user