6 Commits

Author SHA1 Message Date
6ec8894d44 added key events to popup component 2024-10-18 20:29:57 +00:00
7302b27dc3 update 2024-10-15 21:37:55 +00:00
e677a45ecd Refactor file loading and code retrieval logic 2024-09-14 18:12:56 +00:00
fb7d3e6061 added dictionary 2024-09-14 17:34:32 +00:00
25605200e1 Refactor file loading and code retrieval logic
This commit refactors the code in the `Page` component to improve the file loading and code retrieval logic. It removes the hardcoded initial code value and replaces it with an empty string. It also updates the `useEffect` hook to properly handle the startup of the application and retrieve the code content using the `Pterodactyl` API. Additionally, it updates the rendering of the breadcrumbs and adds a save button to the file editor.

Refactor file directory change functionality

This commit refactors the `changeDirectory` function in the `Index` component of the `FilesEditor` module. The function is removed as it is no longer used and has been replaced with a more efficient approach to handle directory changes.
2024-09-14 08:28:09 +00:00
faa45f7fcf loading file into text editor 2024-09-13 21:00:08 +00:00
14 changed files with 522 additions and 103 deletions

View File

@@ -1,24 +1,60 @@
"use client";
import React, { useCallback } from "react";
import React, { useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import TextEditor from "@/components/TextEditor";
import LanguageSelector from "@/components/TextEditor/LanguageSelector";
import { languages } from "@/components/TextEditor/LanguageSelector/languages";
import BreadCrumbs from "@/components/BreadCrumbs";
import ServerIcon from "@/components/Icons/Server";
import FolderIcon from "@/components/Icons/Folder";
import Pterodactyl from "@/components/Pterodactyl";
import Panel from "@/components/Panel";
function Page() {
const searchParams = useSearchParams();
const serverId = searchParams.get("serverid") || "";
const path = searchParams.get("path") || "";
const [code, setCode] = useState("");
const [language, setLanguage] = useState("plaintext");
const changeDirectory = useCallback((path: string) => {
console.log("changeDirectory", path);
}, []);
const setLanguageHandler = (event: any) => {
setLanguage(event.target.value);
};
const saveFile = async () => {
const panelApp = new Panel();
const pterodactyl = new Pterodactyl();
const credentials = await panelApp.getCredentials();
pterodactyl.setApiKey(credentials.api_key);
pterodactyl.setServerId(serverId);
await pterodactyl.files.saveContent(path, code);
};
useEffect(
function () {
async function startupApp() {
const panelApp = new Panel();
const pterodactyl = new Pterodactyl();
const credentials = await panelApp.getCredentials();
pterodactyl.setApiKey(credentials.api_key);
pterodactyl.setServerId(serverId);
setCode(await pterodactyl.files.getContent(path));
}
if (serverId && path) {
startupApp();
}
},
[serverId, path]
);
return (
<>
<div className="flex mb-4 justify-between">
<BreadCrumbs>
<li>
<div className="mr-2">
@@ -54,7 +90,17 @@ function Page() {
);
})}
</BreadCrumbs>
<TextEditor />;
<div className="flex justify-end gap-4">
<LanguageSelector onChange={setLanguageHandler} />
<button
className="ml-auto btn btn-base-100 text-success"
onClick={saveFile}
>
Save
</button>
</div>
</div>
<TextEditor code={code} language={language} />
</>
);
}

View File

@@ -1,11 +1,27 @@
import Header from "@/components/Header"
"use client";
import React, { useState } from "react";
import Popup from "@/components/Popup";
export default function Page() {
const [number, addNumber] = useState(0);
const [showPopup, setShowPopup] = useState(true);
return (
<div>
<Header name='Testowy test' age={18} isMan={true} />
<Header name='Ala ma kota' age={24} isMan={false} />
Main page test
</div>
)
<>
<Popup
show={showPopup}
title="Popup Title"
onClick={() => setShowPopup(false)}
>
<span className="text-base-content">{number}</span>
<button className="btn btn-sm" onClick={() => addNumber(number + 1)}>
Add{" "}
</button>
</Popup>
<button className="btn btn-sm" onClick={() => setShowPopup(!showPopup)}>
Toggle Popup
</button>
</>
);
}

View File

