Added Interactive Setup CLI (Unfinished)
Added the first part of the interactive setup CLI. You can explore the interface but some parts will not work yet. It will also not save to the config file yet. The "Quick Setup" & "Manage Plugins" parts of the CLI will arrive later this week.
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
import {opendiscord, api, utilities} from "../../index"
|
||||
import {Terminal, terminal} from "terminal-kit"
|
||||
import ansis from "ansis"
|
||||
|
||||
const logo = [
|
||||
" ██████╗ ██████╗ ███████╗███╗ ██╗ ████████╗██╗ ██████╗██╗ ██╗███████╗████████╗ ",
|
||||
" ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ ",
|
||||
" ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║██║ █████╔╝ █████╗ ██║ ",
|
||||
" ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ ",
|
||||
" ╚██████╔╝██║ ███████╗██║ ╚████║ ██║ ██║╚██████╗██║ ██╗███████╗ ██║ ",
|
||||
" ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ "
|
||||
]
|
||||
|
||||
/**A utility function to center text to a certain width. */
|
||||
function centerText(text:string,width:number){
|
||||
if (width < text.length) return text
|
||||
let newWidth = width-ansis.strip(text).length+1
|
||||
let final = " ".repeat(newWidth/2)+text
|
||||
return final
|
||||
}
|
||||
|
||||
/**A utility function to terminate the interactive CLI. */
|
||||
async function terminate(){
|
||||
terminal.grabInput(false)
|
||||
terminal.clear()
|
||||
terminal.green("👋 Exited the Open Ticket Interactive Setup CLI.\n")
|
||||
process.exit(0)
|
||||
}
|
||||
terminal.on("key",(name,matches,data) => {
|
||||
if (name == "CTRL_C") terminate()
|
||||
})
|
||||
|
||||
/**Render the header of the interactive CLI. */
|
||||
function renderHeader(path:(string|number)[]){
|
||||
terminal.grabInput(true)
|
||||
terminal.clear().moveTo(1,1)
|
||||
terminal(ansis.hex("#f8ba00")(logo.join("\n")+"\n"))
|
||||
terminal.bold(centerText("Interactive Setup CLI - Version: "+opendiscord.versions.get("opendiscord:version").toString()+" - Support: https://discord.dj-dj.be\n",88))
|
||||
if (path.length < 1) terminal.cyan(centerText("👋 Hi! Welcome to the Open Ticket Interactive Setup CLI! 👋\n\n",88))
|
||||
else terminal.cyan(centerText("🌐 Current Location: "+path.map((v,i) => {
|
||||
if (i == 0) return v.toString()
|
||||
else if (typeof v == "string") return ".\""+v+"\""
|
||||
else if (typeof v == "number") return "."+v
|
||||
}).join("")+"\n\n",88))
|
||||
}
|
||||
|
||||
async function renderConfigSelector(backFn:(() => api.ODPromiseVoid)){
|
||||
renderHeader([])
|
||||
terminal(ansis.bold.green("Please select which config you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
const checkerList = opendiscord.checkers.getAll()
|
||||
const checkerNameList = checkerList.map((checker) => (checker.options.cliDisplayName ? checker.options.cliDisplayName+" ("+checker.config.file+")" : checker.config.file))
|
||||
const checkerNameLength = utilities.getLongestLength(checkerNameList)
|
||||
const finalCheckerNameList = checkerNameList.map((name,index) => name.padEnd(checkerNameLength+5," ")+ansis.gray(checkerList[index].options.cliDisplayDescription ? "=> "+checkerList[index].options.cliDisplayDescription : ""))
|
||||
|
||||
const answer = await terminal.singleColumnMenu(finalCheckerNameList,{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (answer.canceled) return await backFn()
|
||||
const checker = checkerList[answer.selectedIndex]
|
||||
const configData = checker.config.data as api.ODValidJsonType
|
||||
await chooseConfigStructure(checker,async () => {await renderConfigSelector(backFn)},checker.structure,configData,{},NaN,["("+checker.config.path+")"])
|
||||
}
|
||||
|
||||
async function chooseConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object|any[],parentIndex:string|number,path:(string|number)[]){
|
||||
if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigEnabledObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectSwitchStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerNullStructure && data === null) await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else if (structure instanceof api.ODCheckerTypeSwitchStructure) await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
else terminal.red.bold("❌ Unable to detect type of variable! Please try to edit this property in the JSON file itself!")
|
||||
}
|
||||
|
||||
async function renderConfigObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (typeof data != "object" || Array.isArray(data)) throw new Error("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("Please select which variable you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
if (!structure.options.children) return await backFn()
|
||||
|
||||
const list = structure.options.children.filter((child) => !child.cliHideInEditMode)
|
||||
const nameList = list.map((child) => (child.checker.options.cliDisplayName ? child.checker.options.cliDisplayName+" ("+child.key+")" : child.key))
|
||||
const nameLength = utilities.getLongestLength(nameList)
|
||||
const finalnameList = nameList.map((name,index) => name.padEnd(nameLength+5," ")+ansis.gray(list[index].checker.options.cliDisplayDescription ? "=> "+list[index].checker.options.cliDisplayDescription : ""))
|
||||
|
||||
|
||||
const answer = await terminal.singleColumnMenu(finalnameList,{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (answer.canceled) return await backFn()
|
||||
const subStructure = list[answer.selectedIndex]
|
||||
const subData = data[subStructure.key]
|
||||
await chooseConfigStructure(checker,async () => {await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)},subStructure.checker,subData,data,subStructure.key,[...path,subStructure.key])
|
||||
}
|
||||
|
||||
async function renderConfigEnabledObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerEnabledObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (typeof data != "object" || Array.isArray(data)) throw new Error("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
const enabledProperty = structure.options.property
|
||||
const subStructure = structure.options.checker
|
||||
if (!enabledProperty || !subStructure || !subStructure.options.children) return await backFn()
|
||||
|
||||
if (!subStructure.options.children.find((child) => child.key === structure.options.property)){
|
||||
if (typeof structure.options.enabledValue == "string") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerStringStructure("opendiscord:CLI-checker-enabled-object-structure",{})})
|
||||
else if (typeof structure.options.enabledValue == "number") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerNumberStructure("opendiscord:CLI-checker-enabled-object-structure",{})})
|
||||
else if (typeof structure.options.enabledValue == "boolean") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-enabled-object-structure",{})})
|
||||
}
|
||||
|
||||
await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path)
|
||||
}
|
||||
|
||||
async function renderConfigObjectSwitchStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectSwitchStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (typeof data != "object" || Array.isArray(data)) throw new Error("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
if (!structure.options.objects) return await backFn()
|
||||
|
||||
let didMatch: boolean = false
|
||||
for (const objectTemplate of structure.options.objects){
|
||||
if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){
|
||||
//object template matches data
|
||||
const subStructure = objectTemplate.checker
|
||||
didMatch = true
|
||||
await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path)
|
||||
}
|
||||
}
|
||||
if (!didMatch) throw new Error("OT CLI => Unable to detect type of object in the object switch. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
}
|
||||
|
||||
async function renderConfigArrayStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerArrayStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (!Array.isArray(data)) throw new Error("OT CLI => Property is not of the type 'array'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("Please select what you would like to do.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
if (!structure.options.propertyChecker) return await backFn()
|
||||
|
||||
const propertyName = structure.options.cliDisplayPropertyName ?? "index"
|
||||
const answer = await terminal.singleColumnMenu([
|
||||
"Add "+propertyName,
|
||||
"Edit "+propertyName,
|
||||
"Move "+propertyName,
|
||||
"Remove "+propertyName,
|
||||
"Duplicate "+propertyName
|
||||
],{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (answer.canceled) return await backFn()
|
||||
if (answer.selectedIndex == 0){
|
||||
//TODO => add => trigger chooseConfigStructure() function but for "addition instead of editing"
|
||||
}else if (answer.selectedIndex == 1) await renderConfigArrayStructureEditSelector(checker,async () => {await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)},structure,structure.options.propertyChecker,data,parent,parentIndex,path)
|
||||
else if (answer.selectedIndex == 2) await renderconfigArrayStructureMoveSelector(checker,async () => {await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)},structure,structure.options.propertyChecker,data,parent,parentIndex,path)
|
||||
else if (answer.selectedIndex == 3) await renderconfigArrayStructureRemoveSelector(checker,async () => {await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)},structure,structure.options.propertyChecker,data,parent,parentIndex,path)
|
||||
else if (answer.selectedIndex == 4){
|
||||
//TODO => duplicate trigger chooseConfigStructure() function but for "addition instead of editing"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function renderConfigArrayStructureEditSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index"
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("Please select the "+propertyName+" you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i)))
|
||||
const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName))
|
||||
const dataAnswer = await terminal.singleColumnMenu(dataList,{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (dataAnswer.canceled) return await backFn()
|
||||
const subData = data[dataAnswer.selectedIndex]
|
||||
await chooseConfigStructure(checker,async () => {await renderConfigArrayStructureEditSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path)},structure,subData,data,dataAnswer.selectedIndex,[...path,dataAnswer.selectedIndex])
|
||||
}
|
||||
|
||||
async function renderconfigArrayStructureMoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index"
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("Please select the "+propertyName+" you would like to move.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i)))
|
||||
const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName))
|
||||
const dataAnswer = await terminal.singleColumnMenu(dataList,{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (dataAnswer.canceled) return await backFn()
|
||||
const subData = data[dataAnswer.selectedIndex]
|
||||
|
||||
renderHeader([...path,dataAnswer.selectedIndex])
|
||||
terminal(ansis.bold.green("Please select the position you would like to move to.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
const moveAnswer = await terminal.singleColumnMenu([...data.map((d,i) => "Position "+(i+1)),"Last Position"],{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (moveAnswer.canceled) return await renderconfigArrayStructureMoveSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path)
|
||||
console.log("move from:",dataAnswer.selectedIndex,"to:",moveAnswer.selectedIndex)
|
||||
}
|
||||
|
||||
async function renderconfigArrayStructureRemoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index"
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("Please select the "+propertyName+" you would like to delete.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i)))
|
||||
const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName))
|
||||
const dataAnswer = await terminal.singleColumnMenu(dataList,{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (dataAnswer.canceled) return await backFn()
|
||||
const subData = data[dataAnswer.selectedIndex]
|
||||
console.log("delete position:",dataAnswer.selectedIndex)
|
||||
}
|
||||
|
||||
async function renderConfigBooleanStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerBooleanStructure,data:boolean,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (typeof data != "boolean") throw new Error("OT CLI => Property is not of the type 'boolean'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the boolean property "+ansis.blue("\""+parentIndex+"\"") : "boolean property "+ansis.blue("nr."+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
terminal.gray("\nCurrent value: "+ansis.bold[data ? "green" : "red"](data.toString())+"\n")
|
||||
|
||||
const answer = await terminal.singleColumnMenu(["false (Disabled)","true (Enabled)"],{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (answer.canceled) return await backFn()
|
||||
|
||||
//run config checker
|
||||
const newValue = (answer.selectedIndex == 0) ? false : true
|
||||
const newPath = [...path]
|
||||
newPath.shift()
|
||||
checker.messages = [] //manually clear previous messages
|
||||
const isDataValid = structure.check(checker,newValue,newPath)
|
||||
|
||||
if (isDataValid){
|
||||
parent[parentIndex] = newValue
|
||||
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
|
||||
await utilities.timer(400)
|
||||
await backFn()
|
||||
}else{
|
||||
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
|
||||
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
|
||||
terminal.gray("\n"+messages)
|
||||
await utilities.timer(1000+(2000*checker.messages.length))
|
||||
await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
}
|
||||
}
|
||||
|
||||
async function renderConfigNumberStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNumberStructure,data:number,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (typeof data != "number") throw new Error("OT CLI => Property is not of the type 'number'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the number property "+ansis.blue("\""+parentIndex+"\"") : "number property "+ansis.blue("nr."+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n"))
|
||||
|
||||
terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n")
|
||||
|
||||
const answer = await terminal.inputField({
|
||||
style:terminal.cyan,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (typeof answer != "string") return await backFn()
|
||||
|
||||
//run config checker
|
||||
const newValue = Number(answer.replaceAll(",","."))
|
||||
const newPath = [...path]
|
||||
newPath.shift()
|
||||
checker.messages = [] //manually clear previous messages
|
||||
const isDataValid = structure.check(checker,newValue,newPath)
|
||||
|
||||
if (isDataValid){
|
||||
parent[parentIndex] = newValue
|
||||
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
|
||||
await utilities.timer(400)
|
||||
await backFn()
|
||||
}else{
|
||||
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
|
||||
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
|
||||
terminal.red("\n"+messages)
|
||||
await utilities.timer(1000+(2000*checker.messages.length))
|
||||
await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
}
|
||||
}
|
||||
|
||||
async function renderConfigStringStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStringStructure,data:string,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (typeof data != "string") throw new Error("OT CLI => Property is not of the type 'string'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the string property "+ansis.blue("\""+parentIndex+"\"") : "string property "+ansis.blue("nr."+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n"))
|
||||
|
||||
terminal.gray("\nCurrent value:"+(data.includes("\n") ? "\n" : " ")+ansis.bold.blue(data)+"\n")
|
||||
|
||||
const autocompleteList = structure.options.cliAutocompleteList ?? structure.options.choices
|
||||
const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = {
|
||||
style:terminal.white,
|
||||
selectedStyle:terminal.bgBlue.white
|
||||
}
|
||||
|
||||
const answer = await terminal.inputField({
|
||||
style:terminal.cyan,
|
||||
hintStyle:terminal.gray,
|
||||
cancelable:true,
|
||||
autoComplete:autocompleteList,
|
||||
autoCompleteHint:(!!autocompleteList),
|
||||
autoCompleteMenu:(autocompleteList) ? autoCompleteMenuOpts as Terminal.Autocompletion : false
|
||||
}).promise
|
||||
|
||||
if (typeof answer != "string") return await backFn()
|
||||
|
||||
//run config checker
|
||||
const newValue = answer.replaceAll("\\n","\n")
|
||||
const newPath = [...path]
|
||||
newPath.shift()
|
||||
checker.messages = [] //manually clear previous messages
|
||||
const isDataValid = structure.check(checker,newValue,newPath)
|
||||
|
||||
if (isDataValid){
|
||||
parent[parentIndex] = newValue
|
||||
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
|
||||
await utilities.timer(400)
|
||||
await backFn()
|
||||
}else{
|
||||
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
|
||||
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
|
||||
terminal.red("\n"+messages)
|
||||
await utilities.timer(1000+(2000*checker.messages.length))
|
||||
await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
}
|
||||
}
|
||||
|
||||
async function renderConfigNullStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNullStructure,data:null,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
if (data !== null) throw new Error("OT CLI => Property is not of the type 'null'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the null property "+ansis.blue("\""+parentIndex+"\"") : "null property "+ansis.blue("nr."+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
terminal.gray("\nCurrent value:"+ansis.bold.blue("null")+"\n")
|
||||
|
||||
const answer = await terminal.singleColumnMenu(["null"],{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (answer.canceled) return await backFn()
|
||||
|
||||
//run config checker
|
||||
const newValue = null
|
||||
const newPath = [...path]
|
||||
newPath.shift()
|
||||
checker.messages = [] //manually clear previous messages
|
||||
const isDataValid = structure.check(checker,newValue,newPath)
|
||||
|
||||
if (isDataValid){
|
||||
parent[parentIndex] = newValue
|
||||
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
|
||||
await utilities.timer(400)
|
||||
await backFn()
|
||||
}else{
|
||||
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
|
||||
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
|
||||
terminal.red("\n"+messages)
|
||||
await utilities.timer(1000+(2000*checker.messages.length))
|
||||
await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
|
||||
}
|
||||
}
|
||||
|
||||
async function renderConfigTypeSwitchStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,data:any,parent:object,parentIndex:string|number,path:(string|number)[]){
|
||||
renderHeader(path)
|
||||
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("nr."+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
|
||||
|
||||
terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n")
|
||||
|
||||
const actionsList: string[] = []
|
||||
if (structure.options.boolean) actionsList.push("Edit as boolean")
|
||||
if (structure.options.string) actionsList.push("Edit as string")
|
||||
if (structure.options.number) actionsList.push("Edit as number")
|
||||
if (structure.options.object) actionsList.push("Edit as object")
|
||||
if (structure.options.array) actionsList.push("Edit as array/list")
|
||||
if (structure.options.null) actionsList.push("Edit as null")
|
||||
|
||||
const answer = await terminal.singleColumnMenu(actionsList,{
|
||||
leftPadding:"> ",
|
||||
style:terminal.cyan,
|
||||
selectedStyle:terminal.bgDefaultColor.bold,
|
||||
submittedStyle:terminal.bgBlue,
|
||||
extraLines:2,
|
||||
cancelable:true
|
||||
}).promise
|
||||
|
||||
if (answer.canceled) return await backFn()
|
||||
|
||||
//run selected structure editor (untested)
|
||||
if (answer.selectedText.startsWith("Edit as boolean") && structure.options.boolean) await renderConfigBooleanStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.boolean,false,parent,parentIndex,path)
|
||||
else if (answer.selectedText.startsWith("Edit as string") && structure.options.string) await renderConfigStringStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.string,data.toString(),parent,parentIndex,path)
|
||||
else if (answer.selectedText.startsWith("Edit as number") && structure.options.number) await renderConfigNumberStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.number,0,parent,parentIndex,path)
|
||||
else if (answer.selectedText.startsWith("Edit as object") && structure.options.object) await renderConfigObjectStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.object,data,parent,parentIndex,path)
|
||||
else if (answer.selectedText.startsWith("Edit as array/list") && structure.options.array) await renderConfigArrayStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.array,data,parent,parentIndex,path)
|
||||
else if (answer.selectedText.startsWith("Edit as null") && structure.options.null) await renderConfigNullStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.null,null,parent,parentIndex,path)
|
||||
}
|
||||
|
||||
function getArrayPreviewStructureNameLength(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number): number {
|
||||
if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString().length
|
||||
else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString().length
|
||||
else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data.length
|
||||
else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null".length
|
||||
else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array".length
|
||||
else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
|
||||
if (!structure.options.cliDisplayKeyInParentArray) return "Object".length
|
||||
else return data[structure.options.cliDisplayKeyInParentArray].toString().length
|
||||
|
||||
}else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
|
||||
const subStructure = structure.options.checker
|
||||
if (!subStructure) return "<unknown-property>".length
|
||||
return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex)
|
||||
|
||||
}else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){
|
||||
for (const objectTemplate of (structure.options.objects ?? [])){
|
||||
if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){
|
||||
//object template matches data
|
||||
const subStructure = objectTemplate.checker
|
||||
return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex)
|
||||
}
|
||||
}
|
||||
return "<unknown-property>".length
|
||||
|
||||
}else if (structure instanceof api.ODCheckerTypeSwitchStructure){
|
||||
if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewStructureNameLength(structure.options.boolean,data,parent,parentIndex)
|
||||
else if (typeof data == "number" && structure.options.number) return getArrayPreviewStructureNameLength(structure.options.number,data,parent,parentIndex)
|
||||
else if (typeof data == "string" && structure.options.string) return getArrayPreviewStructureNameLength(structure.options.string,data,parent,parentIndex)
|
||||
else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewStructureNameLength(structure.options.object,data,parent,parentIndex)
|
||||
else if (Array.isArray(data) && structure.options.array) return getArrayPreviewStructureNameLength(structure.options.array,data,parent,parentIndex)
|
||||
else if (data === null && structure.options.null) return getArrayPreviewStructureNameLength(structure.options.null,data,parent,parentIndex)
|
||||
else return "<unknown-property>".length
|
||||
}else return "<unknown-property>".length
|
||||
}
|
||||
|
||||
function getArrayPreviewFromStructure(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number,nameLength:number): string {
|
||||
if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString()
|
||||
else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString()
|
||||
else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data
|
||||
else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null"
|
||||
else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array"
|
||||
else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
|
||||
const additionalKeys = (structure.options.cliDisplayAdditionalKeysInParentArray ?? []).map((key) => key+": "+data[key].toString()).join(", ")
|
||||
if (!structure.options.cliDisplayKeyInParentArray) return "Object"
|
||||
else return data[structure.options.cliDisplayKeyInParentArray].toString().padEnd(nameLength+5," ")+ansis.gray(additionalKeys.length > 0 ? "("+additionalKeys+")" : "")
|
||||
|
||||
}else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
|
||||
const subStructure = structure.options.checker
|
||||
if (!subStructure) return "<unknown-property>"
|
||||
return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength)
|
||||
|
||||
}else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){
|
||||
for (const objectTemplate of (structure.options.objects ?? [])){
|
||||
if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){
|
||||
//object template matches data
|
||||
const subStructure = objectTemplate.checker
|
||||
return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength)
|
||||
}
|
||||
}
|
||||
return "<unknown-property>"
|
||||
|
||||
}else if (structure instanceof api.ODCheckerTypeSwitchStructure){
|
||||
if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewFromStructure(structure.options.boolean,data,parent,parentIndex,nameLength)
|
||||
else if (typeof data == "number" && structure.options.number) return getArrayPreviewFromStructure(structure.options.number,data,parent,parentIndex,nameLength)
|
||||
else if (typeof data == "string" && structure.options.string) return getArrayPreviewFromStructure(structure.options.string,data,parent,parentIndex,nameLength)
|
||||
else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewFromStructure(structure.options.object,data,parent,parentIndex,nameLength)
|
||||
else if (Array.isArray(data) && structure.options.array) return getArrayPreviewFromStructure(structure.options.array,data,parent,parentIndex,nameLength)
|
||||
else if (data === null && structure.options.null) return getArrayPreviewFromStructure(structure.options.null,data,parent,parentIndex,nameLength)
|
||||
else return "<unknown-property>"
|
||||
}else return "<unknown-property>"
|
||||
}
|
||||
|
||||
export async function execute(){
|
||||
if (terminal.width < 100 || terminal.height < 35){
|
||||
terminal(ansis.red.bold("\n\nMake sure your console or cmd window has a "+ansis.cyan("minimum width & height")+" of "+ansis.cyan("100x35")+" characters."))
|
||||
terminal(ansis.red.bold("\nOtherwise the Open Ticket Interactive Setup CLI will be rendered incorrectly."))
|
||||
terminal(ansis.red.bold("\nThe current terminal dimensions are: "+ansis.cyan(terminal.width+"x"+terminal.height)+"."))
|
||||
}else await renderConfigSelector(terminate)
|
||||
}
|
||||
Reference in New Issue
Block a user