Skip to content

Commit f777e0e

Browse files
committed
feat(canvas-editor): optimize viewport sizing and add background toolbar support
- Dynamic canvas width: fills available space (capped at 1400px) instead of fixed 900px - Reduce height constraint from -200px to -120px for more vertical space - Remove excessive parent padding for canvas edit step using :has() selectors - Mobile: align canvas to top instead of center to maximize space - FloatingTapBar: use theme-aware CSS variables (background-color, font-color) - Add full background element support in floating toolbar (opacity, color controls)
1 parent 594c892 commit f777e0e

51 files changed

Lines changed: 983 additions & 201 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api/routes/sharepic/sharepic_claude/unifiedHandler.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,6 @@ export async function handleUnifiedRequest(
209209
if (type === 'zitat' || type === 'zitat_pure') {
210210
response.quote = mainData;
211211
response.name = name || '';
212-
delete response.alternatives;
213-
delete response.searchTerms;
214212
}
215213

216214
log.info(`[${type}] Success on attempt ${attempts}`);

apps/api/server.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,51 @@ if (cluster.isPrimary) {
5454
}
5555

5656
// Start Hocuspocus WebSocket server if enabled
57-
let hocuspocusProcess: any = null;
57+
let hocuspocusProcess: ReturnType<typeof spawn> | null = null;
58+
let isShuttingDown = false;
59+
60+
const killHocuspocus = () => {
61+
if (hocuspocusProcess && !hocuspocusProcess.killed) {
62+
log.info('Killing Hocuspocus process...');
63+
hocuspocusProcess.kill('SIGTERM');
64+
// Force kill after 3 seconds if still alive
65+
setTimeout(() => {
66+
if (hocuspocusProcess && !hocuspocusProcess.killed) {
67+
log.warn('Force killing Hocuspocus process...');
68+
hocuspocusProcess.kill('SIGKILL');
69+
}
70+
}, 3000);
71+
}
72+
};
73+
74+
// Ensure Hocuspocus is killed when master process exits
75+
process.on('exit', killHocuspocus);
76+
process.on('beforeExit', killHocuspocus);
77+
5878
if (process.env.HOCUSPOCUS_ENABLED === 'true') {
5979
log.info('Starting Hocuspocus WebSocket server...');
6080
hocuspocusProcess = spawn('npx', ['tsx', 'services/hocuspocus/hocuspocusServer.ts'], {
6181
cwd: __dirname,
6282
stdio: 'inherit',
63-
env: process.env
83+
env: process.env,
84+
detached: false // Ensure child process is attached to parent
6485
});
6586

6687
hocuspocusProcess.on('error', (error: Error) => {
6788
log.error(`Hocuspocus server error: ${error.message}`);
6889
});
6990

70-
hocuspocusProcess.on('exit', (code: number, signal: string) => {
91+
hocuspocusProcess.on('exit', (code: number | null, signal: string | null) => {
7192
log.warn(`Hocuspocus server exited (code: ${code}, signal: ${signal})`);
72-
if (code !== 0 && code !== null) {
73-
log.error('Hocuspocus server crashed, restarting...');
93+
if (!isShuttingDown && code !== 0 && code !== null) {
94+
log.error('Hocuspocus server crashed, restarting in 2s...');
7495
setTimeout(() => {
75-
if (process.env.HOCUSPOCUS_ENABLED === 'true') {
96+
if (!isShuttingDown && process.env.HOCUSPOCUS_ENABLED === 'true') {
7697
hocuspocusProcess = spawn('npx', ['tsx', 'services/hocuspocus/hocuspocusServer.ts'], {
7798
cwd: __dirname,
7899
stdio: 'inherit',
79-
env: process.env
100+
env: process.env,
101+
detached: false
80102
});
81103
}
82104
}, 2000);
@@ -88,10 +110,8 @@ if (cluster.isPrimary) {
88110
workerTimeout: 10000,
89111
logger: log,
90112
onComplete: () => {
91-
if (hocuspocusProcess) {
92-
log.info('Shutting down Hocuspocus server...');
93-
hocuspocusProcess.kill('SIGTERM');
94-
}
113+
isShuttingDown = true;
114+
killHocuspocus();
95115
}
96116
});
97117

apps/api/services/hocuspocus/hocuspocusServer.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,62 @@
11
import { Server } from '@hocuspocus/server';
22
import { Logger } from '@hocuspocus/extension-logger';
3+
import { createServer } from 'net';
4+
import { exec } from 'child_process';
5+
import { promisify } from 'util';
36
import { createLogger } from '../../utils/logger.js';
47
import { PostgresPersistence } from './persistence.js';
58
import { authenticateConnection } from './auth.js';
69

10+
const execAsync = promisify(exec);
711
const log = createLogger('HocuspocusServer');
812

913
const PORT = parseInt(process.env.HOCUSPOCUS_PORT || '1240', 10);
1014
const HOST = process.env.HOCUSPOCUS_HOST || '0.0.0.0';
1115

16+
/**
17+
* Check if a port is available
18+
*/
19+
async function isPortAvailable(port: number): Promise<boolean> {
20+
return new Promise((resolve) => {
21+
const server = createServer();
22+
server.once('error', () => resolve(false));
23+
server.once('listening', () => {
24+
server.close();
25+
resolve(true);
26+
});
27+
server.listen(port);
28+
});
29+
}
30+
31+
/**
32+
* Kill process using the specified port (Linux/Mac only)
33+
*/
34+
async function killProcessOnPort(port: number): Promise<boolean> {
35+
try {
36+
const { stdout } = await execAsync(`lsof -ti :${port}`);
37+
const pids = stdout.trim().split('\n').filter(Boolean);
38+
39+
if (pids.length > 0) {
40+
log.warn(`Found ${pids.length} process(es) on port ${port}, killing...`);
41+
for (const pid of pids) {
42+
try {
43+
await execAsync(`kill -9 ${pid}`);
44+
log.info(`Killed process ${pid} on port ${port}`);
45+
} catch {
46+
// Process may have already exited
47+
}
48+
}
49+
// Wait a moment for the port to be released
50+
await new Promise(resolve => setTimeout(resolve, 500));
51+
return true;
52+
}
53+
return false;
54+
} catch {
55+
// No process found on port (lsof returns error)
56+
return false;
57+
}
58+
}
59+
1260
/**
1361
* Hocuspocus WebSocket Server for Real-Time Collaborative Editing
1462
*
@@ -23,6 +71,23 @@ export async function startHocuspocusServer(): Promise<void> {
2371
try {
2472
log.info('Initializing Hocuspocus server...');
2573

74+
// Check if port is available, kill existing process if needed
75+
const portAvailable = await isPortAvailable(PORT);
76+
if (!portAvailable) {
77+
log.warn(`Port ${PORT} is already in use, attempting to free it...`);
78+
const killed = await killProcessOnPort(PORT);
79+
if (killed) {
80+
// Verify port is now available
81+
const nowAvailable = await isPortAvailable(PORT);
82+
if (!nowAvailable) {
83+
throw new Error(`Port ${PORT} is still in use after kill attempt`);
84+
}
85+
log.info(`Port ${PORT} is now available`);
86+
} else {
87+
throw new Error(`Port ${PORT} is in use and could not be freed`);
88+
}
89+
}
90+
2691
// Initialize PostgreSQL persistence
2792
const persistence = new PostgresPersistence();
2893

apps/web/src/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ function App() {
8383
])
8484
.then(() => {
8585
setAppReady(true);
86-
console.log('[App] Application initialized');
8786
})
8887
.catch((error) => {
8988
console.error('[App] Initialization failed:', error);

apps/web/src/api/lazyApiClient.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export async function initializeApiClient(): Promise<void> {
3232
// Dynamic import defers loading until this function is called
3333
await import('../components/utils/apiClient');
3434
apiClientInitialized = true;
35-
console.log('[API] API client initialized');
3635
} catch (error) {
3736
console.error('[API] Failed to initialize API client:', error);
3837
// Reset so it can be retried

apps/web/src/assets/styles/components/image-studio/typeform-fields.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,3 +483,13 @@
483483
border-radius: 3px;
484484
transition: width 0.3s ease;
485485
}
486+
487+
/* Canvas edit step - eliminate excessive parent padding */
488+
.typeform-content:has(.typeform-field--canvas-edit) {
489+
padding: 0;
490+
}
491+
492+
.typeform-field--canvas-edit {
493+
width: 100%;
494+
height: 100%;
495+
}

apps/web/src/assets/styles/components/sharepic/sharepic-type-selector.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,4 +768,14 @@
768768
.type-options-grid--four {
769769
grid-template-columns: 1fr;
770770
}
771+
}
772+
773+
/* Canvas edit mode - eliminate outer padding for maximum canvas space */
774+
.type-selector-screen:has(.typeform-field--canvas-edit) {
775+
padding: 0;
776+
}
777+
778+
.type-selector-content:has(.typeform-field--canvas-edit) {
779+
padding: 0;
780+
max-width: none;
771781
}

apps/web/src/features/image-studio/ImageStudioPage.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ const ImageStudioPageContent: React.FC<ImageStudioPageContentProps> = ({ showHea
7575
// Auto-route Austrian users to KI category
7676
useEffect(() => {
7777
if (isAustrianUser && !category) {
78-
console.log('[ImageStudioPage] Austrian user detected, auto-routing to KI category');
7978
setCategory('ki', null);
8079
}
8180
}, [isAustrianUser, category, setCategory]);

apps/web/src/features/image-studio/canvas-editor/components/FloatingTapBar/FloatingTapBar.css

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@
2222
/* Ensure the bar content itself captures events */
2323
.floating-tap-bar {
2424
pointer-events: auto;
25-
background: #ffffff;
25+
background: var(--background-color);
26+
color: var(--font-color);
2627
border: 1px solid var(--border-default);
2728
border-radius: 999px;
28-
/* Fully rounded capsule for better "toolbar" feel */
2929
padding: 6px;
30-
/* Tighter padding */
3130
box-shadow:
3231
0 4px 6px -1px rgba(0, 0, 0, 0.1),
3332
0 2px 4px -1px rgba(0, 0, 0, 0.06);
@@ -36,7 +35,6 @@
3635
gap: var(--spacing-2);
3736
animation: slideDownFade 0.2s cubic-bezier(0.16, 1, 0.3, 1);
3837
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
39-
/* Smooth resize on expand */
4038
}
4139

4240
/* Mobile-specific compact styles */
@@ -47,12 +45,6 @@
4745
}
4846
}
4947

50-
[data-theme="dark"] .floating-tap-bar {
51-
background: var(--surface-card);
52-
border-color: var(--border-subtle);
53-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
54-
}
55-
5648
@keyframes slideDownFade {
5749
from {
5850
opacity: 0;

apps/web/src/features/image-studio/canvas-editor/components/FloatingToolbar.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,34 @@ export const FloatingToolbar = memo(({
206206
)}
207207
</>
208208
)}
209+
210+
{activeFloatingModule.type === 'background' && (
211+
<>
212+
{activeFloatingModule.data.fill !== undefined && (
213+
<>
214+
<FloatingColorPicker
215+
currentColor={activeFloatingModule.data.fill || '#FFFFFF'}
216+
onColorSelect={handlers.handleColorSelect}
217+
isExpanded={isColorPickerExpanded}
218+
onExpandChange={setIsColorPickerExpanded}
219+
/>
220+
{!shouldHideOtherControls && <div className="floating-separator" />}
221+
</>
222+
)}
223+
{!shouldHideOtherControls && (
224+
<FloatingOpacityControl
225+
opacity={activeFloatingModule.data.opacity ?? 1}
226+
onOpacityChange={(val) =>
227+
handlers.handleOpacityChange(
228+
activeFloatingModule.data.id,
229+
val,
230+
'background'
231+
)
232+
}
233+
/>
234+
)}
235+
</>
236+
)}
209237
</>
210238
)}
211239
</FloatingTapBar>

0 commit comments

Comments
 (0)