diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d90a0be..2270b22 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -3,8 +3,8 @@ name: Publish Docker Image on: push: branches: [ "master" ] - # You can also trigger this on release tags: - # tags: [ 'v*.*.*' ] + # Uncomment to also trigger on release tags: + # tags: [ 'v*.*.*' ] env: REGISTRY_GHCR: ghcr.io @@ -23,6 +23,9 @@ jobs: with: submodules: recursive + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to GitHub Container Registry (GHCR) uses: docker/login-action@v3 with: @@ -36,9 +39,6 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -48,10 +48,12 @@ jobs: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/velxio - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: Dockerfile.standalone push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile.standalone b/Dockerfile.standalone index 882bba6..b251fe4 100644 --- a/Dockerfile.standalone +++ b/Dockerfile.standalone @@ -1,9 +1,9 @@ # ---- Stage 1: Build frontend and wokwi-libs ---- -FROM node:20 AS frontend-builder +FROM node:20-slim AS frontend-builder WORKDIR /app -# Copy package.json for metadata generation and common dependencies +# Copy root package.json (metadata/scripts) COPY package.json . RUN npm install @@ -22,35 +22,34 @@ COPY wokwi-libs/wokwi-elements/ wokwi-libs/wokwi-elements/ WORKDIR /app/wokwi-libs/wokwi-elements RUN npm install && npm run build -# Build frontend and generate metadata +# Build frontend WORKDIR /app COPY frontend/ frontend/ COPY scripts/ scripts/ WORKDIR /app/frontend -# Explicitly install frontend dependencies -RUN npm install -# Now run the build which includes metadata generation -RUN npm run build +RUN npm install && npm run build # ---- Stage 2: Final Production Image ---- FROM python:3.12-slim -# Install system dependencies, arduino-cli, and nginx +# Install system dependencies and nginx RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ ca-certificates \ nginx \ - && curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh \ - && mv bin/arduino-cli /usr/local/bin/ \ - && rm -rf bin \ - && apt-get purge -y curl \ - && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Initialize arduino-cli and install AVR & RP2040 cores -RUN arduino-cli core update-index \ +# Install arduino-cli into /usr/local/bin directly (avoids touching /bin) +RUN curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh \ + | BINDIR=/usr/local/bin sh + +# Initialize arduino-cli config, add RP2040 board manager URL, then install cores +RUN arduino-cli config init \ + && arduino-cli config add board_manager.additional_urls \ + https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json \ + && arduino-cli core update-index \ && arduino-cli core install arduino:avr \ && arduino-cli core install rp2040:rp2040 @@ -73,7 +72,6 @@ COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html COPY deploy/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh -# Expose port 80 for the unified web server EXPOSE 80 CMD ["/app/entrypoint.sh"] diff --git a/frontend/src/App.css b/frontend/src/App.css index 0cc1cf6..c618b52 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -108,6 +108,22 @@ body { color: #ccc; } +/* Resize handle for the file explorer sidebar */ +.explorer-resize-handle { + width: 4px; + flex-shrink: 0; + cursor: col-resize; + background: #2a2a2a; + transition: background 0.15s; + position: relative; + z-index: 5; +} + +.explorer-resize-handle:hover, +.explorer-resize-handle:active { + background: #007acc; +} + .editor-wrapper { flex: 1; overflow: hidden; diff --git a/frontend/src/components/editor/FileExplorer.css b/frontend/src/components/editor/FileExplorer.css index 312a95f..14b7756 100644 --- a/frontend/src/components/editor/FileExplorer.css +++ b/frontend/src/components/editor/FileExplorer.css @@ -1,6 +1,5 @@ .file-explorer { - width: 195px; - flex-shrink: 0; + width: 100%; background: #252526; border-right: 1px solid #333; display: flex; diff --git a/frontend/src/pages/EditorPage.tsx b/frontend/src/pages/EditorPage.tsx index 3b16155..9060305 100644 --- a/frontend/src/pages/EditorPage.tsx +++ b/frontend/src/pages/EditorPage.tsx @@ -22,6 +22,10 @@ const BOTTOM_PANEL_MIN = 80; const BOTTOM_PANEL_MAX = 600; const BOTTOM_PANEL_DEFAULT = 200; +const EXPLORER_MIN = 120; +const EXPLORER_MAX = 500; +const EXPLORER_DEFAULT = 210; + const resizeHandleStyle: React.CSSProperties = { height: 5, flexShrink: 0, @@ -42,6 +46,7 @@ export const EditorPage: React.FC = () => { const [saveModalOpen, setSaveModalOpen] = useState(false); const [loginPromptOpen, setLoginPromptOpen] = useState(false); const [explorerOpen, setExplorerOpen] = useState(true); + const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT); const user = useAuthStore((s) => s.user); const handleSaveClick = useCallback(() => { @@ -110,6 +115,27 @@ export const EditorPage: React.FC = () => { document.addEventListener('mouseup', onUp); }, [bottomPanelHeight]); + const handleExplorerResizeMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + const startX = e.clientX; + const startWidth = explorerWidth; + + const onMove = (ev: MouseEvent) => { + const delta = ev.clientX - startX; + setExplorerWidth(Math.max(EXPLORER_MIN, Math.min(EXPLORER_MAX, startWidth + delta))); + }; + const onUp = () => { + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + }; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }, [explorerWidth]); + return (