mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 23:58:21 +00:00
Merge remote-tracking branch 'pr/2562' into HEAD
# Conflicts: # crates/ui-egui/src/app/frame.rs
This commit is contained in:
commit
1b3470033b
1 file changed
+2
-47
@@ -98,7 +98,7 @@ impl EffectcraftApp {
|
||||
self.ui.timeline.pps = None;
|
||||
}
|
||||
effectcraft_engine::Event::Toast { message, .. } => self.toast = Some((message, self.now)),
|
||||
effectcraft_engine::Event::OpenUrl(url) => open_url(ctx, url),
|
||||
effectcraft_engine::Event::OpenUrl(url) => ctx.open_url(egui::OpenUrl::new_tab(url)),
|
||||
// Frames are keyed by content: an edit keeps those of comps it doesn't touch.
|
||||
effectcraft_engine::Event::ProjectChanged { .. } => {}
|
||||
effectcraft_engine::Event::Frontend { command, params } => {
|
||||
@@ -410,9 +410,9 @@ impl EffectcraftApp {
|
||||
let header = egui::Rect::from_min_size(egui::pos2(full.min.x, top), egui::vec2(full.width(), header_h));
|
||||
header::show(self, ui, header);
|
||||
let body = egui::Rect::from_min_max(egui::pos2(full.min.x + 4.0, header.max.y + 2.0), egui::pos2(full.max.x - 4.0, full.max.y - 4.0));
|
||||
crate::color_picker::note_dialog(&ctx, self.dialog);
|
||||
// After the shortcuts, menus and header, which can replace the project (New, Open).
|
||||
self.ui.timeline.scope_links(self.session.script_ui.project_epoch);
|
||||
crate::color_picker::note_dialog(&ctx, self.dialog);
|
||||
self.dock_area(ui, body);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
prefs_live::dropped_files(self, ui, full);
|
||||
@@ -583,48 +583,3 @@ impl EffectcraftApp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens `url`: on Windows a `file://` folder (Reveal in Explorer) in File Explorer, since the
|
||||
/// system browser would get it otherwise (#1961); anything else in the browser.
|
||||
fn open_url(ctx: &egui::Context, url: String) {
|
||||
#[cfg(windows)]
|
||||
if let Some(path) = file_url_path(&url) {
|
||||
// The engine only sends folders it checked are plain folders (`reveal_url`).
|
||||
if std::process::Command::new("explorer").arg(&path).spawn().is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
ctx.open_url(egui::OpenUrl::new_tab(url));
|
||||
}
|
||||
|
||||
/// The Windows path of a `file:///C:/…` (or `file:////server/share/…`) URL, percent-decoded.
|
||||
#[cfg(any(windows, test))]
|
||||
fn file_url_path(url: &str) -> Option<String> {
|
||||
let rest = url.strip_prefix("file:///")?;
|
||||
let mut bytes = Vec::with_capacity(rest.len());
|
||||
let mut it = rest.bytes();
|
||||
while let Some(b) = it.next() {
|
||||
if b == b'%' {
|
||||
let hex = [it.next()?, it.next()?];
|
||||
bytes.push(u8::from_str_radix(std::str::from_utf8(&hex).ok()?, 16).ok()?);
|
||||
} else {
|
||||
bytes.push(b);
|
||||
}
|
||||
}
|
||||
let path = String::from_utf8(bytes).ok()?.replace('/', "\\");
|
||||
// `file:////server/share` is the UNC path `\\server\share`.
|
||||
Some(if path.starts_with('\\') { format!("\\{path}") } else { path })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/// #1961: Reveal's `file://` URLs map back to the folder Explorer opens.
|
||||
#[test]
|
||||
fn file_urls_map_to_windows_paths() {
|
||||
assert_eq!(super::file_url_path("file:///C:/Footage/My%20Shots/").as_deref(), Some("C:\\Footage\\My Shots\\"));
|
||||
assert_eq!(super::file_url_path("file:///C:/Clips/%E6%98%A0%E5%83%8F").as_deref(), Some("C:\\Clips\\映像"));
|
||||
assert_eq!(super::file_url_path("file:////server/share/x").as_deref(), Some("\\\\server\\share\\x"));
|
||||
assert_eq!(super::file_url_path("https://getartcraft.com"), None);
|
||||
assert_eq!(super::file_url_path("file:///C:/bad%2"), None);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user