Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3a510eee6 |
No files matched your search
@@ -197,6 +197,27 @@ export function useOnboardingTour(
|
||||
requestAnimationFrame(updateRect)
|
||||
}
|
||||
|
||||
// Bring the current step target into view when the tour jumps to a step
|
||||
// whose target lives inside a scrollable container (e.g. the settings
|
||||
// sidebar). Without this the bubble can point at an off-screen element.
|
||||
function ensureTargetVisible() {
|
||||
const target = targetElement.value
|
||||
if (!target) return
|
||||
const rect = target.getBoundingClientRect()
|
||||
const outOfView =
|
||||
rect.top < 0 ||
|
||||
rect.left < 0 ||
|
||||
rect.bottom > window.innerHeight ||
|
||||
rect.right > window.innerWidth
|
||||
if (!outOfView) return
|
||||
target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' })
|
||||
requestAnimationFrame(() => {
|
||||
if (targetElement.value === target) {
|
||||
targetRect.value = target.getBoundingClientRect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function goTo(destination: StepDestination) {
|
||||
if (destination === 'complete') {
|
||||
events.complete()
|
||||
@@ -343,6 +364,7 @@ export function useOnboardingTour(
|
||||
await nextTick()
|
||||
updateBubbleSize()
|
||||
updateTarget()
|
||||
ensureTargetVisible()
|
||||
})
|
||||
|
||||
watch(mode, () => {
|
||||
|
||||
@@ -109,7 +109,6 @@ pub struct CrashAnalysisMod {
|
||||
struct SourceCandidate {
|
||||
path: PathBuf,
|
||||
filename: String,
|
||||
upload_name: String,
|
||||
source_type: &'static str,
|
||||
modified: SystemTime,
|
||||
}
|
||||
@@ -117,25 +116,10 @@ struct SourceCandidate {
|
||||
#[derive(Debug)]
|
||||
struct SourceText {
|
||||
filename: String,
|
||||
upload_name: String,
|
||||
source_type: &'static str,
|
||||
modified: SystemTime,
|
||||
content: String,
|
||||
}
|
||||
pub(super) struct CollectedCrashLog {
|
||||
pub name: String,
|
||||
pub content: CensoredString,
|
||||
}
|
||||
|
||||
pub(super) struct CollectedCrashLogs {
|
||||
pub combined_log: CensoredString,
|
||||
pub sources: Vec<CollectedCrashLog>,
|
||||
}
|
||||
|
||||
struct CrashContext {
|
||||
sources: Vec<SourceText>,
|
||||
credentials: Vec<Credentials>,
|
||||
}
|
||||
|
||||
struct Rule {
|
||||
id: &'static str,
|
||||
@@ -1201,25 +1185,20 @@ async fn collect_candidates(
|
||||
) -> crate::Result<Vec<SourceCandidate>> {
|
||||
let mut candidates = Vec::new();
|
||||
let mut locations = vec![
|
||||
(
|
||||
directories.game_logs_dir(instance_root),
|
||||
"minecraft_log",
|
||||
None,
|
||||
),
|
||||
(directories.game_logs_dir(instance_root), "minecraft_log"),
|
||||
(
|
||||
directories.game_crash_reports_dir(instance_root),
|
||||
"crash_report",
|
||||
Some("crash-reports"),
|
||||
),
|
||||
(instance_root.to_path_buf(), "instance_log", None),
|
||||
(instance_root.to_path_buf(), "instance_log"),
|
||||
];
|
||||
if let Some(dot_minecraft) = isolated_minecraft_root(instance_root) {
|
||||
// PCL-CE also searches the shared `.minecraft` root for JVM crash
|
||||
// logs when a version-isolated instance is active.
|
||||
locations.push((dot_minecraft, "minecraft_root_log", None));
|
||||
locations.push((dot_minecraft, "minecraft_root_log"));
|
||||
}
|
||||
|
||||
for (directory, default_type, upload_prefix) in locations {
|
||||
for (directory, default_type) in locations {
|
||||
if !tokio::fs::try_exists(&directory).await? {
|
||||
continue;
|
||||
}
|
||||
@@ -1269,9 +1248,6 @@ async fn collect_candidates(
|
||||
};
|
||||
candidates.push(SourceCandidate {
|
||||
path: entry.path(),
|
||||
upload_name: upload_prefix
|
||||
.map(|prefix| format!("{prefix}/{filename}"))
|
||||
.unwrap_or_else(|| filename.clone()),
|
||||
filename,
|
||||
source_type,
|
||||
modified: metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH),
|
||||
@@ -1281,53 +1257,6 @@ async fn collect_candidates(
|
||||
Ok(candidates)
|
||||
}
|
||||
|
||||
async fn collect_crash_context(
|
||||
instance_id: &str,
|
||||
) -> crate::Result<CrashContext> {
|
||||
let state = State::get().await?;
|
||||
let (instance_path, game_dir_override) =
|
||||
resolve_instance_path(instance_id, &state).await?;
|
||||
let instance_root = state
|
||||
.directories
|
||||
.resolve_game_dir(&instance_path, game_dir_override.as_deref());
|
||||
let candidates =
|
||||
collect_candidates(&instance_root, &state.directories).await?;
|
||||
let selected = select_run_candidates(candidates);
|
||||
let sources = read_sources(selected).await;
|
||||
let credentials = Credentials::get_all(&state.pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|credential| credential.1)
|
||||
.collect::<Vec<_>>();
|
||||
Ok(CrashContext {
|
||||
sources,
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) async fn collect_crash_logs(
|
||||
instance_id: &str,
|
||||
) -> crate::Result<CollectedCrashLogs> {
|
||||
let CrashContext {
|
||||
sources,
|
||||
credentials,
|
||||
..
|
||||
} = collect_crash_context(instance_id).await?;
|
||||
let combined_log =
|
||||
CensoredString::censor(build_combined_log(&sources), &credentials);
|
||||
let sources = sources
|
||||
.into_iter()
|
||||
.map(|source| CollectedCrashLog {
|
||||
name: source.upload_name,
|
||||
content: CensoredString::censor(source.content, &credentials),
|
||||
})
|
||||
.collect();
|
||||
Ok(CollectedCrashLogs {
|
||||
combined_log,
|
||||
sources,
|
||||
})
|
||||
}
|
||||
|
||||
fn isolated_minecraft_root(instance_root: &Path) -> Option<PathBuf> {
|
||||
let versions_dir = instance_root.parent()?;
|
||||
if versions_dir
|
||||
@@ -1391,7 +1320,6 @@ async fn read_sources(candidates: Vec<SourceCandidate>) -> Vec<SourceText> {
|
||||
};
|
||||
sources.push(SourceText {
|
||||
filename: candidate.filename,
|
||||
upload_name: candidate.upload_name,
|
||||
source_type: candidate.source_type,
|
||||
modified: candidate.modified,
|
||||
content: String::from_utf8_lossy(bytes).into_owned(),
|
||||
@@ -1851,7 +1779,6 @@ mod tests {
|
||||
fn source(filename: &str, content: &str) -> SourceText {
|
||||
SourceText {
|
||||
filename: filename.to_string(),
|
||||
upload_name: filename.to_string(),
|
||||
source_type: "minecraft_log",
|
||||
modified: SystemTime::now(),
|
||||
content: content.to_string(),
|
||||
@@ -1875,7 +1802,6 @@ mod tests {
|
||||
let candidates = vec![SourceCandidate {
|
||||
path: PathBuf::from("launcher_log.txt"),
|
||||
filename: "launcher_log.txt".to_string(),
|
||||
upload_name: "launcher_log.txt".to_string(),
|
||||
source_type: "minecraft_log",
|
||||
modified: SystemTime::now() - Duration::from_secs(181),
|
||||
}];
|
||||
|
||||
@@ -4,7 +4,8 @@ use futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::crash_analysis::collect_crash_logs;
|
||||
use super::CensoredString;
|
||||
use super::crash_analysis::analyze_crash;
|
||||
use crate::State;
|
||||
use crate::emit_logshare_ai_event;
|
||||
|
||||
@@ -199,6 +200,32 @@ fn source_identifier() -> String {
|
||||
format!("{LOGSHARE_SOURCE_PREFIX}/{}", env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
|
||||
struct CollectedCrashLog {
|
||||
name: String,
|
||||
content: CensoredString,
|
||||
}
|
||||
|
||||
struct CollectedCrashLogs {
|
||||
combined_log: CensoredString,
|
||||
sources: Vec<CollectedCrashLog>,
|
||||
}
|
||||
|
||||
async fn collect_crash_logs(
|
||||
instance_id: &str,
|
||||
) -> crate::Result<CollectedCrashLogs> {
|
||||
let analysis = analyze_crash(instance_id).await?;
|
||||
Ok(CollectedCrashLogs {
|
||||
combined_log: analysis.combined_log,
|
||||
sources: analysis
|
||||
.sources
|
||||
.into_iter()
|
||||
.map(|source| CollectedCrashLog {
|
||||
name: source.filename,
|
||||
content: source.content,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
async fn prepare_upload(instance_id: &str) -> crate::Result<UploadBody> {
|
||||
let settings = get_log_share_settings().await?;
|
||||
let logs = collect_crash_logs(instance_id).await?;
|
||||
|
||||
Reference in new issue
Block a user