Skip to content

Commit a26145b

Browse files
committed
fix: fixed an issue that prevented rendering due to ipc handlers, feat: added right click menu and docs-site
1 parent 78ff1a6 commit a26145b

22 files changed

Lines changed: 1553 additions & 63 deletions

File tree

.github/workflows/deploy-docs.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Deploy Docs Site
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'docs-site/**'
9+
- 'README.md'
10+
- 'package.json'
11+
- '.github/workflows/deploy-docs.yml'
12+
workflow_dispatch:
13+
14+
permissions:
15+
contents: read
16+
pages: write
17+
id-token: write
18+
19+
concurrency:
20+
group: "pages"
21+
cancel-in-progress: false
22+
23+
jobs:
24+
build:
25+
runs-on: ubuntu-latest
26+
27+
steps:
28+
- name: Checkout code
29+
uses: actions/checkout@v4
30+
31+
- name: Setup Node.js
32+
uses: actions/setup-node@v4
33+
with:
34+
node-version: '20'
35+
36+
- name: Clean install dependencies
37+
working-directory: ./docs-site
38+
shell: bash
39+
run: |
40+
rm -rf node_modules package-lock.json
41+
npm install --include=optional
42+
43+
- name: Copy additional files
44+
run: |
45+
# Copy README for reference
46+
cp README.md docs-site/public/README.md
47+
# Copy package.json for version info
48+
cp package.json docs-site/public/package.json
49+
50+
- name: Build static site
51+
working-directory: ./docs-site
52+
run: npm run generate
53+
54+
- name: Upload artifact
55+
uses: actions/upload-pages-artifact@v3
56+
with:
57+
path: ./docs-site/.output/public
58+
59+
deploy:
60+
runs-on: ubuntu-latest
61+
needs: build
62+
environment:
63+
name: github-pages
64+
url: ${{ steps.deployment.outputs.page_url }}
65+
66+
steps:
67+
- name: Deploy to GitHub Pages
68+
id: deployment
69+
uses: actions/deploy-pages@v4

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ A desktop application for managing render queues across multiple 3D and composit
44

55
![RenderQ](public/renderq-screenshot.jpg)
66

7+
Built with AI Assistance from Github Copilot using Claude Opus 4.5, Claude Sonnet 4.5 and GPT 5.2
8+
79
## Supported Applications
810

911
| Application | Extensions | Platforms |

assets/scss/main.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ a {
250250
&--paused {
251251
background-color: $status-paused;
252252
}
253+
254+
&--loading {
255+
background-color: $text-tertiary;
256+
animation: loadingPulse 1.5s ease-in-out infinite;
257+
}
253258
}
254259
}
255260

@@ -262,6 +267,15 @@ a {
262267
}
263268
}
264269

