Skip to content

Commit

Permalink
Titan v1.4.0 Release
Browse files Browse the repository at this point in the history
Merge pull request #13 from ArrushC/11-custom-commands
  • Loading branch information
ArrushC authored Sep 20, 2024
2 parents 9c92634 + d4aea8c commit 50f590e
Show file tree
Hide file tree
Showing 16 changed files with 221 additions and 73 deletions.
2 changes: 1 addition & 1 deletion dist/assets/index.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions dist/assets/index.js

Large diffs are not rendered by default.

50 changes: 25 additions & 25 deletions dist/assets/vendor.js

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { app, BrowserWindow, screen, ipcMain, dialog, session, shell, Menu } from "electron";
import electronUpdaterPkg from "electron-updater";
import fs from "fs";
import path from "path";
import { fork } from "child_process";
import { fileURLToPath } from "url";
import { setupLogger, setupUncaughtExceptionHandler } from "./server/logger.js";
import { exec } from "child_process";

// Import package.json
import packageJson from "./package.json" assert { type: "json" };

const { autoUpdater } = electronUpdaterPkg;

const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -387,6 +391,64 @@ ipcMain.handle("open-tortoisesvn-diff", async (event, data) => {
});
});

ipcMain.handle("fetch-custom-scripts", async () => {
const { configFolderPath } = packageJson;
const scripts = [];

try {
const files = fs.readdirSync(configFolderPath);

files.forEach((file) => {
const ext = path.extname(file);
if ([".bat", ".ps1"].includes(ext.toLowerCase())) {
const fullPath = path.join(configFolderPath, file);
scripts.push({
fileName: path.parse(file).name,
path: fullPath,
type: ext.toLowerCase() === ".bat" ? "batch" : "powershell",
});
}
});
} catch (error) {
logger.error(`Error reading the config folder: ${error.message}`);
return { success: false, error: error.message };
}

// Return the list of scripts found
return { success: true, scripts };
});

