Compare commits

...

7 Commits

9 changed files with 722 additions and 202 deletions

View File

@@ -2,3 +2,4 @@ singleQuote: true
semi: false semi: false
printWidth: 100 printWidth: 100
trailingComma: none trailingComma: none
endOfLine: "auto"

568
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,10 +20,14 @@
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.0", "@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^3.0.0", "@electron-toolkit/utils": "^3.0.0",
"@node-steam/vdf": "^2.2.0",
"find-steam-app": "^1.0.2",
"get-installed-apps": "^1.1.0", "get-installed-apps": "^1.1.0",
"get-startapps": "^1.0.4", "get-startapps": "^1.0.4",
"glob": "^10.4.1",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"serialport": "^12.0.0" "serialport": "^12.0.0",
"vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.1", "@electron-toolkit/eslint-config": "^1.0.1",

42
src/main/Steam.js Normal file
View File

@@ -0,0 +1,42 @@
import * as VDF from '@node-steam/vdf'
import fs from 'fs'
import { glob, globSync } from 'glob'
import { findSteam } from 'find-steam-app'
// const VDF = require('@node-steam/vdf')
// const fs = require('fs')
// const glob = require('glob')
export async function getLibraryFolders(steamPath) {
if (!steamPath) steamPath = await findSteam()
if (!fs.existsSync(steamPath)) throw new Error(`Steam path not found: ${steamPath}`)
if (!fs.existsSync(`${steamPath}/steamapps/libraryfolders.vdf`))
throw new Error(`Steam config.vdf not found: ${steamPath}/steanapps/libraryfolders.vdf`)
const libraryFolders = VDF.parse(
fs.readFileSync(`${steamPath}/steamapps/libraryfolders.vdf`, 'utf8')
)
console.log(typeof libraryFolders)
return Object.values(libraryFolders.libraryfolders)
}
export function getGames(libraryFolder) {
if (!libraryFolder) return null
if (!fs.existsSync(libraryFolder.path)) throw new Error(`Steam path not found: ${libraryFolder}`)
const appManifests = glob.globSync(`${libraryFolder.path}/steamapps/*.acf`)
const gamesCollection = []
appManifests.forEach((appManifest) => {
const appManifestData = VDF.parse(fs.readFileSync(appManifest, 'utf8'))
gamesCollection.push({
baseDirectory: libraryFolder.path.replaceAll('\\\\', '\\'),
appid: appManifestData.AppState.appid,
name: appManifestData.AppState.name,
installdir: appManifestData.AppState.installdir.replaceAll('\\\\', '\\')
})
})
return gamesCollection
}

View File

