Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/main/logwriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,8 @@ async function exportLog(payload = {}) {

/**
* Export ONLY one LOG (the active tab) as a single .log file carrying the
* experiment-field header on top. Always lands in the in-project LOG_OUTPUT
* folder (ignores any custom output root) so a half-finished experiment can be
* shared for confirmation before the full export.
* experiment-field header on top. Honors the custom Output Root when one is
* set, otherwise falls back to the default LOG_OUTPUT folder.
* Returns { ok, single, targetDir, filePath, fileName, files } or { ok:false, error }.
*/
async function exportSingleLog(payload = {}) {
Expand All @@ -151,6 +150,7 @@ async function exportSingleLog(payload = {}) {
tester = '',
testCase = '',
notes = '',
outputBase = '',
customFields = [],
log = {},
abbrevLen,
Expand All @@ -174,9 +174,9 @@ async function exportSingleLog(payload = {}) {
.filter((f) => f.label || f.value)
: [];

// Single-LOG output: a dedicated folder under the in-project LOG_OUTPUT,
// with the one .log file (carrying the experiment header) inside it.
const baseDir = defaultOutputDir();
// Single-LOG output: a dedicated folder under the chosen Output Root (or the
// default LOG_OUTPUT when none is set), with the one .log file inside.
const baseDir = outputBase && outputBase.trim() ? outputBase.trim() : defaultOutputDir();
const now = new Date();
const nameMax = Math.min(40, Math.max(1, parseInt(abbrevLen, 10) || 30));
const folderName = `${dateStamp(now)}_${timeStamp4(now)}_${abbreviate(experimentName, nameMax)}_${typeName}`;
Expand Down
38 changes: 33 additions & 5 deletions src/main/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,51 @@
*/
'use strict';

const fs = require('fs');
const path = require('path');
const { app } = require('electron');

/**
* Base directory used as the writable root for output.
* - Packaged: the folder that contains the installed M2_LOG.exe
* (per-user install under %LOCALAPPDATA%\Programs\M2_LOG is writable).
* Base directory used as the read root for bundled resources (e.g. highlight
* rules) and as the preferred output root.
* - Packaged: the folder that contains the installed M2_LOG.exe.
* - Dev: the project root.
*/
function appBaseDir() {
if (app.isPackaged) return path.dirname(process.execPath);
return path.join(__dirname, '..', '..');
}

/** Default output root: <appBaseDir>\LOG_OUTPUT */
/** True if `dir` can be created and written to (Windows ACLs need a real probe). */
function canWrite(dir) {
try {
fs.mkdirSync(dir, { recursive: true });
const probe = path.join(dir, `.m2log-write-test-${process.pid}-${Date.now()}.tmp`);
fs.writeFileSync(probe, '');
fs.unlinkSync(probe);
return true;
} catch (e) {
return false;
}
}

let cachedOutputDir = null;

/**
* Default output root for exports and the LOG Analysis view.
* Prefers <appBaseDir>\LOG_OUTPUT (portable / per-user install). When the app
* is installed to a protected location (e.g. C:\Program Files) that folder is
* not writable, so we fall back to <Documents>\M2_LOG\LOG_OUTPUT.
*/
function defaultOutputDir() {
return path.join(appBaseDir(), 'LOG_OUTPUT');
if (cachedOutputDir) return cachedOutputDir;
const primary = path.join(appBaseDir(), 'LOG_OUTPUT');
if (!app.isPackaged || canWrite(primary)) {
cachedOutputDir = primary;
} else {
cachedOutputDir = path.join(app.getPath('documents'), 'M2_LOG', 'LOG_OUTPUT');
}
return cachedOutputDir;
}

module.exports = { appBaseDir, defaultOutputDir };
2 changes: 1 addition & 1 deletion src/renderer/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"btn.clear": "Clear",
"btn.clear.title": "Clear current LOG",
"btn.exportSingle": "Export this LOG",
"btn.exportSingle.title": "Export only the current LOG (with the experiment header) to the in-project LOG_OUTPUT",
"btn.exportSingle.title": "Export only the current LOG (with the experiment header) to the Output Root (or LOG_OUTPUT if empty)",
"toast.exportSingleOk": "This LOG exported (with header) to LOG_OUTPUT",
"log.type.ph": "LOG type, e.g. UEFI",
"log.content.ph": "Paste LOG here...",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/i18n/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"btn.clear": "清除",
"btn.clear.title": "清空目前 LOG",
"btn.exportSingle": "輸出此 LOG",
"btn.exportSingle.title": "只輸出目前這個 LOG(含實驗欄位表頭)到專案 LOG_OUTPUT",
"btn.exportSingle.title": "只輸出目前這個 LOG(含實驗欄位表頭)到輸出根目錄(留空則為 LOG_OUTPUT",
"toast.exportSingleOk": "已輸出此 LOG(含實驗表頭)到 LOG_OUTPUT",
"log.type.ph": "LOG 種類,例如 UEFI",
"log.content.ph": "在此貼上 LOG...",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ <h2 data-i18n="panel.expFields">實驗欄位</h2>
<div class="tabs" id="logTabs"></div>
<div class="log-tools">
<span class="counter" id="counter">0 行 · 0 字元</span>
<button class="btn btn-ghost btn-sm" id="btnExportSingle" data-i18n-title="btn.exportSingle.title" title="只輸出目前這個 LOG(含實驗欄位表頭)到專案 LOG_OUTPUT">
<button class="btn btn-ghost btn-sm" id="btnExportSingle" data-i18n-title="btn.exportSingle.title" title="只輸出目前這個 LOG(含實驗欄位表頭)到輸出根目錄(留空則為 LOG_OUTPUT">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
Expand Down
Loading