From 09a9a22bcff4c9d9ba00e6169f977d46671d626a Mon Sep 17 00:00:00 2001
From: Jonas
Date: Wed, 17 Jan 2024 18:54:16 +0100
Subject: [PATCH 1/2] Added Notes Feature
---
package.json | 2 +-
src-tauri/tauri.conf.json | 2 +-
src/Icons/pack_1.tsx | 18 +++
src/Interfaces/IConfigFile.ts | 34 +++++
.../JobCardComponent/JobCardComponent.tsx | 4 +-
src/helpers/StorageManager.ts | 141 +++++++++++++++---
.../Jarvis/Views/BuildView/BuildView.tsx | 34 ++---
.../BooleanParameterValue.tsx | 5 +-
.../CredentialsParameterValue.tsx | 6 +-
.../FileParameterValue.tsx | 6 +-
.../ParameterComponents/OtherParameter.tsx | 6 +-
.../PasswordParameterValue.tsx | 6 +-
.../StringParameterValue.tsx | 6 +-
.../TextParameterValue.tsx | 6 +-
.../Jarvis/Views/StatusView/NotesModal.tsx | 125 ++++++++++++++++
.../Jarvis/Views/StatusView/StatusView.tsx | 12 +-
.../Jarvis/Views/SwitchProjectView/Modal.tsx | 4 +-
17 files changed, 352 insertions(+), 65 deletions(-)
create mode 100644 src/Interfaces/IConfigFile.ts
create mode 100644 src/screens/Jarvis/Views/StatusView/NotesModal.tsx
diff --git a/package.json b/package.json
index f121826..d5c59cd 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "jarvis",
"private": true,
- "version": "0.9.7",
+ "version": "0.9.75",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index ba20d7e..e0b1255 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -7,7 +7,7 @@
},
"package": {
"productName": "Jarvis Client",
- "version": "0.9.7"
+ "version": "0.9.75"
},
"tauri": {
"allowlist": {
diff --git a/src/Icons/pack_1.tsx b/src/Icons/pack_1.tsx
index 877ed06..6b4124a 100644
--- a/src/Icons/pack_1.tsx
+++ b/src/Icons/pack_1.tsx
@@ -321,3 +321,21 @@ export const IcoClose = ({ size = 24, color = "currentColor", className }: IconP
);
+
+// Icon Pack: Phosphor Icons
+// License: MIT - Copyright (c) 2023 Phosphor Icons
+// Used in: Params View
+export const IcoNotes = ({ size = 24, color = "currentColor", className }: IconProps) => (
+
+);
+
+// Icon Pack: Phosphor Icons
+// License: MIT - Copyright (c) 2023 Phosphor Icons
+// Used in: Params View
+export const IcoSave = ({ size = 24, color = "currentColor", className }: IconProps) => (
+
+);
diff --git a/src/Interfaces/IConfigFile.ts b/src/Interfaces/IConfigFile.ts
new file mode 100644
index 0000000..dc76269
--- /dev/null
+++ b/src/Interfaces/IConfigFile.ts
@@ -0,0 +1,34 @@
+export interface ConfigFile {
+ "baseurl" : string | undefined
+ "username" : string | undefined
+ "apiToken" : string | undefined
+ "onboardState" : string | undefined
+ "openInBrowser" : boolean | undefined
+ "pinnedJobs" : string[] | undefined
+ "projectName" : string | undefined
+ "notificationSetJobs" : string[] | undefined
+ "projects" : string[] | undefined
+ "titlebarStyle" : "winStyle" | "macStyle" | "eggStyle" | undefined
+ "notificationPermission" : string | undefined
+ "notes": Notes[] | undefined
+}
+
+interface Notes {
+ [key: string]: {
+ [key: string]: string
+ }
+}
+
+export type allowedKeys =
+ "baseurl" |
+ "username" |
+ "apiToken" |
+ "onboardState" |
+ "openInBrowser" |
+ "pinnedJobs" |
+ "projectName" |
+ "notificationSetJobs" |
+ "projects" |
+ "titlebarStyle" |
+ "notificationPermission" |
+ "notes";
\ No newline at end of file
diff --git a/src/components/JobCardComponent/JobCardComponent.tsx b/src/components/JobCardComponent/JobCardComponent.tsx
index a85df50..0a16010 100644
--- a/src/components/JobCardComponent/JobCardComponent.tsx
+++ b/src/components/JobCardComponent/JobCardComponent.tsx
@@ -44,9 +44,9 @@ const JobCardComponent: React.FC = ({
{pinned && }
{notification_set && }
-
+
{buildData.description}
-
+
);
diff --git a/src/helpers/StorageManager.ts b/src/helpers/StorageManager.ts
index 7b86b7b..ff6d394 100644
--- a/src/helpers/StorageManager.ts
+++ b/src/helpers/StorageManager.ts
@@ -1,26 +1,15 @@
+import { BaseDirectory, createDir, exists, readTextFile, writeTextFile } from "@tauri-apps/api/fs";
import Logger from "./Logger";
+import { ConfigFile, allowedKeys } from "@/Interfaces/IConfigFile";
+import { CONFIG_FILE } from "@/config/constants";
-type allowedKeys =
- "baseurl" |
- "username" |
- "apiToken" |
- "onboardState" |
- "openInBrowser" |
- "pinnedJobs" |
- "projectName" |
- "notificationSetJobs" |
- "projects" |
- "titlebarStyle" |
- "notificationPermission";
-
-/*
-const saveToConfigFile = async (content: ConfigFile, CONFIG_FILE: string) => {
+const saveToConfigFile = async (content: ConfigFile) => {
await writeTextFile(CONFIG_FILE, JSON.stringify(content), {
dir: BaseDirectory.AppData
});
};
-const getConfigFile = async (CONFIG_FILE: string): Promise => {
+const getConfigFile = async (): Promise => {
const appDataDirExists = await exists("", { dir: BaseDirectory.AppData });
if (!appDataDirExists) {
@@ -28,14 +17,130 @@ const getConfigFile = async (CONFIG_FILE: string): Promise => {
}
if (await exists(CONFIG_FILE, { dir: BaseDirectory.AppData })) {
+ let content = await readTextFile(CONFIG_FILE, { dir: BaseDirectory.AppData });
+
+ // If the file is empty, return an empty object
+ if (content === "") return {} as ConfigFile;
+
// File exists, return it
- return await JSON.parse(await readTextFile(CONFIG_FILE, { dir: BaseDirectory.AppData }));
+ return await JSON.parse(content);
} else {
// File doesnt exist, return empty object
return {} as ConfigFile;
}
};
-*/
+
+/**
+ * @classdesc A class to manage the storage of data in the browser.
+ * @note This is still a work in progress and will be adapated gradually.
+ */
+export const new_StorageManager = {
+ get: async (key: allowedKeys): Promise => {
+ try {
+ let configFile = await getConfigFile();
+
+ if (configFile[key] === undefined) return false;
+
+ return configFile[key];
+ } catch (error) {
+ Logger.error("helpers/StorageManager.ts", error);
+ return false;
+ }
+ },
+ save: async (key: K, value: ConfigFile[K]): Promise => {
+ try {
+ let configFile = await getConfigFile();
+ configFile[key] = value;
+ await saveToConfigFile(configFile);
+
+ return true;
+ } catch (error) {
+ Logger.error("helpers/StorageManager.ts", error);
+ return false;
+ }
+ },
+ removeItem: async (key: allowedKeys): Promise => {
+ try {
+ let configFile = await getConfigFile();
+ delete configFile[key];
+ await saveToConfigFile(configFile);
+
+ return true;
+ } catch (error) {
+ Logger.error("helpers/StorageManager.ts", error);
+ return false;
+ }
+ },
+ clearAll: async (): Promise => {
+ try {
+ await saveToConfigFile({} as ConfigFile);
+ return true;
+ } catch (error) {
+ Logger.error("helpers/StorageManager.ts", error);
+ return false;
+ }
+ },
+ notes: {
+ get: async (project: string, job: string): Promise => {
+ try {
+ const configFile = await getConfigFile();
+
+ if (configFile.notes) {
+ const projectNotes = configFile.notes.find((note) => note[project]);
+
+ if (projectNotes) {
+ return projectNotes[project][job];
+ }
+ }
+
+ return undefined;
+ } catch (error) {
+ Logger.error("helpers/StorageManager.ts", error);
+ return undefined;
+ }
+ },
+ save: async (project: string, job: string, note: string): Promise => {
+ try {
+ const configFile = await getConfigFile();
+
+ // Ensure that the notes object and its nested properties are defined
+ if (!configFile.notes) {
+ configFile.notes = [];
+ Logger.debug("Empty notes array created")
+ }
+
+ const projectIndex = configFile.notes.findIndex((note) => Object.keys(note)[0] === project);
+ if (!configFile.notes[projectIndex] || configFile.notes[projectIndex] === undefined || projectIndex === -1) {
+ configFile.notes[projectIndex] = {};
+ Logger.debug("Empty project index created")
+ }
+
+ if (projectIndex < 0) {
+ configFile.notes.push({
+ [project]: {
+ [job]: note,
+ },
+ });
+ } else {
+ configFile.notes[projectIndex][project][job] = note;
+ }
+
+ Logger.debug("Saving note", configFile.notes);
+
+ await saveToConfigFile(configFile);
+
+ return true;
+ } catch (error) {
+ Logger.error("helpers/StorageManager.ts", error);
+ return false;
+ }
+ },
+
+
+ },
+};
+
+
/**
* @classdesc A class to manage the storage of data in the browser.
diff --git a/src/screens/Jarvis/Views/BuildView/BuildView.tsx b/src/screens/Jarvis/Views/BuildView/BuildView.tsx
index a6ea470..2f46dcf 100644
--- a/src/screens/Jarvis/Views/BuildView/BuildView.tsx
+++ b/src/screens/Jarvis/Views/BuildView/BuildView.tsx
@@ -139,23 +139,23 @@ const BuildView: React.FC = ({ parameterDefinition, buildData }) => {
{getComponentForParameter(parameter)}
))}
-
-
- Start Build
-
-
- BUILD STARTED
-
-
+
+
+ Start Build
+
+
+ BUILD STARTED
+
+
);
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx
index c27c098..62d6d15 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx
@@ -8,8 +8,8 @@ interface Props {
const BooleanParameterValue: React.FC = ({ parameter }): React.JSX.Element => (
-
-
+
+
{parameter.name}
{parameter.description}
@@ -23,7 +23,6 @@ const BooleanParameterValue: React.FC
= ({ parameter }): React.JSX.Elemen
value={parameter.value ? "✔" : ""}
className="w-[30px] h-[30px] bg-property-background font-medium rounded-md text-comment-color px-2 mt-2 active:bg-property-background-selected hover:brightness-[1.3]"
/>
-
);
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/CredentialsParameterValue.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/CredentialsParameterValue.tsx
index 9acfa96..daa2fb6 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/CredentialsParameterValue.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/CredentialsParameterValue.tsx
@@ -7,9 +7,9 @@ interface Props {
}
const CredentialsParameterValue: React.FC
= ({ parameter }): React.JSX.Element => (
-
-
-
+
+
+
{parameter.name}
{parameter.description}
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/FileParameterValue.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/FileParameterValue.tsx
index ff7a2f0..f0a57b8 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/FileParameterValue.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/FileParameterValue.tsx
@@ -25,9 +25,9 @@ const FileParameterValue: React.FC
= ({ parameter, buildNumber }): React.
};
return (
-
-
-
+
+
+
{parameter.name}
{parameter.description}
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/OtherParameter.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/OtherParameter.tsx
index c18299c..f1ccef2 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/OtherParameter.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/OtherParameter.tsx
@@ -7,9 +7,9 @@ interface Props {
}
const OtherParameterValue: React.FC
= ({ parameter }): React.JSX.Element => (
-
-
-
+
+
+
{parameter.name}
{parameter.description}
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/PasswordParameterValue.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/PasswordParameterValue.tsx
index 36aeb35..083508b 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/PasswordParameterValue.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/PasswordParameterValue.tsx
@@ -7,9 +7,9 @@ interface Props {
}
const PasswordParameterValue: React.FC
= ({ parameter }): JSX.Element => (
-
-
-
+
+
+
{parameter.name}
{parameter.description}
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/StringParameterValue.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/StringParameterValue.tsx
index 9f53df9..1bd1b85 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/StringParameterValue.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/StringParameterValue.tsx
@@ -7,9 +7,9 @@ interface Props {
}
const StringParameterValue: React.FC
= ({ parameter }): JSX.Element => (
-
-
-
+
+
+
{parameter.name}
{parameter.description}
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/TextParameterValue.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/TextParameterValue.tsx
index 48b3d88..b69c2b5 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/TextParameterValue.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/TextParameterValue.tsx
@@ -7,9 +7,9 @@ interface Props {
}
const TextParameterValue: React.FC
= ({ parameter }): JSX.Element => (
-
-
-
+
+
+
{parameter.name}
{parameter.description}
diff --git a/src/screens/Jarvis/Views/StatusView/NotesModal.tsx b/src/screens/Jarvis/Views/StatusView/NotesModal.tsx
new file mode 100644
index 0000000..7a7330c
--- /dev/null
+++ b/src/screens/Jarvis/Views/StatusView/NotesModal.tsx
@@ -0,0 +1,125 @@
+import React, { useEffect } from "react";
+import { motion } from "framer-motion";
+import { IcoCross, IcoNotes, IcoSave } from "@/Icons/pack_1";
+import StorageManager, { new_StorageManager } from "@/helpers/StorageManager";
+import showdown from "showdown";
+import { IJenkinsBuild } from "@/Interfaces/IBuildInterface";
+import Logger from "@/helpers/Logger";
+
+interface Props {
+ isOpen: boolean;
+ closeModal: () => void;
+ buildData: IJenkinsBuild;
+}
+
+const NotesModal: React.FC
= ({ isOpen, closeModal, buildData }) => {
+ const [notes, setNotes] = React.useState("");
+ const [projectName, setProjectName] = React.useState("");
+ const [inEditMode, setInEditMode] = React.useState(false);
+ const [notesData, setNotesData] = React.useState("");
+
+ const saveNotes = () => {
+ new_StorageManager.notes.save(projectName, buildData.id, notes);
+ return true;
+ }
+
+ const renderMarkdown = (str: string): { __html: string } => {
+ const converter = new showdown.Converter();
+ const html: string = converter.makeHtml(str);
+ return { __html: html };
+ };
+
+ useEffect(() => {
+ const init = async () => {
+ const project = await StorageManager.get("projectName");
+ if (!project) return;
+ setProjectName(project);
+
+ const data = await new_StorageManager.notes.get(project, buildData.id);
+
+ if (data) {
+ setNotesData(data);
+ setNotes(data);
+ }
+
+ if (data === "" || data === undefined) {
+ setInEditMode(true);
+ }
+ }
+
+ init();
+
+ Logger.info("NotesModal.tsx", "NotesModal mounted");
+
+ }, [buildData]);
+
+ return (isOpen ? (
+ closeModal()} >
+ e.stopPropagation()} >
+
closeModal()}
+ className="absolute top-4 right-4 cursor-pointer">
+
+
+
+ {inEditMode ? (
+ /* Edit Section */
+
+
+
Edit Notes for Build #{buildData.id}
+
+ ) : (
+ /* View Section */
+
+
Notes for Build #{buildData.id}
+
+
+ )}
+
+
+
+ {/* Button */}
+
+
{
+ setInEditMode(!inEditMode)
+ if (inEditMode) {
+ saveNotes()
+ setNotesData(notes)
+ }
+
+ }}
+ className="fixed rounded-full w-12 h-12 bg-background-card hover:bg-background-card-selected transition flex justify-center items-center shadow-md hover:shadow-sm cursor-pointer">
+
+
+
+
+
+
+
+
+
+
+ ) : null);
+}
+
+export default NotesModal;
diff --git a/src/screens/Jarvis/Views/StatusView/StatusView.tsx b/src/screens/Jarvis/Views/StatusView/StatusView.tsx
index f5424dd..a5d9816 100644
--- a/src/screens/Jarvis/Views/StatusView/StatusView.tsx
+++ b/src/screens/Jarvis/Views/StatusView/StatusView.tsx
@@ -1,16 +1,19 @@
-import React from "react";
+import React, { useState } from "react";
import { openLink } from "../../../../helpers/utils";
import StorageManager from "../../../../helpers/StorageManager";
import { IJenkinsBuild, JenkinsBuildAction, JenkinsBuildArtifact } from "../../../../Interfaces/IBuildInterface";
import { motion } from "framer-motion";
-import { IcoCodeBracket, IcoCube, IcotInformation, IcoPerson } from "@/Icons/pack_1";
+import { IcoCodeBracket, IcoCube, IcotInformation, IcoPerson, IcoNotes } from "@/Icons/pack_1";
+import NotesModal from "./NotesModal";
interface Props {
buildData: IJenkinsBuild;
}
const StatusView: React.FC = ({ buildData }) => {
+ const [notesModalOpen, setNotesModalOpen] = useState(false);
+
const openArtifact = async (artifact: string = "") => {
const baseURL: string = StorageManager.get("baseurl") || "";
const projectName: string = StorageManager.get("projectName") || "";
@@ -109,7 +112,9 @@ const StatusView: React.FC = ({ buildData }) => {
Started {startedAgo()} ago.
{buildData?.duration > 0 && Duration: {formatMilliseconds(buildData?.duration)}
}
{buildData?.estimatedDuration > 0 && Estimated Duration: {formatMilliseconds(buildData?.estimatedDuration)}
}
-
+ setNotesModalOpen(true)}
+ className="flex space-x-2 cursor-pointer">Notes
@@ -180,6 +185,7 @@ const StatusView: React.FC
= ({ buildData }) => {
+ {notesModalOpen &&
setNotesModalOpen(false)} />}
);
};
diff --git a/src/screens/Jarvis/Views/SwitchProjectView/Modal.tsx b/src/screens/Jarvis/Views/SwitchProjectView/Modal.tsx
index ef324f3..8762ed4 100644
--- a/src/screens/Jarvis/Views/SwitchProjectView/Modal.tsx
+++ b/src/screens/Jarvis/Views/SwitchProjectView/Modal.tsx
@@ -15,11 +15,11 @@ const Modal: React.FC
= ({ isOpen, closeModal, modalData }) => (isOpen ?
animate={{ opacity: 1 }}
transition={{ duration: 0.1 }}
exit={{ opacity: 0 }}
- className="absolute top-0 left-0 w-full h-full backdrop-blur-sm bg-black/70 transition flex justify-center items-center"
+ className="absolute top-0 left-0 w-full z-40 h-full backdrop-blur-sm bg-black/70 transition flex justify-center items-center"
onClick={() => closeModal()}
>
e.stopPropagation()}
>
Date: Wed, 17 Jan 2024 18:54:30 +0100
Subject: [PATCH 2/2] Update BooleanParameterValue.tsx
---
.../ParameterComponents/BooleanParameterValue.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx
index 62d6d15..dba0e84 100644
--- a/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx
+++ b/src/screens/Jarvis/Views/ParametersView/ParameterComponents/BooleanParameterValue.tsx
@@ -9,7 +9,7 @@ interface Props {
const BooleanParameterValue: React.FC
= ({ parameter }): React.JSX.Element => (
-
+
{parameter.name}
{parameter.description}