Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This is the base version of the charts, it is incomplete and still requires fine tuning #33

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
971 changes: 697 additions & 274 deletions RocketControlUnitGUI/package-lock.json

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions RocketControlUnitGUI/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
{
"name": "Rocket-Control-Unit",
"name": "rocket-control-unit-gui",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@skeletonlabs/skeleton": "2.5.1",
"@skeletonlabs/tw-plugin": "0.2.4",
"@sveltejs/adapter-node": "^4.0.1",
Expand All @@ -39,7 +37,23 @@
},
"type": "module",
"dependencies": {
"@dimforge/rapier3d-compat": "^0.14.0",
"@floating-ui/dom": "1.5.3",
"pocketbase": "^0.20.3"
"@theatre/core": "^0.7.2",
"@theatre/studio": "^0.7.2",
"@threlte/core": "^7.3.1",
"@threlte/extras": "^8.12.0",
"@threlte/flex": "^1.0.3",
"@threlte/rapier": "^2.0.1",
"@threlte/theatre": "^2.1.8",
"@threlte/xr": "^0.1.4",
"@types/three": "^0.170.0",
"chart.js": "^4.4.6",
"leaflet": "^1.9.4",
"pocketbase": "^0.20.3",
"three": "^0.169.0",
"threlte": "^3.13.1",
"troika-three-text": "^0.52.0",
"troika-three-utils": "^0.52.0"
}
}
32 changes: 32 additions & 0 deletions RocketControlUnitGUI/src/DataService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// src/data/DataService.ts
import PocketBase from 'pocketbase';



export class DataService {
private pocketBaseInstance = new PocketBase('http://127.0.0.1:8090');
private isAuthenticated = false;


async authenticate(email: string, password: string) {
if (!this.isAuthenticated) {
await this.pocketBaseInstance.admins.authWithPassword(email, password);
this.isAuthenticated = true;

}
}

async fetchPaginatedData(collectionName: string, page: number, batchSize: number) {
return await this.pocketBaseInstance.collection(collectionName).getList(page, batchSize);
}

subscribeToCollection(collectionName: string, callback: (data: any) => void) {
this.pocketBaseInstance.collection(collectionName).subscribe('*', (e) => callback(e.record));
console.log(`Subscribed to real-time updates for collection: ${collectionName}`);
}

unsubscribeFromCollection(collectionName: string) {
this.pocketBaseInstance.collection(collectionName).unsubscribe('*');
console.log(`Unsubscribed from real-time updates for collection: ${collectionName}`);
}
}
90 changes: 90 additions & 0 deletions RocketControlUnitGUI/src/StoreService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// StoreService.ts
import PocketBase from 'pocketbase';

const pb = new PocketBase('http://127.0.0.1:8090');
let isAuthenticated = false;
pb.autoCancellation(false);

// Define the RecordData type explicitly for PocketBase records
export type RecordData = { [key: string]: any };

// Function to authenticate PocketBase before any data fetch or subscription
async function authenticate() {
if (!isAuthenticated) {
const email = import.meta.env.VITE_EMAIL;
const password = import.meta.env.VITE_PASSWORD;

try {
await pb.admins.authWithPassword(email, password);
isAuthenticated = true;
console.log('Authenticated successfully');
} catch (error) {
console.error('Authentication failed:', error);
throw new Error('Authentication failed');
}
}
}

/**
* Fetch paginated data from the specified collection in PocketBase.
* @param collectionName The name of the collection to fetch data from.
* @param batchSize The number of records to fetch per page.
* @param onPageFetched Callback function to process fetched records.
*/
export async function getCollectionData(
collectionName: string,
batchSize: number,
onPageFetched: (data: RecordData[]) => void
): Promise<void> {
await authenticate();

try {
const response = await pb.collection(collectionName).getList(1, batchSize, {
sort: '-created', // Fetch the most recent records first
});

if (response.items.length > 0) {
onPageFetched(response.items); // Send data to the callback
} else {
console.warn(`No data found for collection: ${collectionName}`);
}
} catch (error) {
console.error(`Error fetching data for collection ${collectionName}:`, error);
}
}

