Implemeeeeeent

This commit is contained in:
2021-11-10 00:38:22 +01:00
parent a5dbe91bc1
commit 1bc7c6ab27
14 changed files with 301 additions and 406 deletions

View File

@@ -0,0 +1,120 @@
import { ipcMain } from 'electron';
import { blackout } from '../patterns/blackout';
import { Pattern, PatternOutput, Time } from '../patterns/proto';
import { TestPattern } from '../patterns/test';
import rust, { BeatTrackerHandle, MovingHeadState, OutputHandle } from 'rust_native_module';
type AppState = {
patterns: { [key: string]: PatternOutput },
selectedPattern: string | null,
};
class Backend {
beatTracker: BeatTrackerHandle;
dmxOutput: OutputHandle;
patterns: Map<string, Pattern>;
patternOutputs: Map<string, PatternOutput>;
selectedPattern: string;
constructor() {
// beat tracking
let beatTracker = rust.getBeatTracker();
if (beatTracker.type !== 'success') {
throw new Error("could not initialize beat tracking");
}
this.beatTracker = beatTracker.value;
ipcMain.on('beat-tracking', async (_, arg) => {
if (arg === 'tap') {
this.beatTracker.tap();
}
});
// output
let dmxOutput = rust.openOutput("/dev/ttyUSB0");
if (dmxOutput.type !== 'success') {
throw new Error("could not open DMX output");
}
this.dmxOutput = dmxOutput.value;
// patterns
this.patterns = new Map();
this.patterns.set("test", new TestPattern());
this.patternOutputs = new Map();
this.selectedPattern = "test";
let time: Time = {
absolute: 0,
beat_relative: null
}
for (let [patternId, pattern] of this.patterns.entries()) {
this.patternOutputs.set(patternId, pattern.render(time));
}
}
getState(): AppState {
let result: AppState = {
patterns: {},
selectedPattern: this.selectedPattern,
}
for (let [patternId, patternOutput] of this.patternOutputs) {
result.patterns[patternId] = patternOutput;
}
return result;
}
update() {
let date = new Date();
let time: Time = {
absolute: date.getTime() / 1000,
beat_relative: null,
}
// render all patterns
if (!this.patterns) {
console.log("big oof?");
throw new Error("???");
}
for (let [patternId, pattern] of this.patterns.entries()) {
this.patternOutputs.set(patternId, pattern.render(time));
}
// write selected pattern
let output: Array<MovingHeadState> = blackout;
if (this.selectedPattern !== null) {
let selectedPatternOutput = this.patternOutputs.get(this.selectedPattern)
if (selectedPatternOutput) {
output = selectedPatternOutput;
}
}
this.dmxOutput.set(output);
}
close() {
this.dmxOutput.close();
}
}
export default Backend;

View File

@@ -11,28 +11,20 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import path from 'path';
import { app, BrowserWindow, shell, ipcMain } from 'electron';
import { app, BrowserWindow, ipcMain, shell } from 'electron';
import { resolveHtmlPath } from './util';
import Backend from './backend';
import rust from 'rust_native_module';
let backend = new Backend();
let mainLoop = setInterval(() => { backend.update(); }, 20);
ipcMain.handle('poll', () => {
return backend.getState();
});
let mainWindow: BrowserWindow | null = null;
let beat_tracker = rust.getBeatTracker();
// TODO @thamma why does this not infer that beat_tracker has tap?
if (beat_tracker.type === 'success') {
console.log('Beat Tracker started.');
ipcMain.on('beat-tracking', async (_, arg) => {
if (arg === 'tap') {
// see here
(beat_tracker as any).tap();
}
});
}
const createWindow = async () => {
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'assets')
@@ -99,5 +91,9 @@ app
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow();
});
app.on('quit', () => {
clearInterval(mainLoop);
backend.close();
})
})
.catch(console.log);

View File