@@ -0,0 +1,74 @@
"use client";
import React, { useState, useRef } from "react";
import AddDocumentIcon from "@/components/Icons/AddDocument";
import Popup from "@/components/Popup";
import Pterodactyl from "@/components/Pterodactyl";
import { useSearchParams } from "next/navigation";
const Index = () => {
const [showPopup, setShowPopup] = useState(false);
const [fileName, setFileName] = useState("");
const urlParams = useSearchParams();
const serverId = urlParams.get("serverid") || "";
const pathParam = urlParams.get("path") || "/";
const apiKey = `${process.env.NEXT_PUBLIC_API_KEY}`;
const inputRef = useRef<HTMLInputElement>(null);
const pterodactyl = new Pterodactyl();
function togglePopup() {
setShowPopup(!showPopup);
if (!showPopup) {
// set focus on input
setTimeout(() => {
inputRef.current?.focus();
}, 100);
}
}
function handleOk() {
console.log({ fileName });
if (fileName) {
pterodactyl.setApiKey(apiKey);
pterodactyl.setServerId(serverId);
pterodactyl.helpers.setWorkingDirectory(pathParam);
pterodactyl.files.createFile(fileName);
}
setShowPopup(false);
}
return (
<>
<Popup
title="New file name"
show={showPopup}
onClickClose={togglePopup}
onClickOk={handleOk}
>
<div>
<input
ref={inputRef}
type="text"
className="input input-sm input-bordered mb-4 text-base-content"
placeholder="File name"
value={fileName}
onChange={(e) => setFileName(e.target.value)}
/>
</div>
</Popup>
<button
onClick={() => {
setFileName("");
togglePopup();
}}
className="btn btn-sm"
>
<AddDocumentIcon />
Add Document
</button>
</>
);
};
export default Index;

View File

@@ -11,6 +11,7 @@ import RenamePopup from "./ContextMenu/rename";
import Pterodactyl from "@/components/Pterodactyl";
import BreadCrumbs from "@/components/BreadCrumbs";
import { useSearchParams } from "next/navigation";
import AddDocument from "./AddDocument";
interface FileAttributes {
name: string;
@@ -23,6 +24,12 @@ interface FileProps {
attributes: FileAttributes;
}
interface NewDocumentState {
show: boolean;
x: number;
y: number;
}
interface ContextMenuState {
show: boolean;
x: number;
@@ -34,6 +41,12 @@ interface RenamePopupState {
show: boolean;
}
const initialNewDocumentState: NewDocumentState = {
show: false,
x: 0,
y: 0,
};
const initialContextMenuState: ContextMenuState = {
show: false,
x: 0,
@@ -57,7 +70,7 @@ const Index = () => {
initialRenamePopupState
);
const [selectedFile, setSelectedFile] = useState<FileProps | null>(null);
const [ptero, setPtero] = useState<Pterodactyl | null>(null);
const pterodactyl = useMemo(() => new Pterodactyl(), []);
const urlParams = useSearchParams();
const serverId = urlParams.get("serverid");
const pathParam = urlParams.get("path") || "/";
@@ -71,21 +84,20 @@ const Index = () => {
setRenamePopup(initialRenamePopupState);
}, []);
const fetchFiles = useCallback(async (ptero: Pterodactyl) => {
console.log("chwytam pliki");
const files = await ptero.files.fetchFiles();
const fetchFiles = useCallback(async () => {
const files = await pterodactyl.files.fetchFiles();
setFileList(files);
}, []);
}, [pterodactyl]);
const handleRenameFile = useCallback(
async (file: FileProps, newName: string) => {
if (ptero) {
await ptero.files.rename(file, newName);
await fetchFiles(ptero);
if (pterodactyl) {
await pterodactyl.files.rename(file, newName);
await fetchFiles();
setRenamePopup(initialRenamePopupState);
}
},
[ptero, fetchFiles]
[pterodactyl, fetchFiles]
);
const handleClickContextMenu = useCallback(
@@ -100,31 +112,15 @@ const Index = () => {
setContextMenu(initialContextMenuState);
}, []);
const changeDirectory = useCallback(
(newPath: string) => {
if (ptero) {
ptero.helpers.setWorkingDirectory(newPath);
fetchFiles(ptero);
// setPath(newPath);
}
},
[ptero, fetchFiles]
);
useEffect(() => {
const setupApplication = () => {
if (!apiKey || !serverId) return; // Upewniamy się, że mamy apiKey i serverId
if (!ptero) {
const pteroInstance = new Pterodactyl(serverId, apiKey);
setPtero(pteroInstance);
pteroInstance.helpers.setWorkingDirectory(pathParam);
fetchFiles(pteroInstance); // Wywołanie fetchFiles raz po ustawieniu instancji
}
};
pterodactyl.setApiKey(apiKey);
pterodactyl.setServerId(serverId);
pterodactyl.helpers.setWorkingDirectory(pathParam);
setupApplication();
}, [apiKey, serverId, ptero, pathParam, fetchFiles]);
fetchFiles(); // Wywołanie fetchFiles raz po ustawieniu instancji
}, [apiKey, serverId, pterodactyl, pathParam, fetchFiles]);
return (
<>
@@ -147,7 +143,7 @@ const Index = () => {
)}
{serverId && (
<div className="flex p-4 gap-4">
<div className="flex gap-4">
<BreadCrumbs>
<li>
<div className="mr-2">
@@ -184,20 +180,7 @@ const Index = () => {
})}
<li>
<span className="inline-flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
className="h-4 w-4 stroke-current"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
></path>
</svg>
Add Document
<AddDocument />
</span>
</li>
</BreadCrumbs>

View File

@@ -0,0 +1,25 @@
import React from "react";
const index = () => {
return (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
></path>
</svg>
</>
);
};
export default index;

View File

@@ -0,0 +1,16 @@
interface Credentials {
api_key: string;
}
class Panel {
constructor() {}
// this method is responsible for fetching credentials from the server
async getCredentials(): Promise<Credentials> {
return {
api_key: `${process.env.NEXT_PUBLIC_API_KEY}`,
};
}
}
export default Panel;

View File

@@ -0,0 +1,69 @@
"use client";
import React, { useEffect } from "react";
interface PopupProps extends React.HTMLAttributes<HTMLDivElement> {
show?: boolean;
title?: string;
children: React.ReactNode;
onClickClose?: React.MouseEventHandler<HTMLButtonElement>;
onClickOk?: React.MouseEventHandler<HTMLButtonElement>;
}
const Popup: React.FC<PopupProps> = ({
show = false,
title,
children,
onClickClose,
onClickOk,
...props
}) => {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape" && onClickClose) {
onClickClose(event as unknown as React.MouseEvent<HTMLButtonElement>);
}
if (event.key === "Enter") {
if (onClickOk) {
onClickOk(event as unknown as React.MouseEvent<HTMLButtonElement>);
}
}
};
if (show) {
window.addEventListener("keydown", handleKeyDown);
}
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [show, onClickClose]);
if (!show) return null;
return (
<>
<div className="fixed top-0 left-0 w-full h-full bg-black opacity-75 z-10"></div>
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-20">
<div className="bg-base-100 p-8 rounded-lg">
{title && (
<h2 className="text-xl font-bold mb-4 text-base-content">
{title}
</h2>
)}
{children}
<div className="flex justify-between">
<button onClick={onClickClose} className="btn btn-sm">
Close
</button>
<button onClick={onClickOk} className="btn btn-sm btn-success">
Ok
</button>
</div>
</div>
</div>
</>
);
};
export default Popup;