/**
* Subscribe to a collection in PocketBase for real-time updates.
* @param collectionName The name of the collection to subscribe to.
* @param callback Callback function to process new real-time updates.
*/
export async function subscribeToCollection(collectionName: string, callback: (data: RecordData) => void) {
await authenticate();

try {
pb.collection(collectionName).subscribe('*', (e) => {
if (e.record) {
callback(e.record);
} else {
console.warn(`Received empty record in collection ${collectionName} real-time update.`);
}
});
console.log(`Subscribed to real-time updates for collection: ${collectionName}`);
} catch (error) {
console.error(`Error subscribing to collection ${collectionName}:`, error);
}
}

/**
* Unsubscribe from a collection in PocketBase to stop real-time updates.
* @param collectionName The name of the collection to unsubscribe from.
*/
export function unsubscribeFromCollection(collectionName: string) {
try {
pb.collection(collectionName).unsubscribe('*');
console.log(`Unsubscribed from real-time updates for collection: ${collectionName}`);
} catch (error) {
console.error(`Error unsubscribing from collection ${collectionName}:`, error);
}
}

2 changes: 1 addition & 1 deletion RocketControlUnitGUI/src/app.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
Expand Down
107 changes: 107 additions & 0 deletions RocketControlUnitGUI/src/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// data.ts
import PocketBase from 'pocketbase';


const ADMIN_EMAIL: string = import.meta.env.VITE_EMAIL;
const ADMIN_PASSWORD: string = import.meta.env.VITE_PASSWORD;


const pb = new PocketBase('http://127.0.0.1:8090');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to change back to the PI IP

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, will do!

pb.autoCancellation(false);

let isAuthenticated = false;



// Function to authenticate the admin user
export async function authenticate() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authentication as well as subscribing is handled in usePocketbase hook (which may need some additions) in master so refactor to use that. Also, when using the hook we should make sure to pass around a global instance so that we are not opening duplicate pb connections like in this file.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to refactor my code to incorporate the usePocketbase hook now will I will be implementing soon!

if (!isAuthenticated) {
await pb.admins.authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
isAuthenticated = true;
}
}

type RecordData = { [key: string]: any };
export type AllData = { [collectionName: string]: RecordData[] };

// Fetch paginated data with existing logic
export async function fetchPaginatedData(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be extracted into its own hook that depends on Return<typeof usePocketbase>. Perhaps make a chart hook that encapsulates chart related logic to avoid global state for the chart system (this is not a big deal though, it would just be cleaner).

collectionName: string,
sendToChart: (data: RecordData[]) => void,
batchSize: number = 10
) {
console.log(`Fetching data from collection: ${collectionName}`);
let page = 1;
let hasMoreData = true;

try {
await authenticate(); // Authenticate once before fetching data
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we only need to authenticate once, not every time we want to read/write to/from pocketbase.


while (hasMoreData) {
console.log('Fetching data from collection:', collectionName, 'Page:', page);
const records = await pb.collection(collectionName).getList(page, batchSize);
console.log('Fetched records:', records.items);

if (records.items.length === 0) {
console.warn(`No records found for ${collectionName} on page ${page}.`);
break;
}

const dynamicKeys = Object.keys(records.items[0]).filter(key => key !== 'id' && key !== 'created');

const transformedBatch: RecordData[] = records.items.map((record: RecordData) => {
const transformedRecord: RecordData = {};
dynamicKeys.forEach(key => {
transformedRecord[key] = record[key];
});
return transformedRecord;
});

console.log('Transformed batch:', transformedBatch);
sendToChart(transformedBatch);

if (records.items.length < batchSize) {
hasMoreData = false;
} else {
page += 1;
}
}
console.log('All data fetched for collection:', collectionName);
} catch (error) {
console.error('Error fetching paginated data for collection:', collectionName, error);
}
}

// Real-time subscription to a collection
export async function subscribeToCollection(
collectionName: string,
handleDataUpdate: (data: RecordData) => void
) {
try {
await authenticate(); // Ensure we're authenticated before subscribing

// Subscribe to the collection for any changes
pb.collection(collectionName).subscribe('*', function (e) {
console.log(`Received real-time update for collection ${collectionName}:`, e.record);
handleDataUpdate(e.record);
});

console.log(`Subscribed to real-time updates for collection: ${collectionName}`);
} catch (error) {
console.error(`Error subscribing to collection ${collectionName}:`, error);
}
}