ipcMain.handle("run-custom-script", async (event, data) => {
const { scriptType, scriptPath, branchData } = data;
logger.info(`Running custom script: ${scriptPath} (${scriptType}) with branch data: ${JSON.stringify(branchData)}`);

return new Promise((resolve, reject) => {
let command = "";
const { id, "Branch Folder": branchFolder, "Branch Version": branchVersion, "SVN Branch": svnBranch } = branchData;

const args = `"${id}" "${branchFolder}" "${branchVersion}" "${svnBranch}"`;

if (scriptType === "batch") {
command = `start cmd /k "${scriptPath}" ${args}`;
} else if (scriptType === "powershell") {
command = `start powershell -NoExit -ExecutionPolicy Bypass -File "${scriptPath}" -id "${id}" -branchFolder "${branchFolder}" -branchVersion "${branchVersion}" -svnBranch "${svnBranch}"`;
}

exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error.message}`);
reject({ success: false, error: error.message });
} else if (stderr) {
console.error(`Stderr: ${stderr}`);
reject({ success: false, error: stderr });
} else {
console.log(`Stdout: ${stdout}`);
resolve({ success: true });
}
});
});
});

ipcMain.handle("download-update", () => {
return autoUpdater.downloadUpdate();
});
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
"name": "titan",
"private": true,
"proxy": "http://localhost:5173",
"version": "1.3.5",
"version": "1.4.0",
"type": "module",
"main": "main.js",
"description": "A desktop application for streamlining your workflow in Revision Control Systems (RCS) like Subversion (SVN).",
"author": "ArrushC",
"configFolderPath": "C:/Titan",
"configFilePath": "C:/Titan/Titan.config.json",
"targetsFilePath": "C:/Titan/Titan.targets.txt",
"logFilePath": "C:/Titan/Titan.app.log",
"scripts": {
"start": "npm run server:start",
"client:dev": "vite",
Expand Down
2 changes: 2 additions & 0 deletions preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electron", {
getAppVersion: () => ipcRenderer.invoke("app-version"),
openTortoiseSVNDiff: (data) => ipcRenderer.invoke("open-tortoisesvn-diff", data),
fetchCustomScripts: () => ipcRenderer.invoke("fetch-custom-scripts"),
runCustomScript: (data) => ipcRenderer.invoke("run-custom-script", data),
onAppClosing: (callback) => ipcRenderer.on("app-closing", callback),
removeAppClosingListener: () => ipcRenderer.removeAllListeners("app-closing"),
downloadUpdate: () => ipcRenderer.invoke("download-update"),
Expand Down
5 changes: 4 additions & 1 deletion server/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { createLogger, format, transports } from "winston";
import fs from "fs";
import path from "path";

// Import package.json
import packageJson from "../package.json" assert { type: "json" };

const { combine, timestamp, printf, errors } = format;

const myFormat = printf(({ level, message, timestamp, stack, label }) => {
return `${timestamp} [${label}] [${level}]: ${stack || message}`;
});

export const logFilePath = "C:/ATHive/Titan.app.log";
export const logFilePath = packageJson.logFilePath;

// Ensure the log file exists
const logDir = path.dirname(logFilePath);
Expand Down
6 changes: 3 additions & 3 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ const server = createServer(app);
const io = new Server(server);

const latestVersion = packageJson.version;
const configFilePath = "C:/ATHive/Titan.config.json";
const targetsFilePath = "C:/ATHive/Titan.targets.txt";
const configFilePath = packageJson.configFilePath;
const targetsFilePath = packageJson.targetsFilePath;

// Use compression middleware
app.use(compression());
Expand Down Expand Up @@ -550,7 +550,7 @@ io.on("connection", (socket) => {

// logger.debug(`Branch: ${branchString(data.folder, data.version, data.branch)} | Revisions Behind: ${count}`);

let branchInfo = count == 0 ? "Up to date" : `Behind by ${count} revision${count > 1 ? "s" : ""}`;
let branchInfo = count == 0 ? "Latest" : `-${count} Revision${count > 1 ? "s" : ""}`;

emitBranchInfo(socket, data.id, branchInfo);
} catch (err) {
Expand Down
16 changes: 16 additions & 0 deletions src/AppContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const AppContext = createContext({
setSelectedBranches: (_) => {},
showSelectedBranchesLog: false,
setShowSelectedBranchesLog: (_) => {},
customScripts: [],
setCustomScripts: (_) => {},
isCommitMode: false,
setIsCommitMode: (_) => {},
selectedBranchStatuses: [],
Expand Down Expand Up @@ -118,6 +120,7 @@ export const AppProvider = ({ children }) => {
const branchTableGridRef = useRef(null);
const [selectedBranches, setSelectedBranches] = useState([]);
const [showSelectedBranchesLog, setShowSelectedBranchesLog] = useState(false);
const [customScripts, setCustomScripts] = useState([]);

// Props used in SectionCommit
const [isCommitMode, setIsCommitMode] = useState(false);
Expand Down Expand Up @@ -173,6 +176,17 @@ export const AppProvider = ({ children }) => {
setShowCommitView(false);
}, [configurableRowData]);

useEffect(() => {
if (!window.electron) return;
window.electron.fetchCustomScripts().then((data) => {
if (data.success) {
setCustomScripts(data.scripts);
return;
}
toast(createToastConfig(data.error, "error", 0, true));
});
}, [configurableRowData]);

/**** SectionCommit ****/
// Scroll to the commit region when it is in commit mode
useEffect(() => {
Expand Down Expand Up @@ -263,6 +277,8 @@ export const AppProvider = ({ children }) => {
setSelectedBranches,
showSelectedBranchesLog,
setShowSelectedBranchesLog,
customScripts,
setCustomScripts,
isCommitMode,
setIsCommitMode,
selectedBranchStatuses,
Expand Down
7 changes: 4 additions & 3 deletions src/components/ButtonDiff.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { IconButton } from "@chakra-ui/react";
import React from "react";
import React, { useCallback } from "react";
import { VscDiffSingle } from "react-icons/vsc";

export default function ButtonDiff(props) {
const { data, onDiffResult } = props;
const handleDiff = async () => {

const handleDiff = useCallback(async () => {
try {
const result = await window.electron.openTortoiseSVNDiff({
fullPath: data["Full Path"],
Expand All @@ -15,7 +16,7 @@ export default function ButtonDiff(props) {
} catch (error) {
onDiffResult({ success: false, error: error.message });
}
};
}, [data, onDiffResult]);

return <IconButton aria-label="Diff" size="sm" icon={<VscDiffSingle />} onClick={handleDiff} colorScheme="yellow" />;
}
18 changes: 18 additions & 0 deletions src/components/ButtonElectron.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IconButton, Tooltip } from "@chakra-ui/react";
import React from "react";
import { useCallback } from "react";


export default function ButtonElectron(props) {
const { icon, onClick, colorScheme, label, size } = props;

const handleClick = useCallback(() => {
if (onClick) onClick();
}, [onClick]);

return (
<Tooltip label={window.electron ? label : "Feature must be used in desktop application"} >
<IconButton aria-label={label} size={size} icon={icon} onClick={handleClick} colorScheme={colorScheme} isDisabled={!window.electron} />
</Tooltip>
);
}
19 changes: 19 additions & 0 deletions src/components/ButtonIconTooltip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IconButton, Tooltip } from "@chakra-ui/react";
import React from "react";
import { useCallback } from "react";

export default function ButtonIconTooltip(props) {
const { icon, onClick, colorScheme, label, size, placement, isDisabled = false } = props;

const handleClick = useCallback(() => {
if (onClick) onClick();
}, [onClick]);

return label && placement ? (
<Tooltip label={label} hasArrow placement={placement}>
<IconButton aria-label={label} size={size} icon={icon} onClick={handleClick} colorScheme={colorScheme} isDisabled={isDisabled} />
</Tooltip>
) : (
<IconButton aria-label={label} size={size} icon={icon} onClick={handleClick} colorScheme={colorScheme} isDisabled={isDisabled} />
);
}
50 changes: 29 additions & 21 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import { Heading, Icon, IconButton, Image, Link, Tooltip, useColorMode, Wrap, WrapItem } from "@chakra-ui/react";
import { Heading, Icon, Image, Link, useColorMode, Wrap, WrapItem } from "@chakra-ui/react";
import React, { useCallback } from "react";
import Logo from "../assets/Titan.png";
import { useApp } from "../AppContext";
import { MdBrowserUpdated, MdCode, MdCodeOff, MdDarkMode, MdSunny } from "react-icons/md";
import { IoReload } from "react-icons/io5";
import useSocketEmits from "../hooks/useSocketEmits";
import { LuFileCog } from "react-icons/lu";
import useNotifications from "../hooks/useNotifications";
import ButtonElectron from "./ButtonElectron";
import ButtonIconTooltip from "./ButtonIconTooltip";

export default function Header() {
const { config, isDebug, setIsDebug } = useApp();
const { isDebug, setIsDebug } = useApp();
const { emitOpenConfig } = useSocketEmits();
const { RaiseClientNotificaiton } = useNotifications();
const { colorMode, toggleColorMode } = useColorMode()
const { colorMode, toggleColorMode } = useColorMode();

const handleGetAppVersion = useCallback(() => {
if (!window.electron) return;

window.electron.getAppVersion().then((version) => {
RaiseClientNotificaiton(`Application Version: v${version}`, "info", 2000);
});
}, [RaiseClientNotificaiton]);

const handleReload = useCallback(() => {
window.location.reload();
}, []);

const handleCheckForUpdates = useCallback(() => {
window.electron.checkForUpdates().then((result) => {
Expand All @@ -23,13 +38,13 @@ export default function Header() {
});
}, [RaiseClientNotificaiton]);

const handleGetAppVersion = useCallback(() => {
if (!window.electron) return;
const handleOpenConfig = useCallback(() => {
emitOpenConfig();
}, [emitOpenConfig]);

window.electron.getAppVersion().then((version) => {
RaiseClientNotificaiton(`Application Version: v${version}`, "info", 2000);
});
}, [RaiseClientNotificaiton]);
const handleToggleDebug = useCallback(() => {
setIsDebug((prev) => !prev);
}, [setIsDebug]);

return (
<Wrap my={5} spacingY={5} justify={"space-between"}>
Expand All @@ -45,18 +60,11 @@ export default function Header() {
</Heading>
</WrapItem>
<WrapItem alignItems={"center"} columnGap={2}>
<Tooltip label={"Toggle Light/Dark Mode"} hasArrow placement="left">
<IconButton aria-label="Toggle light/dark mode" colorScheme={"yellow"} icon={<Icon as={colorMode === "light" ? MdSunny : MdDarkMode} />} onClick={toggleColorMode} />
</Tooltip>
<Tooltip label={"Check For Updates"} hasArrow placement="bottom-start" isDisabled={!window.electron}>
<IconButton aria-label="Check for updates" colorScheme={"yellow"} icon={<Icon as={MdBrowserUpdated} />} onClick={handleCheckForUpdates} isDisabled={!window.electron} />
</Tooltip>
<Tooltip label={"Open Config File"} hasArrow placement="bottom-start">
<IconButton aria-label="Open configuration file" colorScheme={"yellow"} icon={<Icon as={LuFileCog} />} onClick={() => emitOpenConfig()} />
</Tooltip>
<Tooltip label={`Current Debug Mode: ${isDebug ? "on" : "off"}`} hasArrow placement="right">
<IconButton aria-label="Toggle Debug Mode" colorScheme={"yellow"} icon={!isDebug ? <Icon as={MdCodeOff} /> : <Icon as={MdCode} />} onClick={() => setIsDebug((prev) => !prev)} />
</Tooltip>
<ButtonIconTooltip icon={<Icon as={colorMode === "light" ? MdSunny : MdDarkMode} />} onClick={toggleColorMode} colorScheme={"yellow"} label="Toggle Light/Dark Mode" placement={"bottom-start"} size="md" />
<ButtonIconTooltip icon={<Icon as={IoReload} />} onClick={handleReload} colorScheme={"yellow"} label="Reload" placement={"bottom-start"} size="md" />
<ButtonElectron icon={<Icon as={MdBrowserUpdated} />} onClick={handleCheckForUpdates} colorScheme={"yellow"} label="Check For Updates" size="md" />
<ButtonIconTooltip icon={<Icon as={LuFileCog} />} onClick={handleOpenConfig} colorScheme={"yellow"} label="Open Config File" placement={"bottom-start"} size="md" />
<ButtonIconTooltip icon={!isDebug ? <Icon as={MdCodeOff} /> : <Icon as={MdCode} />} onClick={handleToggleDebug} colorScheme={"yellow"} label={`Current Debug Mode: ${isDebug ? "on" : "off"}`} placement={"bottom-start"} size="md" />
</WrapItem>
</Wrap>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/SectionBranches.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default function SectionBranches() {

// Check for outdated branches when rowDataBranches changes
useEffect(() => {
const outdatedBranches = rowDataBranches.filter((row) => String(row["Branch Info"]).toLowerCase().includes("behind"));
const outdatedBranches = rowDataBranches.filter((row) => String(row["Branch Info"]).toLowerCase().includes("-"));
setOutdatedBranches(outdatedBranches);
}, [rowDataBranches]);

Expand Down
Loading

0 comments on commit 50f590e

Please sign in to comment.