sync: wip changes
This commit is contained in:
@@ -17,7 +17,11 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="terminal"></div>
|
||||
<div id="cursor"></div>
|
||||
|
||||
<div id="terminal">
|
||||
<div id="lines"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- run main script -->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CreateKeyboardListeners } from './input/keyboard'
|
||||
import { Wush } from './shell/Wush'
|
||||
import { Terminal } from './terminal/Terminal'
|
||||
import { EventBroadcaster } from './utils/EventBroadcaster'
|
||||
|
||||
@@ -8,11 +9,12 @@ const init = () => {
|
||||
|
||||
// creates keyboard listeners for the local event broadcaster
|
||||
CreateKeyboardListeners(
|
||||
(key: string) => localBroadcaster.emit('keydown', key),
|
||||
(key: string) => localBroadcaster.emit('keyup', key),
|
||||
(key: string, isCharacter: boolean) => localBroadcaster.emit('keydown', key, isCharacter),
|
||||
(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()
|
||||
|
||||
0
src/init.ts
Normal file
0
src/init.ts
Normal file
@@ -1,5 +1,15 @@
|
||||
// creates keyboard listeners
|
||||
export const CreateKeyboardListeners = (onKeyDown: (key: string) => void, onKeyUp: (key: string) => void) => {
|
||||
document.addEventListener('keydown', event => onKeyDown(event.key))
|
||||
document.addEventListener('keyup', event => onKeyUp(event.key))
|
||||
export const CreateKeyboardListeners = (
|
||||
onKeyDown: (key: string, isCharacter: boolean) => void,
|
||||
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;
|
||||
|
||||
@forward "cursor.scss";
|
||||
|
||||
#terminal {
|
||||
background: colors.$terminal-background;
|
||||
color: colors.$terminal-white;
|
||||
@@ -13,9 +15,16 @@
|
||||
p {
|
||||
margin: 0;
|
||||
height: fit-content;
|
||||
word-wrap: break-word;
|
||||
text-wrap: nowrap;
|
||||
|
||||
span {
|
||||
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 type { CursorPosition, CursorStyle } from './CursorProperties'
|
||||
|
||||
export class Terminal {
|
||||
private terminal: HTMLElement
|
||||
private cursor: HTMLElement
|
||||
private cellHeight = 16
|
||||
private cellWidth = 8
|
||||
|
||||
constructor() {
|
||||
private cursorPosition: CursorPosition = {
|
||||
col: 0,
|
||||
row: 0,
|
||||
}
|
||||
|
||||
private shell?: Shell
|
||||
|
||||
constructor(broadcaster: EventBroadcaster) {
|
||||
this.terminal = sqs('#terminal')
|
||||
this.cursor = sqs('#cursor')
|
||||
|
||||
this.SetCursorStyle('bar')
|
||||
|
||||
this.ResetCellSize()
|
||||
|
||||
this.NewPage()
|
||||
this.AppendLine('idk./home/idk -> ')
|
||||
}
|
||||
|
||||
LoadShell(shell: Shell) {
|
||||
this.shell = shell
|
||||
this.shell.Init()
|
||||
}
|
||||
|
||||
NewPage() {
|
||||
this.terminal.innerHTML = ''
|
||||
this.SetCursorPosition(0, 0)
|
||||
}
|
||||
|
||||
AppendLine(line: string) {
|
||||
AppendLine(content: string) {
|
||||
this.AdjustCursorPosition(0, 1)
|
||||
|
||||
const paragraph = document.createElement('p')
|
||||
paragraph.style.height = `${this.cellHeight}px`
|
||||
paragraph.style.lineHeight = `${this.cellHeight}px`
|
||||
|
||||
line.split('').forEach((char) => {
|
||||
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)
|
||||
})
|
||||
paragraph.id = `line-${this.cursorPosition.row}`
|
||||
paragraph.className = 'line'
|
||||
|
||||
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() {
|
||||
// dynamically determine cell size using dom
|
||||
const cell = document.createElement('span')
|
||||
cell.textContent = '\\'
|
||||
cell.textContent = 'A'
|
||||
|
||||
this.terminal.appendChild(cell)
|
||||
|
||||
this.cellWidth = cell.scrollWidth
|
||||
this.cellHeight = cell.scrollHeight
|
||||
|
||||
cell.remove()
|
||||
}
|
||||
|
||||
GetHeightCells(): number {
|
||||
@@ -53,4 +126,39 @@ export class Terminal {
|
||||
GetWidthCells(): number {
|
||||
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