// Function to unsubscribe from a collection (optional)
export async function unsubscribeFromCollection(collectionName: string) {
try {
await authenticate(); // Ensure we're authenticated before unsubscribing

// Unsubscribe from the collection
pb.collection(collectionName).unsubscribe('*');

console.log(`Unsubscribed from real-time updates for collection: ${collectionName}`);
} catch (error) {
console.error(`Error unsubscribing from collection ${collectionName}:`, error);
}
}
2,576 changes: 2,576 additions & 0 deletions RocketControlUnitGUI/src/lib/components/Diagram.svelte

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions RocketControlUnitGUI/src/lib/components/PausablePromptModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script lang="ts" context="module">
export type PausablePromptResponse = ["submit" | "cancel" | "pause" | undefined, string];
</script>

<script lang="ts">
import { getModalStore } from '@skeletonlabs/skeleton';

const modalStore = getModalStore();

export let heading: string = "";
let inputValue: string = "";

const finish = (value: PausablePromptResponse) => {
$modalStore[0]?.response!(value);
modalStore.close();
}

const submit = () => {
finish(["submit", inputValue]);
}

const pause = () => {
finish(["pause", inputValue]);
}

const cancel = () => {
finish(["cancel", ""]);
}
</script>

<div class="modal block overflow-y-auto bg-surface-100-800-token w-modal h-auto p-4 space-y-4 rounded-container-token shadow-xl">
<header class="modal-header text-2xl font-bold">{heading}</header>
<input class="modal-prompt-input input" bind:value={inputValue} type="text" />
<footer class="modal-footer flex justify-end space-x-2">
<button class="btn variant-ghost-surface" type="submit" on:click={cancel}>Cancel</button>
<button class="btn variant-filled" type="submit" on:click={pause}>Pause</button>
<button class="btn variant-filled" type="submit" on:click={submit}>Submit</button>
</footer>
</div>
5 changes: 5 additions & 0 deletions RocketControlUnitGUI/src/lib/components/ReadOnlySvg.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<svg width="20px" height="20px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools --> <title>ic_fluent_read_only_24_regular</title> <desc>Created with Sketch.</desc> <g id="🔍-System-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="ic_fluent_read_only_24_regular" fill="#d4163c" fill-rule="nonzero"> <path d="M3.28046,2.21962 L21.7802,20.7198 C22.0731,21.0127 22.0731,21.4875 21.7802,21.7804 C21.4873,22.0733 21.0125,22.0733 20.7196,21.7804 L14,15.0608 L9.0621,19.9987 C8.78522,20.2756 8.44089,20.4754 8.06312,20.5784 L2.94743,21.9736 C2.38756,22.1263 1.87383,21.6126 2.02652,21.0527 L3.42171,15.937 C3.52474,15.5593 3.72456,15.2149 4.00144,14.9381 L8.93944,10.0001 L2.21979,3.28027 C1.9269,2.98737 1.9269,2.5125 2.2198,2.21961 C2.51269,1.92672 2.98757,1.92672 3.28046,2.21962 Z M10.0001,11.0607 L5.0621,15.9987 C4.9928825,16.067925 4.93811,16.1498063 4.90056266,16.2395344 L4.86886,16.3317 L3.81901,20.1811 L7.66845,19.1313 C7.76289,19.105575 7.85106375,19.061625 7.92818297,19.0023609 L8.00144,18.9381 L12.9392,13.9999 L10.0001,11.0607 Z M15.9698,2.96973 C17.3672,1.57227 19.633,1.57227 21.0304,2.96973 C22.3779893,4.31728071 22.4261175,6.47219893 21.1747846,7.87741875 L21.0304,8.03039 L16.1207,12.9401 L15.0598,11.8793 L17.9393,8.99963 L15.0003,6.06063 L12.1207,8.94013 L11.0601,7.87943 L15.9698,2.96973 Z M17.0304,4.03039 L16.0603,4.99963 L18.9993,7.93963 L19.9698,6.96973 C20.7814,6.15805 20.7814,4.84206 19.9698,4.03039 C19.1581,3.21871 17.8421,3.21871 17.0304,4.03039 Z" id="🎨-Color"> </path> </g> </g> </g>
</svg>
Loading