diff options
-rw-r--r-- | index.js | 49 | ||||
-rw-r--r-- | package-lock.json | 5 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | record-store.js | 35 | ||||
m--------- | tui-lib | 0 | ||||
-rw-r--r-- | ui.js | 113 |
6 files changed, 203 insertions, 0 deletions
diff --git a/index.js b/index.js index f85d101..a86918a 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,12 @@ const { getPlayer } = require('./players') const { getDownloaderFor } = require('./downloaders') +const { AppElement } = require('./ui') +const ansi = require('./tui-lib/util/ansi') +const CommandLineInterfacer = require('./tui-lib/util/CommandLineInterfacer') const EventEmitter = require('events') +const Flushable = require('./tui-lib/util/Flushable') +const Root = require('./tui-lib/ui/Root') class InternalApp extends EventEmitter { constructor() { @@ -53,9 +58,53 @@ async function main() { internalApp.stopPlaying() */ + /* for (const item of require('./flat.json').items) { await internalApp.download(item.downloaderArg) } + */ + + const interfacer = new CommandLineInterfacer() + const size = await interfacer.getScreenSize() + + const flushable = new Flushable(process.stdout, true) + flushable.screenLines = size.lines + flushable.screenCols = size.cols + flushable.shouldShowCompressionStatistics = process.argv.includes('--show-ansi-stats') + flushable.write(ansi.clearScreen()) + flushable.flush() + + const root = new Root(interfacer) + root.w = size.width + root.h = size.height + + const appElement = new AppElement() + root.addChild(appElement) + root.select(appElement) + + appElement.on('quitRequested', () => { + process.stdout.write(ansi.cleanCursor()) + process.exit(0) + }) + + const grouplike = { + items: [ + {name: 'Nice'}, + {name: 'W00T!'}, + {name: 'All-star'} + ] + } + + appElement.recordStore.getRecord(grouplike.items[2]).downloading = true + + appElement.grouplikeListingElement.loadGrouplike(grouplike) + + root.select(appElement.grouplikeListingElement) + + setInterval(() => { + root.renderTo(flushable) + flushable.flush() + }, 50) } main().catch(err => console.error(err)) diff --git a/package-lock.json b/package-lock.json index f1205d9..e9fd90f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, + "iac": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/iac/-/iac-1.1.0.tgz", + "integrity": "sha1-C83Rc3Jy/qwj5126pFeCnYHTtMw=" + }, "js-base64": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz", diff --git a/package.json b/package.json index ec51a8b..b26650a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "command-exists": "^1.2.6", "fifo-js": "^2.1.0", "fs-extra": "^6.0.1", + "iac": "^1.1.0", "js-base64": "^2.4.5", "mkdirp": "^0.5.1", "node-fetch": "^2.1.2", diff --git a/record-store.js b/record-store.js new file mode 100644 index 0000000..d0c7623 --- /dev/null +++ b/record-store.js @@ -0,0 +1,35 @@ +const recordSymbolKey = Symbol() + +module.exports = class RecordStore { + constructor() { + // Each track (or whatever) gets a symbol which is used as a key here to + // store more information. + this.data = {} + } + + getRecord(obj) { + if (typeof obj !== 'object') { + throw new TypeError('Cannot get the record of a non-object') + } + + if (!obj[recordSymbolKey]) { + obj[recordSymbolKey] = Symbol() + } + + if (!this.data[obj[recordSymbolKey]]) { + this.data[obj[recordSymbolKey]] = {} + } + + return this.data[obj[recordSymbolKey]] + } + + deleteRecord(obj) { + if (typeof obj !== 'object') { + throw new TypeError('Non-objects cannot have a record in the first place') + } + + if (obj[recordSymbolKey]) { + delete this.data[obj[recordSymbolKey]] + } + } +} diff --git a/tui-lib b/tui-lib -Subproject 6ee1936266dda3bd22e4412a7b51cdc6e3c396d +Subproject 581c8db27bc25c74b02a1b29d795c847118c623 diff --git a/ui.js b/ui.js new file mode 100644 index 0000000..26aa12e --- /dev/null +++ b/ui.js @@ -0,0 +1,113 @@ +const ansi = require('./tui-lib/util/ansi') +const Button = require('./tui-lib/ui/form/Button') +const FocusElement = require('./tui-lib/ui/form/FocusElement') +const ListScrollForm = require('./tui-lib/ui/form/ListScrollForm') +const Pane = require('./tui-lib/ui/Pane') +const RecordStore = require('./record-store') + +class AppElement extends FocusElement { + constructor(internalApp) { + super() + + this.internalApp = internalApp + this.recordStore = new RecordStore() + + this.pane = new Pane() + this.addChild(this.pane) + + this.grouplikeListingElement = new GrouplikeListingElement(this.recordStore) + this.pane.addChild(this.grouplikeListingElement) + } + + fixLayout() { + this.w = this.parent.contentW + this.h = this.parent.contentH + + this.pane.w = this.contentW + this.pane.h = this.contentH + + this.grouplikeListingElement.w = this.pane.contentW + this.grouplikeListingElement.h = this.pane.contentH + } + + keyPressed(keyBuf) { + if (keyBuf[0] === 0x03) { // ^C + this.emit('quitRequested') + return + } + + super.keyPressed(keyBuf) + } +} + +class GrouplikeListingElement extends ListScrollForm { + constructor(recordStore) { + super('vertical') + + this.grouplike = null + this.recordStore = recordStore + } + + loadGrouplike(grouplike) { + this.grouplike = grouplike + this.buildItems() + } + + buildItems() { + if (!this.grouplike) { + throw new Error('Attempted to call buildItems before a grouplike was loaded') + } + + for (const item of this.grouplike.items) { + this.addInput(new GrouplikeItemElement(item, this.recordStore)) + } + + this.fixLayout() + } +} + +class GrouplikeItemElement extends Button { + constructor(item, recordStore) { + super() + + this.item = item + this.recordStore = recordStore + } + + fixLayout() { + this.w = this.parent.contentW + this.h = 1 + } + + drawTo(writable) { + if (this.isFocused) { + writable.write(ansi.invert()) + } + + writable.write(ansi.moveCursor(this.absTop, this.absLeft)) + this.drawX = this.x + this.writeStatus(writable) + this.drawX += this.item.name.length + writable.write(this.item.name) + writable.write(' '.repeat(this.w - this.drawX)) + + writable.write(ansi.resetAttributes()) + } + + writeStatus(writable) { + this.drawX += 3 + + const braille = '⠈⠐⠠⠄⠂⠁' + const brailleChar = braille[Math.floor(Date.now() / 250) % 6] + + writable.write(' ') + if (this.recordStore.getRecord(this.item).downloading) { + writable.write(braille[Math.floor(Date.now() / 250) % 6]) + } else { + writable.write(' ') + } + writable.write(' ') + } +} + +module.exports.AppElement = AppElement |