View File

@@ -1,3 +1,4 @@
import { get } from "http";
import { File } from "./interfaces";
export default function files(pterodactyl: any) {
@@ -57,5 +58,48 @@ export default function files(pterodactyl: any) {
console.error("Error fetching data:", error);
}
},
async getContent(filename: string) {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_URL}/api/client/servers/${pterodactyl.server_id}/files/contents?file=${filename}`,
{
method: "GET",
headers: await pterodactyl.helpers.authHeader(),
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.text();
return data;
} catch (error) {
console.error("Error fetching data:", error);
}
},
async saveContent(filename: string, content: string) {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_URL}/api/client/servers/${pterodactyl.server_id}/files/write?file=${filename}`,
{
method: "POST",
headers: await pterodactyl.helpers.authHeader("application/text"),
body: "'test'",
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching data:", error);
}
},
async createFile(filename: string) {
this.saveContent(filename, "");
},
};
}

View File

@@ -1,10 +1,10 @@
export default function helpers(pterodactyl: any) {
return {
// helper what return auth header
async authHeader() {
async authHeader(contentType: string = "application/json") {
return {
Authorization: `Bearer ${pterodactyl.api_key}`,
"Content-Type": "application/json",
"Content-Type": contentType,
Accept: "Application/vnd.pterodactyl.v1+json",
};
},
@@ -28,5 +28,16 @@ export default function helpers(pterodactyl: any) {
async getWorkingDirectory() {
return pterodactyl.workingDirectory;
},
// helper that get main site and get csrf token from it
async getCsrfToken() {
const response = await fetch(`${process.env.NEXT_PUBLIC_URL}`, {
method: "GET",
});
const text = await response.text();
const match = text.match(/<meta name="csrf-token" content="(.*)">/);
const csrfToken = match ? match[1] : null;
return csrfToken;
},
};
}

View File

@@ -2,15 +2,21 @@ import filesModule from "./files";
import helpersModule from "./helpers";
class Pterodactyl {
server_id: string;
api_key: string;
server_id: string = "";
api_key: string = "";
files: any;
helpers: any;
workingDirectory: string = "/";
constructor(server_id: string, api_key: string) {
this.server_id = server_id;
setApiKey(api_key: string) {
this.api_key = api_key;
}
setServerId(server_id: string) {
this.server_id = server_id;
}
constructor() {
this.files = filesModule(this);
this.helpers = helpersModule(this);
}

View File

@@ -0,0 +1,46 @@
import React, { useState } from "react";
import { languages } from "./languages";
interface LanguageSelectorProps {
onChange: (event: any) => void;
defaultLanguage?: string;
}
const Index = (props: LanguageSelectorProps) => {
const { onChange } = props;
let { defaultLanguage } = props;
if (!defaultLanguage) {
defaultLanguage = "plaintext";
}
const [selectedLanguage, setSelectedLanguage] =
useState<string>(defaultLanguage);
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedLanguage(event.target.value);
onChange(event);
};
return (
<select
className="select bg-base-200 text-base-content w-full max-w-xs"
value={selectedLanguage}
onChange={handleChange}
>
{Object.entries(languages).map(([key, value]) => (
<option
disabled={selectedLanguage === key}
defaultValue={selectedLanguage}
// selected={selectedLanguage === key}
key={key}
value={key}
>
{value}
</option>
))}
</select>
);
};
export default Index;

View File

@@ -0,0 +1,73 @@
// interface of supported languages what are available at https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages
export const languages = {
abap: "ABAP",
apex: "Apex",
azcli: "Azure CLI",
bat: "Batch",
cameligo: "Cameligo",
clojure: "Clojure",
coffee: "CoffeeScript",
cpp: "C++",
csharp: "C#",
csp: "CSP",
css: "CSS",
dart: "Dart",
dockerfile: "Dockerfile",
ebnf: "EBNF",
fsharp: "F#",
go: "Go",
graphql: "GraphQL",
handlebars: "Handlebars",
hcl: "HCL",
html: "HTML",
ini: "INI",
java: "Java",
javascript: "JavaScript",
json: "JSON",
julia: "Julia",
kotlin: "Kotlin",
less: "Less",
lexon: "Lexon",
lua: "Lua",
markdown: "Markdown",
mips: "MIPS",
msdax: "MSDAX",
mysql: "MySQL",
objectivec: "Objective-C",
pascal: "Pascal",
pascaligo: "Pascaligo",
perl: "Perl",
pgsql: "PL/pgSQL",
php: "PHP",
plaintext: "Plain Text",
postiats: "Postiats",
powerquery: "Power Query",
powershell: "PowerShell",
pug: "Pug",
python: "Python",
r: "R",
razor: "Razor",
redis: "Redis",
redshift: "Redshift",
restructuredtext: "reStructuredText",
ruby: "Ruby",
rust: "Rust",
sb: "SB",
scala: "Scala",
scheme: "Scheme",
scss: "SCSS",
shell: "Shell",
solidity: "Solidity",
sql: "SQL",
st: "ST",
swift: "Swift",
systemverilog: "SystemVerilog",
tcl: "Tcl",
twig: "Twig",
typescript: "TypeScript",
vb: "VB",
vba: "VBA",
verilog: "Verilog",
xml: "XML",
yaml: "YAML",
};

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import MonacoEditor, { OnChange } from "@monaco-editor/react";
interface EditorProps {
@@ -6,11 +6,14 @@ interface EditorProps {
width?: string;
theme?: string;
language?: string;
code?: string;
onChange?: OnChange;
}
const Editor = (props: EditorProps) => {
const [code, setCode] = useState<string>('console.log("Hello, Monaco!");');
const [code, setCode] = useState<string>(
props.code || 'console.log("Hello, Monaco!");'
);
const handleEditorChange: OnChange = (value) => {
setCode(value || "");
@@ -21,16 +24,23 @@ const Editor = (props: EditorProps) => {
width = "100%",
theme = "vs-dark",
language = "typescript",
code: _code = code, // z props, jeśli jest dostępne
onChange = handleEditorChange,
} = props;
// Efekt do synchronizacji props.code z wewnętrznym stanem code
useEffect(() => {
if (_code !== undefined) {
setCode(_code);
}
}, [_code]);
return (
<MonacoEditor
height={height}
width={width}
language={language}
theme={theme}
defaultValue={code}
value={code}
onChange={onChange}
/>