@@ -1,290 +0,0 @@
import {
app,
Menu,
shell,
BrowserWindow,
MenuItemConstructorOptions,
} from 'electron';
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
selector?: string;
submenu?: DarwinMenuItemConstructorOptions[] | Menu;
}
export default class MenuBuilder {
mainWindow: BrowserWindow;
constructor(mainWindow: BrowserWindow) {
this.mainWindow = mainWindow;
}
buildMenu(): Menu {
if (
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
) {
this.setupDevelopmentEnvironment();
}
const template =
process.platform === 'darwin'
? this.buildDarwinTemplate()
: this.buildDefaultTemplate();
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
return menu;
}
setupDevelopmentEnvironment(): void {
this.mainWindow.webContents.on('context-menu', (_, props) => {
const { x, y } = props;
Menu.buildFromTemplate([
{
label: 'Inspect element',
click: () => {
this.mainWindow.webContents.inspectElement(x, y);
},
},
]).popup({ window: this.mainWindow });
});
}
buildDarwinTemplate(): MenuItemConstructorOptions[] {
const subMenuAbout: DarwinMenuItemConstructorOptions = {
label: 'Electron',
submenu: [
{
label: 'About ElectronReact',
selector: 'orderFrontStandardAboutPanel:',
},
{ type: 'separator' },
{ label: 'Services', submenu: [] },
{ type: 'separator' },
{
label: 'Hide ElectronReact',
accelerator: 'Command+H',
selector: 'hide:',
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:',
},
{ label: 'Show All', selector: 'unhideAllApplications:' },
{ type: 'separator' },
{
label: 'Quit',
accelerator: 'Command+Q',
click: () => {
app.quit();
},
},
],
};
const subMenuEdit: DarwinMenuItemConstructorOptions = {
label: 'Edit',
submenu: [
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
{ label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
{ type: 'separator' },
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:',
},
],
};
const subMenuViewDev: MenuItemConstructorOptions = {
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'Command+R',
click: () => {
this.mainWindow.webContents.reload();
},
},
{
label: 'Toggle Full Screen',
accelerator: 'Ctrl+Command+F',
click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
},
},
{
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
click: () => {
this.mainWindow.webContents.toggleDevTools();
},
},
],
};
const subMenuViewProd: MenuItemConstructorOptions = {
label: 'View',
submenu: [
{
label: 'Toggle Full Screen',
accelerator: 'Ctrl+Command+F',
click: () => {
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
},
},
],
};
const subMenuWindow: DarwinMenuItemConstructorOptions = {
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:',
},
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
{ type: 'separator' },
{ label: 'Bring All to Front', selector: 'arrangeInFront:' },
],
};
const subMenuHelp: MenuItemConstructorOptions = {
label: 'Help',
submenu: [
{
label: 'Learn More',
click() {
shell.openExternal('https://electronjs.org');
},
},
{
label: 'Documentation',
click() {
shell.openExternal(
'https://github.com/electron/electron/tree/main/docs#readme'
);
},
},
{
label: 'Community Discussions',
click() {
shell.openExternal('https://www.electronjs.org/community');
},
},
{
label: 'Search Issues',
click() {
shell.openExternal('https://github.com/electron/electron/issues');
},
},
],
};
const subMenuView =
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
? subMenuViewDev
: subMenuViewProd;
return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
}
buildDefaultTemplate() {
const templateDefault = [
{
label: '&File',
submenu: [
{
label: '&Open',
accelerator: 'Ctrl+O',
},
{
label: '&Close',
accelerator: 'Ctrl+W',
click: () => {
this.mainWindow.close();
},
},
],
},
{
label: '&View',
submenu:
process.env.NODE_ENV === 'development' ||
process.env.DEBUG_PROD === 'true'
? [
{
label: '&Reload',
accelerator: 'Ctrl+R',
click: () => {
this.mainWindow.webContents.reload();
},
},
{
label: 'Toggle &Full Screen',
accelerator: 'F11',
click: () => {
this.mainWindow.setFullScreen(
!this.mainWindow.isFullScreen()
);
},
},
{
label: 'Toggle &Developer Tools',
accelerator: 'Alt+Ctrl+I',
click: () => {
this.mainWindow.webContents.toggleDevTools();
},
},
]
: [
{
label: 'Toggle &Full Screen',
accelerator: 'F11',
click: () => {
this.mainWindow.setFullScreen(
!this.mainWindow.isFullScreen()
);
},
},
],
},
{
label: 'Help',
submenu: [
{
label: 'Learn More',
click() {
shell.openExternal('https://electronjs.org');
},
},
{
label: 'Documentation',
click() {
shell.openExternal(
'https://github.com/electron/electron/tree/main/docs#readme'
);
},
},
{
label: 'Community Discussions',
click() {
shell.openExternal('https://www.electronjs.org/community');
},
},
{
label: 'Search Issues',
click() {
shell.openExternal('https://github.com/electron/electron/issues');
},
},
],
},
];
return templateDefault;
}
}

View File

@@ -1,21 +1,5 @@
const { contextBridge, ipcRenderer } = require('electron');
const rust = require('rust_native_module');
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
send(channel, data) {
const validChannels = ['beat-tracking'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
on(channel, func) {
const validChannels = ['tick', 'beat-tracking'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
},
rustding: rust
ipcRenderer: ipcRenderer,
});