Skip to content

Commit

Permalink
Add Code Editor For Local Configurations (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
excalith authored Mar 3, 2023
1 parent 50c02b8 commit 81078db
Show file tree
Hide file tree
Showing 22 changed files with 389 additions and 169 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Clone this repo and run `yarn dev` command to host it locally on your machine
- Show info with `fetch` command (time, date, system and browser data)
- Update your configuration with `config` command
- `config import <url>` - Import configuration from a URL to your local storage
- `config export` - Export your configuration as a blob
- `config edit` - Edit local configuration within editor
- `config reset` - Reset your configuration to default

## Customization
Expand Down
1 change: 1 addition & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"@/*": ["./src/*"],
"@components/*": ["src/components/*"],
"@pages/*": ["src/pages/*"],
"@hooks/*": ["src/hooks/*"],
"@utils/*": ["src/utils/*"]
}
}
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
"dependencies": {
"@fontsource/fira-code": "^4.5.13",
"@iconify/react": "^4.1.0",
"ace-builds": "^1.15.3",
"moment": "^2.29.4",
"next": "13.1.6",
"one-theme-ace": "^0.5.0",
"react": "18.2.0",
"react-ace": "^10.1.0",
"react-device-detect": "^2.2.2",
"react-dom": "18.2.0"
}
Expand Down
114 changes: 74 additions & 40 deletions src/components/Config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import React, { useEffect, useState } from "react"
import { isURL } from "@/utils/isURL"
import Prompt from "@/components/Prompt"
import { openLink } from "@/utils/openLink"
import { isURL } from "@/utils/isURL"
import { useSettings } from "@/context/settings"
import dynamic from "next/dynamic"

