Skip to content

Commit

Permalink
😼 Command Line Is Operational
Browse files Browse the repository at this point in the history
  • Loading branch information
catpea committed Jan 7, 2025
1 parent 844e5ae commit c105544
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 15 deletions.
49 changes: 44 additions & 5 deletions components/console/Console.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,25 @@ export default class Console extends HTMLElement {
const application = transcend(this, `x-application`);
if(!application) throw new Error('Unable to locate applicaion!')

this.gc = application.project.commander.on('alert', o => {
this.publishAlert(o);
});

const debounceDelay = 300;
const executedCommandTimeouts = new Map();
this.gc = application.project.commander.on('executed', o => {
if (executedCommandTimeouts.has(o.commandName)) {
clearTimeout(executedCommandTimeouts.get(o.commandName));

let commandId = o.commandName;
if(o.commandArguments && o.commandArguments.id) commandId = o.commandName + o.commandArguments.id
console.info(commandId)
if (executedCommandTimeouts.has(commandId)) {
clearTimeout(executedCommandTimeouts.get(commandId));
}
const timeoutId = setTimeout(() => {
this.publishExecutedCommand(o);
executedCommandTimeouts.delete(o.commandName);
executedCommandTimeouts.delete(commandId);
}, debounceDelay);
executedCommandTimeouts.set(o.commandName, timeoutId);
executedCommandTimeouts.set(commandId, timeoutId);
});

this.status.value = 'ready';
Expand All @@ -66,7 +73,39 @@ export default class Console extends HTMLElement {



publishAlert(alert){
const application = transcend(this, `x-application`);
if(!application) throw new Error('Unable to locate applicaion!')
const selfDestruct = alert.ttl*1000 || 30_000;
const alertContainer = lol.div({ class: `alert alert-${alert.type||'primary'} mb-3` });
alertContainer.appendChild( lol.button({ class: `btn btn-link btn-sm position-absolute top-0 end-0`, on: {click: ()=>alertContainer.remove()} }, lol.i({class:'bi bi-x-lg'})))

if(alert.title) alertContainer.appendChild(lol.h4({},alert.title));
if(alert.text) alertContainer.appendChild(lol.p({},alert.text));
if(alert.note){
alertContainer.appendChild(lol.hr());
alertContainer.appendChild(lol.small({},alert.note));
}

const progressBar = lol.div({ class: 'progress-bar opacity-25', style: {width: '100%'} },)
let progressBarWidth = 100;
const progressBarWidthIntervalId = setInterval(() => {
progressBarWidth = progressBarWidth - 1;
progressBar.style.width = progressBarWidth + '%';
if (progressBarWidth < 0){
clearInterval(progressBarWidthIntervalId);
alertContainer.remove()
}
}, selfDestruct / 100);
alertContainer.addEventListener("mouseover", function (e) {
clearInterval(progressBarWidthIntervalId);
progressBar.style.display = 'none';
});
alertContainer.appendChild(lol.div({ class: 'progress position-absolute top-100 start-50 translate-middle', style: { marginTop: '-6px', height:'1px', width: '98%', background:'transparent'} }, progressBar) )

const consoleContainer = this.shadowRoot.querySelector('.console');
consoleContainer.insertBefore(alertContainer, consoleContainer.firstChild);
}

publishExecutedCommand(executedCommandEvent){
const application = transcend(this, `x-application`);
Expand Down Expand Up @@ -99,7 +138,7 @@ export default class Console extends HTMLElement {
commandForm.appendChild( lol.small({ class:'opacity-25 mb-3 d-block'}, executedCommandEvent.timestamp))
commandForm.appendChild(forms.createHidden({name: 'commandName', value:executedCommandEvent.commandName}))

for (const [name, value] of Object.entries(executedCommandEvent.commandArguments[0])) {
for (const [name, value] of Object.entries(executedCommandEvent.commandArguments)) {
commandForm.appendChild(forms.createCompactInput({name, value}))
}

Expand Down
57 changes: 49 additions & 8 deletions components/prompt/Prompt.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Signal from 'signal';
import commando from 'commando';
import lol from 'lol';
import transcend from 'transcend';

export default class Prompt extends HTMLElement {

Expand All @@ -18,12 +20,18 @@ export default class Prompt extends HTMLElement {
right: 1rem;
padding: .1rem;
.prompt-send {
i.prompt-send {
position: absolute;
right: 1rem;
top: .69rem;
}
button.prompt-send {
position: absolute;
right: 1rem;
top: .32rem;
}
.prompt-control {
border-radius: 32px;
border: none;
Expand All @@ -49,18 +57,51 @@ export default class Prompt extends HTMLElement {
const shadow = this.attachShadow({ mode: 'open' });
shadow.adoptedStyleSheets = [...document.adoptedStyleSheets, localCss];

const container = lol.div({ class: 'prompt' });
container.innerHTML = `
<input type="text" class="prompt-control" placeholder="">
<i class="prompt-send bi bi-send-fill"></i>
`;
this.appendChild(container);
shadow.appendChild(container);


}

connectedCallback() {


const application = transcend(this, `x-application`);
if(!application) throw new Error('Unable to locate applicaion!')


let commandForm;

const executeForm = ()=>{
const formData = new FormData(commandForm);
const commandLine = formData.get('commandLine');
for( const { commandName, commandArguments } of commando(commandLine)){
application.project.commander[commandName](commandArguments);
}
}
const commandProcessor = e => {
e.preventDefault();
executeForm()
};

const commandLine = lol.input({type:'text', class:'prompt-control', name:'commandLine', value:'windowMove -id windowPostMessage -left 100 -top 50 --note "Bork Bork"; windowMove -id uppercaseOutput -left 500 -top 700'});
const submitIcon = lol.i({class:'prompt-send bi bi-send-fill', on:{click:()=>executeForm()} })
commandForm = lol.form({on:{submit:commandProcessor}}, commandLine, submitIcon);
const container = lol.div({ class: 'prompt', }, commandForm);

this.shadowRoot.appendChild(container);

setTimeout(()=>{
const commandLine = `alert --type info --ttl 60 --title "Hello!" --text "Command line is working now. Just hit enter (when in there) to execute, or click send icon." --note "Click yellow edit-icons in window captions to move between scenes. You can move windows too." `;
for( const { commandName, commandArguments } of commando(commandLine)){
console.log(commandName, commandArguments);
application.project.commander[commandName](commandArguments);
}
}, 1_000)

this.status.value = 'ready';




}

}
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"scene": "./components/scene/Scene.js",

"lol": "./library/lol/lol.js",
"commando": "./library/commando/index.js",
"transcend": "./library/transcend/transcend.js",
"forms": "./library/forms/Forms.js",
"dataset": "./library/dataset/Dataset.js",
Expand Down
8 changes: 6 additions & 2 deletions library/commander/Commander.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class CommandLoader {
class CommandExecutor {
static async execute(model, CommandClass, args) {
const command = new CommandClass(model);
return await command.execute(...args);
return await command.execute(args);
}
}

Expand Down Expand Up @@ -154,10 +154,14 @@ export default class Commander extends CommandEmitter {
}

// Return command execution handler
return async (...args) => {
return async (args) => {
return await CommandHandler.handle(this, model, commandName, args);
};
}
});
}

alert(o){
this.emit('alert', o)
}
}
113 changes: 113 additions & 0 deletions library/commando/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Token types
const TOKEN_TYPE = {
COMMAND: "COMMAND",
FLAG: "FLAG",
PIPE: "PIPE",
SEMICOLON: "SEMICOLON",
};

// Tokenize the input string
function tokenize(input) {
const tokens = [];
// Updated regex to handle semicolons attached to other tokens
const regex = /(--\w+|-.\w*|"[^"]*"|;|\S+)/g;
const words = input.match(regex);

for (let i = 0; i < words.length; i++) {
let word = words[i];

if (word.startsWith("--") || word.startsWith("-")) {
// Check if the next word is a value for this flag
if (
i + 1 < words.length &&
!words[i + 1].startsWith("--") &&
!words[i + 1].startsWith("-") &&
words[i + 1] !== "|" &&
words[i + 1] !== ";"
) {
let flagValue = words[++i].replace(/"/g, "");
// If the flag value ends with a semicolon, separate it
if (flagValue.endsWith(";")) {
flagValue = flagValue.slice(0, -1);
tokens.push({
type: TOKEN_TYPE.FLAG,
value: word,
flagValue: flagValue,
});
tokens.push({ type: TOKEN_TYPE.SEMICOLON, value: ";" });
} else {
tokens.push({
type: TOKEN_TYPE.FLAG,
value: word,
flagValue: flagValue,
});
}
} else {
tokens.push({ type: TOKEN_TYPE.FLAG, value: word, flagValue: null });
}
} else if (word === "|") {
tokens.push({ type: TOKEN_TYPE.PIPE, value: word });
} else if (word === ";") {
tokens.push({ type: TOKEN_TYPE.SEMICOLON, value: word });
} else {
tokens.push({ type: TOKEN_TYPE.COMMAND, value: word.replace(/"/g, "") });
}
}

return tokens;
}

// Parse the tokens into a syntax tree
function parse(tokens) {
const commands = [];
let currentCommand = null;

for (let token of tokens) {
switch (token.type) {
case TOKEN_TYPE.COMMAND:
currentCommand = {
commandName: token.value,
commandArguments: {},
terminators: [],
};
commands.push(currentCommand);
break;
case TOKEN_TYPE.FLAG:
if (currentCommand) {
const flagName = token.value.replace(/^-+/,'');
currentCommand.commandArguments[flagName] = token.flagValue;
// currentCommand.flags.push({ flag: token.value, value: token.flagValue });
} else {
throw new Error("Flag without command");
}
break;
case TOKEN_TYPE.PIPE:
if (currentCommand) {
currentCommand.terminators.push(token.value);
} else {
throw new Error("Pipe without command");
}
currentCommand = null; // Reset current command for the next one
break;
case TOKEN_TYPE.SEMICOLON:
if (currentCommand) {
currentCommand.terminators.push(token.value);
} else {
throw new Error("Semicolon without command");
}
currentCommand = null; // Reset current command for the next one
break;
default:
throw new Error("Unknown token type");
}
}

return commands;
}

export default function main(input) {
const tokens = tokenize(input);
const syntaxTree = parse(tokens);

return syntaxTree;
}

0 comments on commit c105544

Please sign in to comment.