270+
@keyframes loadingPulse {
271+
0%, 100% {
272+
opacity: 1;
273+
}
274+
50% {
275+
opacity: 0.5;
276+
}
277+
}
278+
265279
// Badges
266280
.badge {
267281
display: inline-flex;
@@ -301,6 +315,12 @@ a {
301315
color: $status-error;
302316
}
303317

318+
&--loading {
319+
background-color: rgba($text-tertiary, 0.2);
320+
color: $text-secondary;
321+
animation: loadingPulse 1.5s ease-in-out infinite;
322+
}
323+
304324
// Application type badges
305325
&--app {
306326
color: $text-primary;

components/RenderJobItem.vue

Lines changed: 189 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,48 @@
44
:class="[`job-item--${job.status}`, { 'job-item--dragging': isDragging, 'job-item--selected': isSelected }]"
55
:draggable="draggable && job.status !== 'rendering'"
66
@click="handleClick"
7+
@contextmenu.prevent="handleContextMenu"
78
@dragstart="handleDragStart"
89
@dragend="handleDragEnd"
910
>
11+
<!-- Context Menu -->
12+
<div
13+
v-if="showContextMenu"
14+
class="context-menu"
15+
:style="contextMenuPosition"
16+
@click.stop
17+
>
18+
<button class="context-menu__item" @click="openOutputFolder">
19+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
20+
<path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
21+
</svg>
22+
<span>Open Output Folder</span>
23+
</button>
24+
<button class="context-menu__item" @click="openProjectFolder">
25+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
26+
<path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-6 10H6v-2h8v2zm4-4H6v-2h12v2z"/>
27+
</svg>
28+
<span>Open Project Folder</span>
29+
</button>
30+
<button class="context-menu__item" @click="openProject">
31+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
32+
<path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/>
33+
</svg>
34+
<span>Open Project in {{ appLabel }}</span>
35+
</button>
36+
<div class="context-menu__divider"></div>
37+
<button
38+
class="context-menu__item"
39+
@click="resetJob"
40+
:disabled="job.status === 'rendering' || job.status === 'idle'"
41+
>
42+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
43+
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
44+
</svg>
45+
<span>Reset to Pending</span>
46+
</button>
47+
</div>
48+
1049
<div class="job-item__header">
1150
<div
1251
class="job-item__drag-handle"
@@ -66,17 +105,22 @@
66105
<div
67106
class="progress-bar__fill"
68107
:class="[`progress-bar__fill--${job.status}`]"
69-
:style="{ width: `${job.progress}%` }"
108+
:style="{ width: job.status === 'loading' ? '100%' : `${job.progress}%` }"
70109
/>
71110
</div>
72111
<div class="job-item__progress-info">
73112
<span class="job-item__progress-text">
74-
{{ Math.round(job.progress) }}%
75-
<template v-if="job.status === 'rendering' || job.status === 'paused'">
76-
• Frame {{ job.currentFrame }} / {{ job.totalFrames }}
113+
<template v-if="job.status === 'loading'">
114+
Loading...
115+
</template>
116+
<template v-else>
117+
{{ Math.round(job.progress) }}%
118+
<template v-if="job.status === 'rendering' || job.status === 'paused'">
119+
• Frame {{ job.currentFrame }} / {{ job.totalFrames }}
120+
</template>
77121
</template>
78122
</span>
79-
<span v-if="job.estimatedTimeRemaining > 0" class="job-item__eta">
123+
<span v-if="job.estimatedTimeRemaining > 0 && job.status !== 'loading'" class="job-item__eta">
80124
ETA: {{ formatTime(job.estimatedTimeRemaining) }}
81125
</span>
82126
</div>
@@ -163,7 +207,7 @@
163207
</template>
164208

165209
<script setup lang="ts">
166-
import { ref, computed } from 'vue';
210+
import { ref, computed, onUnmounted } from 'vue';
167211
import type { RenderJob } from '~/stores/renderQueue';
168212
import { ApplicationType, APPLICATION_INFO } from '~/types/applications';
169213
@@ -183,10 +227,13 @@ const emit = defineEmits<{
183227
(e: 'dragstart', event: DragEvent): void;
184228
(e: 'dragend', event: DragEvent): void;
185229
(e: 'select'): void;
230+
(e: 'reset'): void;
186231
}>();
187232
188233
const expanded = ref(false);
189234
const isDragging = ref(false);
235+
const showContextMenu = ref(false);
236+
const contextMenuPosition = ref({ top: '0px', left: '0px' });
190237
191238
const statusLabel = computed(() => {
192239
switch (props.job.status) {
@@ -196,10 +243,20 @@ const statusLabel = computed(() => {
196243
case 'complete': return 'Complete';
197244
case 'error': return 'Error';
198245
case 'missing-app': return 'Missing App';
246+
case 'loading': return loadingLabel.value;
199247
default: return props.job.status;
200248
}
201249
});
202250
251+
// Loading label - different text for After Effects/Nuke (comp) vs others (scene)
252+
const loadingLabel = computed(() => {
253+
const appType = props.job.applicationType || ApplicationType.BLENDER;
254+
if (appType === ApplicationType.AFTER_EFFECTS || appType === ApplicationType.NUKE) {
255+
return 'Fetching comp metadata...';
256+
}
257+
return 'Fetching scene metadata...';
258+
});
259+
203260
// Application type display
204261
const appLabel = computed(() => {
205262
const appType = props.job.applicationType || ApplicationType.BLENDER;
@@ -280,15 +337,84 @@ function openOutputFolder() {
280337
if (typeof window !== 'undefined' && (window as any).electronAPI) {
281338
(window as any).electronAPI.openInExplorer(props.job.outputDir);
282339
}
340+
closeContextMenu();
283341
}
342+
343+
// Context menu handlers
344+
function handleContextMenu(e: MouseEvent) {
345+
e.preventDefault();
346+
347+
// Get the bounding rect of the job item to position the menu
348+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
349+
350+
// Position the menu at the click point, but relative to the item
351+
contextMenuPosition.value = {
352+
top: `${e.clientY - rect.top}px`,
353+
left: `${e.clientX - rect.left}px`
354+
};
355+
356+
showContextMenu.value = true;
357+
358+
// Close menu when clicking elsewhere
359+
setTimeout(() => {
360+
document.addEventListener('click', closeContextMenu, { once: true });
361+
document.addEventListener('contextmenu', closeContextMenu, { once: true });
362+
}, 0);
363+
}
364+
365+
function closeContextMenu() {
366+
showContextMenu.value = false;
367+
}
368+
369+
function openProjectFolder() {
370+
if (typeof window !== 'undefined' && (window as any).electronAPI) {
371+
// Get the directory containing the project file
372+
const filePath = props.job.filePath;
373+
(window as any).electronAPI.openInExplorer(filePath);
374+
}
375+
closeContextMenu();
376+
}
377+
378+
function openProject() {
379+
if (typeof window !== 'undefined' && (window as any).electronAPI) {
380+
// Open the project file with its default application (like double-clicking)
381+
(window as any).electronAPI.openFileWithDefaultApp(props.job.filePath);
382+
}
383+
closeContextMenu();
384+
}
385+
386+
function resetJob() {
387+
if (props.job.status !== 'rendering' && props.job.status !== 'idle') {
388+
emit('update', {
389+
status: 'idle',
390+
progress: 0,
391+
currentFrame: 0,
392+
elapsedTime: 0,
393+
estimatedTimeRemaining: 0,
394+
lastRenderedFrame: null,
395+
error: null,
396+
renderStartTime: null,
397+
frameTimes: [],
398+
renderedFramePaths: [],
399+
});
400+
}
401+
closeContextMenu();
402+
}
403+
404+
// Cleanup on unmount
405+
onUnmounted(() => {
406+
document.removeEventListener('click', closeContextMenu);
407+
document.removeEventListener('contextmenu', closeContextMenu);
408+
});
284409
</script>
285410

286411
<style lang="scss">
287412
.job-item {
413+
position: relative;
288414
background-color: $bg-tertiary;
289415
border: 1px solid $border-subtle;
290416
border-radius: $radius-lg;
291-
overflow: hidden;
417+
overflow: visible;
292418
transition: border-color $transition-fast ease, opacity $transition-fast ease, transform $transition-fast ease;
293419
294420
&:hover {
@@ -345,6 +471,15 @@ function openOutputFolder() {
345471
}
346472
}
347473
474+
&--loading {
475+
border-color: $text-tertiary;
476+
opacity: 0.8;
477+
478+
.job-item__name {
479+
color: $text-secondary;
480+
}
481+
}
482+
348483
&__header {
349484
display: flex;
350485
justify-content: space-between;
@@ -561,4 +696,51 @@ function openOutputFolder() {
561696
color: $text-primary;
562697
}
563698
}
699+
700+
.context-menu {
701+
position: absolute;
702+
z-index: 1000;
703+
min-width: 200px;
704+
background-color: $bg-secondary;
705+
border: 1px solid $border-subtle;
706+
border-radius: $radius-md;
707+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
708+
padding: $spacing-02;
709+
710+
&__item {
711+
display: flex;
712+
align-items: center;
713+
gap: $spacing-03;
714+
width: 100%;
715+
padding: $spacing-03 $spacing-04;
716+
background: none;
717+
border: none;
718+
border-radius: $radius-sm;
719+
color: $text-primary;
720+
font-size: $font-size-sm;
721+
text-align: left;
722+
cursor: pointer;
723+
transition: background-color $transition-fast ease;
724+
725+
&:hover:not(:disabled) {
726+
background-color: rgba($accent-primary, 0.1);
727+
}
728+
729+
&:disabled {
730+
opacity: 0.5;
731+
cursor: not-allowed;
732+
}
733+
734+
svg {
735+
flex-shrink: 0;
736+
color: $text-secondary;
737+
}
738+
}
739+
740+
&__divider {
741+
height: 1px;
742+
background-color: $border-subtle;
743+
margin: $spacing-02 0;
744+
}
745+
}
564746
</style>

0 commit comments

Comments
 (0)