sync: wip changes
This commit is contained in:
@@ -17,7 +17,11 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div id="terminal"></div>
|
<div id="cursor"></div>
|
||||||
|
|
||||||
|
<div id="terminal">
|
||||||
|
<div id="lines"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- run main script -->
|
<!-- run main script -->
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CreateKeyboardListeners } from './input/keyboard'
|
import { CreateKeyboardListeners } from './input/keyboard'
|
||||||
|
import { Wush } from './shell/Wush'
|
||||||
import { Terminal } from './terminal/Terminal'
|
import { Terminal } from './terminal/Terminal'
|
||||||
import { EventBroadcaster } from './utils/EventBroadcaster'
|
import { EventBroadcaster } from './utils/EventBroadcaster'
|
||||||
|
|
||||||
@@ -8,11 +9,12 @@ const init = () => {
|
|||||||
|
|
||||||
// creates keyboard listeners for the local event broadcaster
|
// creates keyboard listeners for the local event broadcaster
|
||||||
CreateKeyboardListeners(
|
CreateKeyboardListeners(
|
||||||
(key: string) => localBroadcaster.emit('keydown', key),
|
(key: string, isCharacter: boolean) => localBroadcaster.emit('keydown', key, isCharacter),
|
||||||
(key: string) => localBroadcaster.emit('keyup', key),
|
(key: string, isCharacter: boolean) => localBroadcaster.emit('keyup', key, isCharacter),
|
||||||
)
|
)
|
||||||
|
|
||||||
const terminal = new Terminal()
|
const terminal = new Terminal(localBroadcaster)
|
||||||
|
terminal.LoadShell(new Wush(localBroadcaster, terminal))
|
||||||
}
|
}
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|||||||
0
src/init.ts
Normal file
0
src/init.ts
Normal file
@@ -1,5 +1,15 @@
|
|||||||
// creates keyboard listeners
|
// creates keyboard listeners
|
||||||
export const CreateKeyboardListeners = (onKeyDown: (key: string) => void, onKeyUp: (key: string) => void) => {
|
export const CreateKeyboardListeners = (
|
||||||
document.addEventListener('keydown', event => onKeyDown(event.key))
|
onKeyDown: (key: string, isCharacter: boolean) => void,
|
||||||
document.addEventListener('keyup', event => onKeyUp(event.key))
|
onKeyUp: (key: string, isCharacter: boolean) => void,
|
||||||
|
) => {
|
||||||
|
document.addEventListener('keydown', event => {
|
||||||
|
onKeyDown(event.key, event.key.length === 1)
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('keyup', event => {
|
||||||
|
onKeyUp(event.key, event.key.length === 1)
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/shell/Shell.ts
Normal file
15
src/shell/Shell.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Terminal } from '../terminal/Terminal'
|
||||||
|
import type { EventBroadcaster } from '../utils/EventBroadcaster'
|
||||||
|
|
||||||
|
export abstract class Shell {
|
||||||
|
broadcaster: EventBroadcaster
|
||||||
|
terminal: Terminal
|
||||||
|
|
||||||
|
constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
|
||||||
|
this.broadcaster = broadcaster
|
||||||
|
this.terminal = terminal
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Init(): void
|
||||||
|
abstract HandleKeyInput(key: string, isCharacter: boolean): void
|
||||||
|
}
|
||||||
42
src/shell/Wush.ts
Normal file
42
src/shell/Wush.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Web-Uno Shell
|
||||||
|
// the best name I could come up with lmao
|
||||||
|
|
||||||
|
import { Terminal } from '../terminal/Terminal'
|
||||||
|
import type { EventBroadcaster } from '../utils/EventBroadcaster'
|
||||||
|
import { Shell } from './Shell'
|
||||||
|
|
||||||
|
export class Wush extends Shell {
|
||||||
|
constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
|
||||||
|
super(broadcaster, terminal)
|
||||||
|
}
|
||||||
|
|
||||||
|
Init() {
|
||||||
|
this.broadcaster.on('keydown', (key: string, isCharacter: boolean) =>
|
||||||
|
this.HandleKeyInput(key, isCharacter),
|
||||||
|
)
|
||||||
|
this.Prompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
Prompt() {
|
||||||
|
this.terminal.Write('hi -> ')
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleKeyInput(key: string, isCharacter: boolean) {
|
||||||
|
console.log(key)
|
||||||
|
console.log(isCharacter)
|
||||||
|
|
||||||
|
if (!isCharacter)
|
||||||
|
switch (key) {
|
||||||
|
case 'Backspace':
|
||||||
|
this.terminal.RemoveCells(1)
|
||||||
|
break
|
||||||
|
case 'Enter':
|
||||||
|
this.Prompt()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.terminal.Write(key)
|
||||||
|
this.terminal.AdjustCursorPosition(1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/styles/cursor.scss
Normal file
10
src/styles/cursor.scss
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@use "colors.scss" as colors;
|
||||||
|
|
||||||
|
#cursor {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
background: colors.$terminal-white;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
@use "colors.scss" as colors;
|
@use "colors.scss" as colors;
|
||||||
|
|
||||||
|
@forward "cursor.scss";
|
||||||
|
|
||||||
#terminal {
|
#terminal {
|
||||||
background: colors.$terminal-background;
|
background: colors.$terminal-background;
|
||||||
color: colors.$terminal-white;
|
color: colors.$terminal-white;
|
||||||
@@ -13,9 +15,16 @@
|
|||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
|
word-wrap: break-word;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
background: red;
|
||||||
|
width: fit-content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/terminal/CursorProperties.ts
Normal file
6
src/terminal/CursorProperties.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type CursorPosition = {
|
||||||
|
col: number,
|
||||||
|
row: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CursorStyle = 'bar' | 'underline' | 'cell' | 'cell_hollow'
|
||||||
@@ -1,49 +1,122 @@
|
|||||||
|
import type { Shell } from '../shell/Shell'
|
||||||
|
import type { EventBroadcaster } from '../utils/EventBroadcaster'
|
||||||
import sqs from '../utils/sqs'
|
import sqs from '../utils/sqs'
|
||||||
|
import type { CursorPosition, CursorStyle } from './CursorProperties'
|
||||||
|
|
||||||
export class Terminal {
|
export class Terminal {
|
||||||
private terminal: HTMLElement
|
private terminal: HTMLElement
|
||||||
|
private cursor: HTMLElement
|
||||||
private cellHeight = 16
|
private cellHeight = 16
|
||||||
private cellWidth = 8
|
private cellWidth = 8
|
||||||
|
|
||||||
constructor() {
|
private cursorPosition: CursorPosition = {
|
||||||
|
col: 0,
|
||||||
|
row: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
private shell?: Shell
|
||||||
|
|
||||||
|
constructor(broadcaster: EventBroadcaster) {
|
||||||
this.terminal = sqs('#terminal')
|
this.terminal = sqs('#terminal')
|
||||||
|
this.cursor = sqs('#cursor')
|
||||||
|
|
||||||
|
this.SetCursorStyle('bar')
|
||||||
|
|
||||||
this.ResetCellSize()
|
this.ResetCellSize()
|
||||||
|
|
||||||
this.NewPage()
|
this.NewPage()
|
||||||
this.AppendLine('idk./home/idk -> ')
|
}
|
||||||
|
|
||||||
|
LoadShell(shell: Shell) {
|
||||||
|
this.shell = shell
|
||||||
|
this.shell.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
NewPage() {
|
NewPage() {
|
||||||
this.terminal.innerHTML = ''
|
this.terminal.innerHTML = ''
|
||||||
|
this.SetCursorPosition(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendLine(line: string) {
|
AppendLine(content: string) {
|
||||||
|
this.AdjustCursorPosition(0, 1)
|
||||||
|
|
||||||
const paragraph = document.createElement('p')
|
const paragraph = document.createElement('p')
|
||||||
paragraph.style.height = `${this.cellHeight}px`
|
paragraph.style.height = `${this.cellHeight}px`
|
||||||
paragraph.style.lineHeight = `${this.cellHeight}px`
|
paragraph.style.lineHeight = `${this.cellHeight}px`
|
||||||
|
paragraph.id = `line-${this.cursorPosition.row}`
|
||||||
line.split('').forEach((char) => {
|
paragraph.className = 'line'
|
||||||
const span = document.createElement('span')
|
|
||||||
span.textContent = char.replace(' ', '\u00A0')
|
|
||||||
span.style.width = `${this.cellWidth}px`
|
|
||||||
span.style.height = `${this.cellHeight}px`
|
|
||||||
span.style.lineHeight = `${this.cellHeight}px`
|
|
||||||
paragraph.appendChild(span)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.terminal.appendChild(paragraph)
|
this.terminal.appendChild(paragraph)
|
||||||
|
this.AppendCells(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write(text: string) {
|
||||||
|
let adjustment = 0
|
||||||
|
|
||||||
|
text.split('').forEach(char => {
|
||||||
|
console.log(this.cursorPosition)
|
||||||
|
|
||||||
|
this.AdjustCursorPosition(adjustment, 0)
|
||||||
|
adjustment = 1
|
||||||
|
|
||||||
|
this.SetCell(char)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCell(char: string) {
|
||||||
|
const id = `#cell-${this.cursorPosition.row}_${this.cursorPosition.col}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
sqs(id)
|
||||||
|
} catch (_) {
|
||||||
|
const cell = document.createElement('span')
|
||||||
|
cell.id = id
|
||||||
|
cell.style.width = `${this.cellWidth}px`
|
||||||
|
cell.style.height = `${this.cellHeight}px`
|
||||||
|
cell.style.lineHeight = `${this.cellHeight}px`
|
||||||
|
|
||||||
|
const lineId = `#line-${this.cursorPosition.row}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
sqs(lineId)
|
||||||
|
} catch (_) {
|
||||||
|
// todo: create as many lines as we need
|
||||||
|
for (let i = sqs('#terminal'); ;) {}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
sqs(id).innerText = char[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendCells(content: string) {
|
||||||
|
const paragraph = sqs(`#line-${this.cursorPosition.row}`)
|
||||||
|
|
||||||
|
content.split('').forEach(char => {
|
||||||
|
paragraph.textContent += char.replace(' ', '\u00A0')
|
||||||
|
this.AdjustCursorPosition(1, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveCells(amount: number) {
|
||||||
|
const paragraph = sqs(`#line-${this.cursorPosition.row}`)
|
||||||
|
paragraph.innerText = paragraph.innerText.slice(
|
||||||
|
0,
|
||||||
|
Math.max(paragraph.innerText.length - amount, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
this.AdjustCursorPosition(-amount, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
ResetCellSize() {
|
ResetCellSize() {
|
||||||
// dynamically determine cell size using dom
|
// dynamically determine cell size using dom
|
||||||
const cell = document.createElement('span')
|
const cell = document.createElement('span')
|
||||||
cell.textContent = '\\'
|
cell.textContent = 'A'
|
||||||
|
|
||||||
this.terminal.appendChild(cell)
|
this.terminal.appendChild(cell)
|
||||||
|
|
||||||
this.cellWidth = cell.scrollWidth
|
this.cellWidth = cell.scrollWidth
|
||||||
this.cellHeight = cell.scrollHeight
|
this.cellHeight = cell.scrollHeight
|
||||||
|
|
||||||
|
cell.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
GetHeightCells(): number {
|
GetHeightCells(): number {
|
||||||
@@ -53,4 +126,39 @@ export class Terminal {
|
|||||||
GetWidthCells(): number {
|
GetWidthCells(): number {
|
||||||
return this.terminal.clientWidth / this.cellWidth
|
return this.terminal.clientWidth / this.cellWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateCursor() {
|
||||||
|
this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth}px`
|
||||||
|
this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
GetCursorPosition(): CursorPosition {
|
||||||
|
return this.cursorPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCursorPosition(col: number, row: number) {
|
||||||
|
this.cursorPosition.col = Math.min(
|
||||||
|
this.GetWidthCells(),
|
||||||
|
Math.max(col, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
this.cursorPosition.row = Math.min(
|
||||||
|
this.GetHeightCells(),
|
||||||
|
Math.max(row, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
this.UpdateCursor()
|
||||||
|
}
|
||||||
|
|
||||||
|
AdjustCursorPosition(col: number, row: number) {
|
||||||
|
this.SetCursorPosition(this.cursorPosition.col + col, this.cursorPosition.row + row)
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCursorStyle(style: CursorStyle) {
|
||||||
|
switch (style) {
|
||||||
|
default:
|
||||||
|
this.cursor.style.width = '1px'
|
||||||
|
this.cursor.style.height = `${this.cellHeight}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user