const Config = ({ commands, closeCallback }) => {
const [command] = useState(commands.join(" "))
const [consoleLog, setConsoleLog] = useState([])
const [isDone, setDone] = useState(false)
const [isEditMode, setIsEditMode] = useState(false)
const { setSettings, resetSettings } = useSettings()

const CodeEditor = dynamic(() => import("@/components/Editor"), {
ssr: false
})

useEffect(() => {
setConsoleLog([])
Expand All @@ -17,8 +24,8 @@ const Config = ({ commands, closeCallback }) => {
isURL(commands[2])
) {
importConfig(commands[2])
} else if (commands[1] === "export") {
exportConfig()
} else if (commands[1] === "edit") {
editConfig()
} else if (commands[1] === "reset") {
resetConfig()
} else {
Expand All @@ -28,82 +35,109 @@ const Config = ({ commands, closeCallback }) => {
}, [commands])

const importConfig = (url) => {
appendToLog("Fetching settings from remote", "[✓]")
appendToLog("Fetching settings from remote", "success")
fetch(url)
.then((res) => {
if (!res.ok) {
const message = "File not found on URL"
appendToLog(message, "[✖]")
appendToLog("File not found on URL", "error")
throw Error(message)
}

return res.json()
})
.then((data) => {
localStorage.setItem("settings", JSON.stringify(data))
appendToLog("Successfully saved to local storage", "[✓]")
setSettings(data)
appendToLog("Successfully saved to local storage", "success")
})
.catch((err) => {
appendToLog(err, "[✖]")
appendToLog(err, true)
})
.finally(() => {
setDone(true)
})
}

const resetConfig = () => {
appendToLog("Removed local configuration", "[✓]")
localStorage.removeItem("settings")
appendToLog("Reverted back to default configuration", "success")
resetSettings()
setDone(true)
}

const exportConfig = () => {
appendToLog("Exporting local configuration", "[✓]")
const settings = localStorage.getItem("settings")

if (settings) {
const blob = new Blob([settings], { type: "application/json" })
const url = URL.createObjectURL(blob)
openLink(url)
} else {
appendToLog("No local configuration found", "[✖]")
}

setDone(true)
const editConfig = () => {
setIsEditMode(true)
}

const invalidCommand = () => {
appendToLog("Invalid config command: " + commands.join(" "), "[✖]")
appendToLog("Invalid config command: " + commands.join(" "), "error")
appendToLog("Usage:")
appendToLog("config import <url>: Import remote config")
appendToLog("config export: Export local config")
appendToLog("config edit: Edit local config")
appendToLog("config reset: Reset to default config")
}

const appendToLog = (text, symbol) => {
const prefix = symbol ? symbol : ""
setConsoleLog((consoleLog) => [...consoleLog, prefix + text])
const appendToLog = (text, type) => {
setConsoleLog((consoleLog) => [
...consoleLog,
{ type: type, text: text }
])
}

function closeConfigWindow() {
if (isEditMode) return

setIsEditMode(false)
closeCallback()
}

return (
<div
className="h-full overflow-y-auto text-white"
onClick={closeCallback}>
onClick={closeConfigWindow}>
<div className="row">
<ul className="list-none mb-line">
<ul className="list-none">
<li>
<Prompt />
{command}
</li>
</ul>
<ul className="list-none">
{consoleLog.map((data, index) => {
return <li key={index}>{data}</li>
})}
{isDone && (
<li className="mt-line">Press Enter to continue...</li>
)}
</ul>
{isEditMode ? (
<>
<CodeEditor />
</>
) : (
<ul className="list-none mt-line">
{consoleLog.map((data, index) => {
return (
<li key={index}>
{data.type === "error" && (
<p>
<span className="text-red">
[✖]{" "}
</span>
{data.text}
</p>
)}
{data.type === "success" && (
<p>
<span className="text-green">
[✓]{" "}
</span>
{data.text}
</p>
)}
{data.type === undefined && (
<p>{data.text}</p>
)}
</li>
)
})}
{isDone && (
<li className="mt-line">
Press ESC to continue...
</li>
)}
</ul>
)}
</div>
</div>
)
Expand Down
77 changes: 77 additions & 0 deletions src/components/Editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useEffect, useState, useRef } from "react"
import AceEditor from "react-ace"
import "ace-builds/src-noconflict/mode-json"
import "one-theme-ace/one-dark"
import { useSettings } from "@/context/settings"

const Editor = () => {
const editor = useRef(null)
const [hasError, setError] = useState(false)
const { settings, setSettings } = useSettings()

useEffect(() => {
editor.current.editor.session.foldAll(
1,
editor.current.editor.session.doc.getAllLines().length
)
}, [])

function onChange(changes) {
try {
JSON.parse(changes)
setError(false)
} catch (e) {
setError(true)
}
}

const handleSave = () => {
const val = editor.current.editor.getValue()
try {
const data = JSON.parse(val)
setSettings(data)
setError(false)
} catch (e) {
setError(true)
}
}

return (
<>
<div className="relative w-full mt-2">
<AceEditor
defaultValue={JSON.stringify(settings, null, "\t")}
mode="json"
onChange={onChange}
name="json-editor"
theme="one_dark"
showPrintMargin={false}
style={{
width: "100%"
}}
setOptions={{
showLineNumbers: true,
tabSize: 2,
useWorker: false
}}
ref={editor}
/>
</div>
<div className="absolute z-50 align-middle bottom-3 right-3">
<div className="flex flex-row">
{hasError ? (
<p className="py-2 mr-1 text-red">Invalid JSON</p>
) : (
<button
className="p-2 border text-blue border-blue hover:text-white"
onClick={handleSave}>
Save
</button>
)}
</div>
</div>
</>
)
}

export default Editor
19 changes: 10 additions & 9 deletions src/components/Fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import {
browserVersion
} from "react-device-detect"
import moment from "moment"
import Settings from "@/utils/settings"
import Prompt from "@/components/Prompt"
import { useSettings } from "@/context/settings"

const Fetch = ({ closeCallback }) => {
const [info, setInfo] = useState({})
const { settings } = useSettings()

useEffect(() => {
setInfo({
time: moment().format(Settings.fetch.timeFormat),
date: moment().format(Settings.fetch.dateFormat),
time: moment().format(settings.fetch.timeFormat),
date: moment().format(settings.fetch.dateFormat),
osName: osName,
browser: browserName,
browserLower: browserName.toLowerCase(),
Expand Down Expand Up @@ -48,14 +49,14 @@ const Fetch = ({ closeCallback }) => {
<ul className="mt-2">
<li>
<span
className={`text-${Settings.fetch.titleColor}`}>
className={`text-${settings.fetch.titleColor}`}>
Time:
</span>{" "}
{info.time}
</li>
<li>
<span
className={`text-${Settings.fetch.titleColor}`}>
className={`text-${settings.fetch.titleColor}`}>
Date:
</span>{" "}
{info.date}
Expand All @@ -64,28 +65,28 @@ const Fetch = ({ closeCallback }) => {
<ul className="mt-line">
<li>
<span
className={`text-${Settings.fetch.titleColor}`}>
className={`text-${settings.fetch.titleColor}`}>
OS:
</span>{" "}
{info.osName}
</li>
<li>
<span
className={`text-${Settings.fetch.titleColor}`}>
className={`text-${settings.fetch.titleColor}`}>
Browser:
</span>{" "}
{info.browser}
</li>
<li>
<span
className={`text-${Settings.fetch.titleColor}`}>
className={`text-${settings.fetch.titleColor}`}>
Version:
</span>{" "}
{info.browserVersion}
</li>
<li>
<span
className={`text-${Settings.fetch.titleColor}`}>
className={`text-${settings.fetch.titleColor}`}>
Engine:
</span>{" "}
{info.engineName}
Expand Down
Loading

1 comment on commit 81078db

@vercel
Copy link

@vercel vercel bot commented on 81078db Mar 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

start-page – ./

start-page-excalith.vercel.app
start-page-git-main-excalith.vercel.app
excalith-start-page.vercel.app

Please sign in to comment.