-
Notifications
You must be signed in to change notification settings - Fork 6
Print dashboard refinement #78
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
base: staging
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,15 +13,23 @@ export async function load({ locals }) { | |||||
| throw error(403, { message: 'oi get out' }); | ||||||
| } | ||||||
|
|
||||||
| const projects = await getProjects(['t1_approved'], [], []); | ||||||
|
|
||||||
| const allowedStatuses: (typeof project.status._.data)[] = ['printing', 't1_approved']; | ||||||
| const projects = await getProjects(allowedStatuses, [], []); | ||||||
|
|
||||||
| const allProjects = await db | ||||||
| .select({ | ||||||
| id: project.id, | ||||||
| name: project.name | ||||||
| name: project.name, | ||||||
| status: project.status, | ||||||
| userId: project.userId | ||||||
| }) | ||||||
| .from(project) | ||||||
| .where(and(eq(project.deleted, false))); | ||||||
| .where(and( | ||||||
| eq(project.deleted, false), | ||||||
| inArray(project.status, ['printing', 't1_approved', 'printed']), | ||||||
| sql`(${project.status} != 'printed' OR ${project.userId} = ${locals.user.id})` | ||||||
| )); | ||||||
|
|
||||||
| const users = await db | ||||||
| .select({ | ||||||
|
|
@@ -37,7 +45,8 @@ export async function load({ locals }) { | |||||
| allProjects, | ||||||
| projects, | ||||||
| users, | ||||||
| currentlyPrinting | ||||||
| currentlyPrinting, | ||||||
| currentUserId: locals.user.id | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -51,20 +60,27 @@ export const actions = { | |||||
| } | ||||||
|
|
||||||
| const data = await request.formData(); | ||||||
| const statusFilter = data.getAll('status') as (typeof project.status._.data)[]; | ||||||
| const allowedStatuses: (typeof project.status._.data)[] = ['printing', 't1_approved', 'printed']; | ||||||
| const statusFilter = data.getAll('status') | ||||||
| .map((s) => s.toString()) | ||||||
| .filter((s): s is typeof project.status._.data => allowedStatuses.includes(s as typeof project.status._.data)) as (typeof project.status._.data)[]; | ||||||
|
|
||||||
| const projectFilter = data.getAll('project').map((projectId) => { | ||||||
| const parsedInt = parseInt(projectId.toString()); | ||||||
| if (!parsedInt) throw error(400, { message: 'malformed project filter' }); | ||||||
| return parseInt(projectId.toString()); | ||||||
| }); | ||||||
|
|
||||||
| const userFilter = data.getAll('user').map((userId) => { | ||||||
| let userFilter = data.getAll('user').map((userId) => { | ||||||
| const parsedInt = parseInt(userId.toString()); | ||||||
| if (!parsedInt) throw error(400, { message: 'malformed user filter' }); | ||||||
| return parseInt(userId.toString()); | ||||||
|
Comment on lines
+74
to
77
|
||||||
| }); | ||||||
|
|
||||||
| if (statusFilter.length === 1 && statusFilter[0] === 'printed') { | ||||||
|
||||||
| if (statusFilter.length === 1 && statusFilter[0] === 'printed') { | |
| if (statusFilter.length === 1 && statusFilter[0] === 'printed' && userFilter.length === 0) { |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,15 +7,23 @@ | |||||||||||||||||||||
| let { data, form } = $props(); | ||||||||||||||||||||||
| type AllProject = { id: number; name: string | null; status: string; userId: number }; | ||||||||||||||||||||||
| const allProjects: AllProject[] = data.allProjects; | ||||||||||||||||||||||
| const currentUserId: number = data.currentUserId; | ||||||||||||||||||||||
| let projectSearch = $state(''); | ||||||||||||||||||||||
| let userSearch = $state(''); | ||||||||||||||||||||||
| let projects = $derived(form?.projects ?? data.projects); | ||||||||||||||||||||||
| let filteredProjects = $derived( | ||||||||||||||||||||||
| data.allProjects.filter((project) => | ||||||||||||||||||||||
| project.name?.toLowerCase().includes(projectSearch.toLowerCase()) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| allProjects | ||||||||||||||||||||||
| .filter((project) => { | ||||||||||||||||||||||
| if (project.status === 'printing') return true; | ||||||||||||||||||||||
| if (project.status === 'printed' && project.userId === currentUserId) return true; | ||||||||||||||||||||||
| return false; | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
| .filter((project) => project.name?.toLowerCase().includes(projectSearch.toLowerCase())) | ||||||||||||||||||||||
|
Comment on lines
+20
to
+26
|
||||||||||||||||||||||
| allProjects | |
| .filter((project) => { | |
| if (project.status === 'printing') return true; | |
| if (project.status === 'printed' && project.userId === currentUserId) return true; | |
| return false; | |
| }) | |
| .filter((project) => project.name?.toLowerCase().includes(projectSearch.toLowerCase())) | |
| allProjects.filter((project) => | |
| project.name?.toLowerCase().includes(projectSearch.toLowerCase()) | |
| ) |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent indentation: lines 75-76 use tabs while line 77 uses a tab plus spaces. All three option elements should have the same indentation level for consistency.
| <option value="printed" class="truncate">{projectStatuses['printed'] ?? 'Printed'}</option> | |
| <option value="printed" class="truncate">{projectStatuses['printed'] ?? 'Printed'}</option> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ import { eq, and, asc, sql } from 'drizzle-orm'; | |||||||||||||||||
| import type { Actions } from './$types'; | ||||||||||||||||||
| import { sendSlackDM } from '$lib/server/slack.js'; | ||||||||||||||||||
| import { getReviewHistory } from '../../getReviewHistory.server'; | ||||||||||||||||||
| import { getCurrentlyPrinting, calculateFilamentUsage } from '../utils.server'; | ||||||||||||||||||
|
|
||||||||||||||||||
| export async function load({ locals, params }) { | ||||||||||||||||||
| if (!locals.user) { | ||||||||||||||||||
|
|
@@ -213,26 +214,24 @@ export const actions = { | |||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const data = await request.formData(); | ||||||||||||||||||
| const filamentUsed = data.get('filament'); | ||||||||||||||||||
| const gcodeFile = data.get('gcode'); | ||||||||||||||||||
| const notes = data.get('notes')?.toString(); | ||||||||||||||||||
| const feedback = data.get('feedback')?.toString(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (notes === null || feedback === null) { | ||||||||||||||||||
| if (!gcodeFile || typeof gcodeFile === 'string' || notes === null || feedback === null) { | ||||||||||||||||||
| return error(400); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if ( | ||||||||||||||||||
| !filamentUsed || | ||||||||||||||||||
| isNaN(parseFloat(filamentUsed.toString())) || | ||||||||||||||||||
| parseFloat(filamentUsed.toString()) < 0 | ||||||||||||||||||
| ) { | ||||||||||||||||||
| return error(400, { message: 'invalid filament used' }); | ||||||||||||||||||
| const gcodeText = await gcodeFile.text(); | ||||||||||||||||||
|
||||||||||||||||||
| const gcodeText = await gcodeFile.text(); | |
| const MAX_GCODE_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB | |
| const gcodeBlob = gcodeFile as Blob; | |
| if (typeof (gcodeBlob as any).size === 'number' && (gcodeBlob as any).size > MAX_GCODE_SIZE_BYTES) { | |
| return error(413, { message: 'G-code file is too large' }); | |
| } | |
| const gcodeText = await gcodeBlob.text(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -163,6 +163,7 @@ | |
| <form | ||
| method="POST" | ||
| action="?/print" | ||
| enctype="multipart/form-data" | ||
| class="flex flex-col gap-3" | ||
| use:enhance={() => { | ||
| printFormPending = true; | ||
|
|
@@ -175,15 +176,14 @@ | |
| return confirm('really submit?'); | ||
| }} | ||
| > | ||
|
|
||
| <label class="flex flex-col gap-1"> | ||
| <span class="font-medium">Filament used <span class="opacity-50">(grams)</span></span> | ||
| <span class="font-medium">G-code file <span class="opacity-50">(from slicer, required)</span></span> | ||
| <input | ||
| name="filament" | ||
| type="number" | ||
| name="gcode" | ||
| type="file" | ||
| class="themed-input-on-box" | ||
| placeholder="50" | ||
| step="0.1" | ||
| min="0" | ||
| accept=".gcode" | ||
| required | ||
| /> | ||
|
Comment on lines
182
to
188
|
||
| </label> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,3 +20,32 @@ export async function getCurrentlyPrinting(user: { id: number | SQLWrapper }) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return currentlyPrinting; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | |
| * Estimates the amount of filament used in a print based on its G-code. | |
| * | |
| * The function parses the provided G-code text, tracking the extruder position (`E` value) | |
| * on supported commands and summing only the positive increases in extrusion length. | |
| * | |
| * Assumptions and calculation details: | |
| * - Filament diameter is assumed to be 1.75 mm. | |
| * - Filament density is assumed to be 1.24 g/cm³ (typical for PLA). | |
| * - G-code units are assumed to be millimeters. | |
| * - Extrusion is assumed to be in absolute mode for the `E` axis. | |
| * - Only the following commands are interpreted: | |
| * - `G1` with an `E` parameter for extrusion moves; increases in `E` are treated as | |
| * additional filament length extruded. | |
| * - `G92` with an `E` parameter to reset the current extruder position. | |
| * - All other commands and parameters are ignored for the purpose of this calculation. | |
| * | |
| * The total extruded filament length (in mm) is converted to volume using the | |
| * cross-sectional area of a 1.75 mm filament, then converted to cm³ and multiplied | |
| * by the assumed density to estimate the mass in grams. | |
| * | |
| * @param gcodeText - Full G-code content as a string, using millimeters and absolute `E`. | |
| * @returns Estimated filament mass in grams. | |
| */ |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The filament diameter (1.75mm) and density (1.24 g/cm³) are hardcoded. These values may vary depending on the filament type (PLA, ABS, PETG, etc.) and manufacturer. Consider making these configurable parameters or documenting which filament type these values represent. The density appears to be for PLA, but this should be explicitly documented.
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regular expression pattern allows multiple dots in numbers (e.g., "1.2.3" would match). This could lead to parseFloat returning NaN for malformed numbers, though the validation at line 227 would catch it. Consider using a more restrictive pattern like /E(-?[0-9]+.?[0-9]*)/ to ensure only valid decimal numbers are matched.
| const eMatch = trimmed.match(/E([0-9.-]+)/); | |
| if (eMatch) { | |
| currentE = parseFloat(eMatch[1]); | |
| } | |
| } else if (trimmed.startsWith('G1')) { | |
| const eMatch = trimmed.match(/E([0-9.-]+)/); | |
| const eMatch = trimmed.match(/E(-?\d+(?:\.\d+)?)/); | |
| if (eMatch) { | |
| currentE = parseFloat(eMatch[1]); | |
| } | |
| } else if (trimmed.startsWith('G1')) { | |
| const eMatch = trimmed.match(/E(-?\d+(?:\.\d+)?)/); |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The G-code parser only accounts for positive extrusion differences when newE is greater than currentE. However, it doesn't handle retractions (negative E movements) properly. When newE is less than currentE (which happens during retraction), the currentE is still updated to newE, but the totalExtruded is not adjusted. This can lead to incorrect calculations if the G-code uses absolute extrusion positioning. Consider tracking retractions separately or handling all E value changes to maintain accurate state.
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function processes every line in the G-code file even if most lines don't contain E (extrusion) commands. For large G-code files, this could impact performance. Consider optimizing by checking for the presence of 'E' in the line before running the regex match, or filtering lines that contain 'E' first before processing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parseInt conversion is called twice on the same value. The result of the first parseInt on line 69 is already validated, so the second call on line 71 is redundant. Consider storing the result in a variable and returning it directly to improve code clarity and avoid unnecessary computation.