@@ -5,9 +5,11 @@ import icon from '../../resources/icon.png?asset'
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
// import {getInstalledApps} from 'get-installed-apps' // import {getInstalledApps} from 'get-installed-apps'
import getapps from 'get-startapps' import getapps from 'get-startapps'
import * as steam from './Steam.js'
import { SerialPort } from 'serialport' import { SerialPort } from 'serialport'
import * as logTimestamp from 'log-timestamp' import * as logTimestamp from 'log-timestamp'
import { globSync } from 'glob'
async function loadConfigurationYAML() { async function loadConfigurationYAML() {
try { try {
@@ -25,13 +27,32 @@ async function getSerialPorts() {
} }
async function getApps() { async function getApps() {
// return await getInstalledApps() // let appCollection = await getInstalledApps()
let appCollection = await getapps() let appCollection = await getapps()
let filters = [(a) => a.appid.includes('.exe'), (a) => a.appid.includes('steam')] let filters = [(a) => a.appid.includes('.exe'), (a) => a.appid.includes('steam')]
console.log(` Total apps ${appCollection.length}`) console.log(` Total apps ${appCollection.length}`)
return appCollection.filter((app) => filters.some((fn) => fn(app))) return appCollection.filter((app) => filters.some((fn) => fn(app)))
} }
async function getSteamApps() {
let steamGames = []
let steamLibraries = await steam.getLibraryFolders()
steamLibraries.forEach((libraryFolder) => {
let games = steam.getGames(libraryFolder)
games.forEach((game) => {
const dir = `${game.baseDirectory}\\steamapps\\common\\${game.installdir}\\`
const exeFiles = globSync(`${dir}/**/*.exe`)
exeFiles.forEach((file) => {
steamGames.push({
name: game.name,
app: file.split('\\').pop()
})
})
})
})
return steamGames
}
function createWindow() { function createWindow() {
// Create the browser window. // Create the browser window.
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
@@ -64,10 +85,6 @@ function createWindow() {
} }
} }
function developmentConsole() {
// SerialPort.list().then((data) => console.log(data))
}
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
@@ -87,10 +104,9 @@ app.whenReady().then(() => {
ipcMain.handle('loadConfigurationYML', loadConfigurationYAML) ipcMain.handle('loadConfigurationYML', loadConfigurationYAML)
ipcMain.handle('getSerialPorts', getSerialPorts) ipcMain.handle('getSerialPorts', getSerialPorts)
ipcMain.handle('getInstalledApps', getApps) ipcMain.handle('getInstalledApps', getApps)
ipcMain.handle('getSteamApps', getSteamApps)
developmentConsole()
createWindow() createWindow()
app.on('activate', function () { app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the // On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.

View File

@@ -1,14 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import COMselect from './components/COMselect.vue' import COMselect from './components/COMselect.vue'
import { onMounted } from 'vue' import ProgramSelector from './components/ProgramSelector.vue'
import { getTransitionRawChildren, onMounted } from 'vue'
import { ref } from 'vue' import { ref } from 'vue'
import * as logTimestamp from 'log-timestamp' import * as logTimestamp from 'log-timestamp'
import draggable from 'vuedraggable'
import ProgramGroup from './components/ProgramGroup.vue'
// import MovieCard from './components/MovieCard.vue'
const movies = ref([]) // const movies = ref([])
const apiKey = 'a0eb411ca9c81896004dce1d27a7245b' // const apiKey = 'a0eb411ca9c81896004dce1d27a7245b'
const configuration = ref('Loading....') const configuration = ref('Loading....')
const serialPorts = ref('Loading Serial ports.... ') const serialPorts = ref('Loading Serial ports.... ')
const insatlledApps = ref({}) const installedApps = ref([])
const steamApps = ref([])
const steamPath = ref('')
//model variables for user provided input
const programCounter = ref([
'python.exe',
'nvm.exe',
'narrator.exe',
['TimeCamp.exe', 'charmap.exe'],
'nvm.exe'
])
const serialPortSelection = ref('select the COM port... ')
const ipcHandle = () => window.electron.ipcRenderer.send('ping') const ipcHandle = () => window.electron.ipcRenderer.send('ping')
// const getConfigFileContents = async () => { // const getConfigFileContents = async () => {
@@ -21,38 +37,109 @@ const getSerialPorts = async () => {
console.log(`the returned serial ports are ${serialPorts.value}`) console.log(`the returned serial ports are ${serialPorts.value}`)
} }
const getTrendingMovies = (category) => { // const getTrendingMovies = (category) => {
return fetch(`https://api.themoviedb.org/3/trending/movie/${category}?api_key=${apiKey}`) // return fetch(`https://api.themoviedb.org/3/trending/movie/${category}?api_key=${apiKey}`)
.then((response) => response.json()) // .then((response) => response.json())
.then((data) => { // .then((data) => {
movies.value = data.results // movies.value = data.results
}) // })
} // }
async function getInstalledApps() { async function getInstalledApps() {
insatlledApps.value = await window.electron.ipcRenderer.invoke('getInstalledApps') installedApps.value = await window.electron.ipcRenderer.invoke('getInstalledApps')
// console.log(installedApps.value)
installedApps.value.forEach((app) => {
app.appid = app.appid.split('\\').pop()
})
getSteamApps()
} }
async function getSteamApps() {
steamApps.value = await window.electron.ipcRenderer.invoke('getSteamApps')
steamApps.value.forEach((game) => {
console.log(`the game is ${Object.entries(game)}`)
})
}
function deleteProgramRow(index) {
programCounter.value.splice(index, 1)
// Vue.delete(programCounter.value, index)
console.log(`Got event to delete row for index : ${index}`)
}
function deleteAppFromCollection(index, childIndex) {
console.log(`The Indexi is ${index} and the child index is ${childIndex}`)
programCounter.value[index].splice(childIndex, 1)
}
function addProgram() {
programCounter.value.push('')
}
function addAppToCollection(index, childIndex) {
programCounter.value[index].push('')
}
function updateCollectionApp(index, childIndex, appName) {
programCounter.value[index][childIndex] = appName
}
onMounted(() => { onMounted(() => {
getTrendingMovies('day') // getTrendingMovies('day')
getSerialPorts() getSerialPorts()
insatlledApps.value = getInstalledApps() getInstalledApps()
}) })
</script> </script>
<template> <template>
<!-- <img alt="logo" class="logo" src="./assets/electron.svg" /> <img alt="logo" class="logo" src="./assets/electron.svg" />
<div class="creator">Powered by electron-vite</div>
<COMselect
<Versions /> --> v-model="serialPortSelection"
:serialports="serialPorts"
<COMselect :serialports="serialPorts" :installedApps="insatlledApps" /> :installed-apps="installedApps"
<div class="row"> />
<!-- <div class="row mt-2">
<button type="button" class="col-auto" @click="ipcHandle">Send Ping</button> <button type="button" class="col-auto" @click="ipcHandle">Send Ping</button>
<button type="button" class="col-auto" @click="getSerialPorts">Load config</button> <button type="button" class="col-auto" @click="getSerialPorts">Load config</button>
</div> </div>
<div>{{ configuration }}</div> <div>{{ configuration }}</div> -->
<draggable v-model="programCounter">
<template #item="{ element, index }">
<div v-if="typeof element === 'string'">
<ProgramSelector
:installed-apps="installedApps"
:steam-apps="steamApps"
:selected-app="element"
@update-app="programCounter[index] = $event"
@delete-row="deleteProgramRow(index)"
/>
</div>
<div v-else class="border-top border-bottom py-2 mt-2">
<ProgramGroup
:installed-apps="installedApps"
:program-counter="element"
:steam-apps="steamApps"
@delete-row="deleteProgramRow(index)"
@update-app="(appName, childIndex) => updateCollectionApp(index, childIndex, appName)"
@add-app="(childIndex) => addAppToCollection(index, childIndex)"
@delete-app="(childIndex) => deleteAppFromCollection(index, childIndex)"
/>
</div>
</template>
</draggable>
<div class="row mt-2">
<button class="col-auto" @click="addProgram">Add</button>
<!-- <button class="col-auto" click="removeProgram">Remove</button>/ -->
</div>
<div class="row">
<div class="col-auto">COM Port</div>
<div class="col-auto">{{ serialPortSelection }}</div>
</div>
<div>{{ programCounter }}</div>
<div>{{ steamPath }}</div>
<!-- <!--
<div class="container"> <div class="container">
<div class="text-center"> <div class="text-center">
@@ -67,8 +154,10 @@ onMounted(() => {
</div> </div>
<div v-if="movies.length > 0" class="row"> <div v-if="movies.length > 0" class="row">
<div v-for="(movie, i) in movies.slice(0, 2)" :key="i" class="col-md-4"> <draggable v-model="movies">
<MovieCard :movie="movie" /> <template #item="{ element }">
</div> <MovieCard :movie="element" />
</template>
</draggable>
</div> --> </div> -->
</template> </template>

View File

@@ -1,46 +1,29 @@
<template> <template>
<p>Select COM port for device: {{ com_port }}</p>
<div class="row"> <div class="row">
<div class="col-auto"> <div class="col-auto">
<label class="form-label">Select COM Port for your deej device</label> <label class="form-label">Select COM Port for your deej device</label>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<select v-model="com_port" class="form-select"> <select v-model="com_port" class="form-select">
<option v-for="(serialport, i) in props.serialports" :key="i" :value="serialport.path" class="form-option"> <option
v-for="(serialport, i) in props.serialports"
:key="i"
:value="serialport.path"
class="form-option"
>
{{ serialport.friendlyName }} {{ serialport.friendlyName }}
</option> </option>
</select> </select>
</div> </div>
</div> </div>
<div id="programs"></div>
<div>
<button class="form-button" @click="addNewProgram">Add program</button>
</div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
const props = defineProps(['serialports', 'installedApps']) const props = defineProps(['serialports', 'installedApps'])
const com_port = ref('')
function addNewProgram() { console.log (`Inside COMSelect the length of installedApps is ${props.installedApps.length}`)
const programsContent = document.getElementById('programs') const com_port = defineModel()
const programNode = document.createElement('div')
// programNode.innerHTML = `<div class='row'><select class='form-select' class='col-auto'> <option> ${optioniterator()} </option> </select></div>`
programNode.innerHTML = generateAppSelector()
programsContent.appendChild(programNode.firstChild)
}
function generateAppSelector() {
let optionsString = ''
for (let app in props.installedApps) {
optionsString += `<option value=${props.installedApps[app].name}> ${props.installedApps[app].name} </option>`
}
return `<div class="row mt-3">
<select class="form-select"> ${optionsString} </select></div>
`
}
</script> </script>

View File

@@ -0,0 +1,56 @@
<script setup>
import ProgramSelector from './ProgramSelector.vue'
import draggable from 'vuedraggable'
import { ref } from 'vue'
const props = defineProps({
installedApps: {
type: Array,
required: true
},
programCounter: {
type: Array,
required: false
},
steamApps: {
type: Array,
required: false
}
})
const emit = defineEmits(['update-app', 'delete-row', 'add-app', 'delete-app'])
// function deleteProgramRow(index) {
// programCounter.value.splice(index, 1)
// // Vue.delete(programCounter.value, index)
// console.log(`Got event to delete row for index : ${index}`)
// }
// function addProgramRow() {
// programCounter.value.push('')
// }
</script>
<template>
<div>{{ programCounter }}</div>
<div class="row">
<div class="col">Select programs</div>
<div class="col text-end">
<i class="fas fa-plus p-2" @click="$emit('add-app', index)"></i>
<i class="fas fa-trash-alt" @click="$emit('delete-row')"></i>
</div>
</div>
<draggable :list="programCounter">
<template #item="{ element, index }">
<div>
<ProgramSelector
:installed-apps="installedApps"
:selected-app="element"
:steam-apps="steamApps"
@delete-row="$emit('delete-app', index)"
@update-app="$emit('update-app', $event, index)"
/>
</div>
</template>
</draggable>
</template>

View File

@@ -0,0 +1,47 @@
<script setup>
defineEmits(['delete-row', 'update-app'])
const props = defineProps({ installedApps: Object, selectedApp: String, steamApps: Object })
console.log(props.installedApps)
</script>
<template>
<div class="row mt-2 d-flex align-items-center">
<div class="col-auto"><i class="fas fa-bars"></i></div>
<div class="col-auto">Program</div>
<div class="col-auto">
<select class="form-select" @change="$emit('update-app', $event.target.value)">
<optgroup label="Deej defaults">
<option value="master">Master</option>
<option value="deej.unmapped">deej.unmapped</option>
<option value="mic">Mic</option>
<option value="system">System</option>
</optgroup>
<optgroup label="Steam Games">
<option
v-for="(app, i) in props.steamApps"
:key="i"
class="col-auto mt-3"
:value="app.app"
:selected="app.app == selectedApp"
>
<b>{{ app.name }}:</b> {{ app.app }}
</option>
</optgroup>
<optgroup label="Installed apps">
<option
v-for="(app, i) in props.installedApps"
:key="i"
class="col-auto mt-3"
:value="app.appid"
:selected="app.appid == selectedApp"
>
{{ app.name }}
</option>
</optgroup>
</select>
</div>
<div class="col-auto"><i class="fas fa-trash-alt" @click="$emit('delete-row')"></i></div>
</div>
<!-- <div>{{ selectedApp }}</div> -->
</template>