mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Remove client
This commit is contained in:
54
.github/workflows/ci.yml
vendored
54
.github/workflows/ci.yml
vendored
@@ -33,60 +33,6 @@ jobs:
|
|||||||
- name: Build and test
|
- name: Build and test
|
||||||
run: mvn -B verify
|
run: mvn -B verify
|
||||||
|
|
||||||
electron-build:
|
|
||||||
timeout-minutes: 10
|
|
||||||
name: Electron packages (${{ matrix.os }})
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-latest
|
|
||||||
build-command: npx electron-builder --linux AppImage --publish never
|
|
||||||
artifact-name: electron-linux
|
|
||||||
- os: windows-latest
|
|
||||||
build-command: npx electron-builder --win nsis --publish never
|
|
||||||
artifact-name: electron-windows
|
|
||||||
- os: macos-latest
|
|
||||||
build-command: npx electron-builder --mac --publish never
|
|
||||||
artifact-name: electron-macos
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
lfs: true
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: package-lock.json
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build Electron bundles
|
|
||||||
run: ${{ matrix.build-command }}
|
|
||||||
|
|
||||||
- name: Smoke test AppImage
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
run: |
|
|
||||||
chmod +x dist/*.AppImage
|
|
||||||
./dist/*.AppImage --appimage-extract
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y xvfb
|
|
||||||
cd squashfs-root
|
|
||||||
APPDIR="$(pwd)" xvfb-run ./AppRun --no-sandbox
|
|
||||||
|
|
||||||
- name: Upload Electron artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.artifact-name }}
|
|
||||||
path: |
|
|
||||||
dist/*.AppImage
|
|
||||||
dist/*.exe
|
|
||||||
dist/*.dmg
|
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
41
.github/workflows/release.yml
vendored
41
.github/workflows/release.yml
vendored
@@ -1,41 +0,0 @@
|
|||||||
name: Manual Release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release (${{ matrix.os }})
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-latest
|
|
||||||
args: --linux AppImage
|
|
||||||
- os: windows-latest
|
|
||||||
args: --win nsis
|
|
||||||
- os: macos-latest
|
|
||||||
args: --mac
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
lfs: true
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
cache: npm
|
|
||||||
- name: Install deps
|
|
||||||
run: npm ci
|
|
||||||
- name: Verify version is set
|
|
||||||
run: |
|
|
||||||
node -e "process.exit(require('./package.json').version ? 0 : 1)"
|
|
||||||
- name: Build + publish
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
npx electron-builder ${{ matrix.args }} --publish always
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,7 +10,6 @@ local/
|
|||||||
*.db-shm
|
*.db-shm
|
||||||
*.db-wal
|
*.db-wal
|
||||||
previews/
|
previews/
|
||||||
node_modules/
|
|
||||||
src/main/node/dist/
|
src/main/node/dist/
|
||||||
/assets
|
/assets
|
||||||
!src/main/resources/assets/
|
!src/main/resources/assets/
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -4,19 +4,13 @@
|
|||||||
.DEFAULT_GOAL := build
|
.DEFAULT_GOAL := build
|
||||||
|
|
||||||
IMGFLOAT_DB_PATH ?= ./imgfloat.db
|
IMGFLOAT_DB_PATH ?= ./imgfloat.db
|
||||||
IMGFLOAT_GITHUB_OWNER ?= Kruhlmann
|
IMGFLOAT_GITHUB_OWNER ?= imgfloat
|
||||||
IMGFLOAT_GITHUB_REPO ?= imgfloat-j
|
IMGFLOAT_GITHUB_REPO ?= client
|
||||||
IMGFLOAT_ASSETS_PATH ?= ./assets
|
IMGFLOAT_ASSETS_PATH ?= ./assets
|
||||||
IMGFLOAT_PREVIEWS_PATH ?= ./previews
|
IMGFLOAT_PREVIEWS_PATH ?= ./previews
|
||||||
SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE ?= 10MB
|
SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE ?= 10MB
|
||||||
SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE ?= 10MB
|
SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE ?= 10MB
|
||||||
WATCHDIR = ./src/main
|
WATCHDIR = ./src/main
|
||||||
ELECTRON := $(shell \
|
|
||||||
if [ -f /etc/os-release ] && grep -q '^ID=nixos' /etc/os-release; then \
|
|
||||||
echo electron; \
|
|
||||||
else \
|
|
||||||
echo "npx electron"; \
|
|
||||||
fi)
|
|
||||||
RUNTIME_ENV = IMGFLOAT_ASSETS_PATH=$(IMGFLOAT_ASSETS_PATH) \
|
RUNTIME_ENV = IMGFLOAT_ASSETS_PATH=$(IMGFLOAT_ASSETS_PATH) \
|
||||||
IMGFLOAT_PREVIEWS_PATH=$(IMGFLOAT_PREVIEWS_PATH) \
|
IMGFLOAT_PREVIEWS_PATH=$(IMGFLOAT_PREVIEWS_PATH) \
|
||||||
IMGFLOAT_GITHUB_OWNER=$(IMGFLOAT_GITHUB_OWNER) \
|
IMGFLOAT_GITHUB_OWNER=$(IMGFLOAT_GITHUB_OWNER) \
|
||||||
@@ -48,20 +42,3 @@ test:
|
|||||||
.PHONY: package
|
.PHONY: package
|
||||||
package:
|
package:
|
||||||
mvn clean package
|
mvn clean package
|
||||||
|
|
||||||
.PHONY: run-client
|
|
||||||
run-client:
|
|
||||||
IMGFLOAT_CHANNELS_URL=http://localhost:8080/channels $(ELECTRON) ./src/main/node/app.js
|
|
||||||
|
|
||||||
.PHONY: run-client-x
|
|
||||||
run-client-x:
|
|
||||||
IMGFLOAT_CHANNELS_URL=http://localhost:8080/channels ./src/main/shell/run-electron-app-in-xorg $(ELECTRON)
|
|
||||||
|
|
||||||
.PHONY: run-client-wl
|
|
||||||
run-client-wl:
|
|
||||||
IMGFLOAT_CHANNELS_URL=http://localhost:8080/channels ./src/main/shell/run-electron-app-in-xorg $(ELECTRON)
|
|
||||||
|
|
||||||
.PHONY: fix
|
|
||||||
fix: node_modules
|
|
||||||
./node_modules/.bin/prettier --write src
|
|
||||||
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,20 +1,4 @@
|
|||||||
<p align="center">
|
# Server
|
||||||
<a href="https://imgfloat.kruhlmann.dev"><img src="src/main/resources/assets/banner.png" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[demo.webm](https://github.com/user-attachments/assets/f154ed72-6e3d-40ed-a111-706f6a916d52)
|
|
||||||
|
|
||||||
Bring your stream to life with a lightweight, real-time overlay system built for Twitch. Upload images once, place and animate them from anywhere, and see your changes live, so your stream visuals look polished.
|
|
||||||
|
|
||||||
# Getting started
|
|
||||||
|
|
||||||
## Streamers
|
|
||||||
|
|
||||||
Visit [imgfloat.kruhlmann.dev](https://imgfloat.kruhlmann.dev) to add your channel admins from the dashboard and download the application for your platform. Run the appliation, enter your twitch channel name and add the window to your OBS scene.
|
|
||||||
|
|
||||||
## Moderators
|
|
||||||
|
|
||||||
Visit [imgfloat.kruhlmann.dev](https://imgfloat.kruhlmann.dev) once your streamer has added you as a channel admin. From there you can upload images, place them on the canvas and animate them in real-time.
|
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|||||||
4301
package-lock.json
generated
4301
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
@@ -1,66 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "imgfloat-electron",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"description": "Electron wrapper for the Imgfloat overlay",
|
|
||||||
"main": "src/main/node/app.js",
|
|
||||||
"build": {
|
|
||||||
"appId": "dev.kruhlmann.imgfloat",
|
|
||||||
"productName": "Imgfloat",
|
|
||||||
"files": [
|
|
||||||
"src/main/node/app.js",
|
|
||||||
"src/main/resources/**"
|
|
||||||
],
|
|
||||||
"publish": {
|
|
||||||
"provider": "github",
|
|
||||||
"owner": "Kruhlmann",
|
|
||||||
"repo": "imgfloat-j"
|
|
||||||
},
|
|
||||||
"asar": false,
|
|
||||||
"directories": {
|
|
||||||
"output": "dist"
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"target": [
|
|
||||||
"AppImage"
|
|
||||||
],
|
|
||||||
"category": "Utility",
|
|
||||||
"icon": "src/main/resources/assets/icon/linux"
|
|
||||||
},
|
|
||||||
"win": {
|
|
||||||
"target": [
|
|
||||||
"nsis"
|
|
||||||
],
|
|
||||||
"icon": "src/main/resources/assets/icon/appicon.ico"
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": true,
|
|
||||||
"perMachine": true,
|
|
||||||
"allowElevation": true,
|
|
||||||
"allowToChangeInstallationDirectory": false,
|
|
||||||
"createDesktopShortcut": true,
|
|
||||||
"createStartMenuShortcut": true,
|
|
||||||
"shortcutName": "Imgfloat",
|
|
||||||
"installerIcon": "src/main/resources/assets/icon/appicon.ico",
|
|
||||||
"uninstallerIcon": "src/main/resources/assets/icon/appicon.ico"
|
|
||||||
},
|
|
||||||
"mac": {
|
|
||||||
"category": "public.app-category.productivity",
|
|
||||||
"icon": "src/main/resources/assets/icon/macos.icns",
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "dmg",
|
|
||||||
"arch": ["arm64"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"electron-updater": "^6.6.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"electron": "^35.7.5",
|
|
||||||
"electron-builder": "^24.13.3",
|
|
||||||
"prettier": "^3.7.4",
|
|
||||||
"prettier-plugin-java": "^2.7.7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
const path = require("node:path");
|
|
||||||
|
|
||||||
const { app, BrowserWindow } = require("electron");
|
|
||||||
const { autoUpdater } = require("electron-updater");
|
|
||||||
|
|
||||||
const initialWindowWidthPx = 960;
|
|
||||||
const initialWindowHeightPx = 640;
|
|
||||||
|
|
||||||
let canvasSizeInterval;
|
|
||||||
function clearCanvasSizeInterval() {
|
|
||||||
if (canvasSizeInterval) {
|
|
||||||
clearInterval(canvasSizeInterval);
|
|
||||||
canvasSizeInterval = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function autoResizeWindow(win, lastSize) {
|
|
||||||
if (win.isDestroyed()) {
|
|
||||||
return lastSize;
|
|
||||||
}
|
|
||||||
const newSize = await win.webContents.executeJavaScript(`(() => {
|
|
||||||
const canvas = document.getElementById('broadcast-canvas');
|
|
||||||
if (!canvas) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
return {
|
|
||||||
width: Math.round(rect.width),
|
|
||||||
height: Math.round(rect.height),
|
|
||||||
};
|
|
||||||
})();`);
|
|
||||||
|
|
||||||
if (!newSize?.width || !newSize?.height) {
|
|
||||||
return lastSize;
|
|
||||||
}
|
|
||||||
if (lastSize.width === newSize.width && lastSize.height === newSize.height) {
|
|
||||||
return lastSize;
|
|
||||||
}
|
|
||||||
console.info(
|
|
||||||
`Window size did not match canvas old: ${lastSize.width}x${lastSize.height} new: ${newSize.width}x${newSize.height}. Resizing.`,
|
|
||||||
);
|
|
||||||
win.setContentSize(newSize.width, newSize.height, false);
|
|
||||||
win.setResizable(false);
|
|
||||||
return newSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPostNavigationLoad(win, url, broadcastRect) {
|
|
||||||
url = url || win.webContents.getURL();
|
|
||||||
let pathname;
|
|
||||||
try {
|
|
||||||
pathname = new URL(url).pathname;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Failed to parse URL: ${url}`, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const isBroadcast = /\/view\/[^/]+\/broadcast\/?$/.test(pathname);
|
|
||||||
|
|
||||||
console.info(`Navigation to ${url} detected. Is broadcast: ${isBroadcast}`);
|
|
||||||
if (isBroadcast) {
|
|
||||||
clearCanvasSizeInterval();
|
|
||||||
console.info("Setting up auto-resize for broadcast window.");
|
|
||||||
canvasSizeInterval = setInterval(() => {
|
|
||||||
autoResizeWindow(win, broadcastRect).then((newSize) => {
|
|
||||||
broadcastRect = newSize;
|
|
||||||
});
|
|
||||||
}, 750);
|
|
||||||
autoResizeWindow(win, broadcastRect).then((newSize) => {
|
|
||||||
broadcastRect = newSize;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
clearCanvasSizeInterval();
|
|
||||||
win.setSize(initialWindowWidthPx, initialWindowHeightPx, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createWindow(version) {
|
|
||||||
const win = new BrowserWindow({
|
|
||||||
width: initialWindowWidthPx,
|
|
||||||
height: initialWindowHeightPx,
|
|
||||||
transparent: true,
|
|
||||||
frame: true,
|
|
||||||
alwaysOnTop: false,
|
|
||||||
icon: path.join(__dirname, "../resources/assets/icon/appicon.ico"),
|
|
||||||
webPreferences: { backgroundThrottling: false },
|
|
||||||
});
|
|
||||||
win.setMenu(null);
|
|
||||||
win.setTitle(`Imgfloat Client v${version}`);
|
|
||||||
|
|
||||||
return win;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
|
||||||
if (process.env.CI) {
|
|
||||||
process.on("uncaughtException", (err) => {
|
|
||||||
console.error("Uncaught exception:", err);
|
|
||||||
app.exit(1);
|
|
||||||
});
|
|
||||||
setTimeout(() => app.quit(), 3000);
|
|
||||||
}
|
|
||||||
autoUpdater.checkForUpdatesAndNotify();
|
|
||||||
|
|
||||||
let broadcastRect = { width: 0, height: 0 };
|
|
||||||
const version = app.getVersion();
|
|
||||||
const win = createWindow(version);
|
|
||||||
win.loadURL(process.env["IMGFLOAT_CHANNELS_URL"] || "https://imgfloat.kruhlmann.dev/channels");
|
|
||||||
win.webContents.on("did-finish-load", () => onPostNavigationLoad(win, undefined, broadcastRect));
|
|
||||||
win.webContents.on("did-navigate", (_, url) => onPostNavigationLoad(win, url, broadcastRect));
|
|
||||||
win.webContents.on("did-navigate-in-page", (_, url) => onPostNavigationLoad(win, url, broadcastRect));
|
|
||||||
win.on("page-title-updated", (e) => e.preventDefault());
|
|
||||||
win.on("closed", clearCanvasSizeInterval);
|
|
||||||
});
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,49 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ELECTRON="$1"
|
|
||||||
APP_ENTRY="src/main/node/app.js"
|
|
||||||
SIZE="1280x800"
|
|
||||||
RUNTIME="${XDG_RUNTIME_DIR:-/tmp}"
|
|
||||||
|
|
||||||
# Find free WAYLAND_DISPLAY
|
|
||||||
for d in 1 2 3 4 5; do
|
|
||||||
if [ ! -S "$RUNTIME/wayland-$d" ]; then
|
|
||||||
WAYLAND_NUM="$d"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
[ -n "${WAYLAND_NUM:-}" ] || {
|
|
||||||
echo "No free WAYLAND_DISPLAY found" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export WAYLAND_DISPLAY="wayland-$WAYLAND_NUM"
|
|
||||||
export XDG_RUNTIME_DIR="$RUNTIME"
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
kill "$ELECTRON_PID" "$WESTON_PID" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
trap cleanup EXIT INT TERM
|
|
||||||
|
|
||||||
weston \
|
|
||||||
--backend=wayland-backend.so \
|
|
||||||
--width="${SIZE%x*}" \
|
|
||||||
--height="${SIZE#*x}" \
|
|
||||||
--xwayland \
|
|
||||||
--socket="$WAYLAND_DISPLAY" &
|
|
||||||
WESTON_PID=$!
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
WAYLAND_DISPLAY="$WAYLAND_DISPLAY" \
|
|
||||||
DISPLAY="" \
|
|
||||||
"${ELECTRON}" "$APP_ENTRY" &
|
|
||||||
ELECTRON_PID=$!
|
|
||||||
|
|
||||||
while :; do
|
|
||||||
kill -0 "$ELECTRON_PID" 2>/dev/null || break
|
|
||||||
kill -0 "$WESTON_PID" 2>/dev/null || break
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ELECTRON="$1"
|
|
||||||
APP_ENTRY="src/main/node/app.js"
|
|
||||||
SCREEN="1280x800"
|
|
||||||
|
|
||||||
for d in 99 98 97 96 95; do
|
|
||||||
if ! xdpyinfo -display ":$d" >/dev/null 2>&1; then
|
|
||||||
DISP="$d"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
[ -n "${DISP:-}" ] || {
|
|
||||||
echo "No free DISPLAY found" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Using DISPLAY=:$DISP"
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
kill "$ELECTRON_PID" "$OPENBOX_PID" "$XEPHYR_PID" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
trap cleanup EXIT INT TERM
|
|
||||||
|
|
||||||
Xephyr ":$DISP" -screen "$SCREEN" -resizeable -ac &
|
|
||||||
XEPHYR_PID=$!
|
|
||||||
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
DISPLAY=":$DISP" openbox &
|
|
||||||
OPENBOX_PID=$!
|
|
||||||
|
|
||||||
sleep 0.5
|
|
||||||
|
|
||||||
DISPLAY=":$DISP" "${ELECTRON}" "$APP_ENTRY" &
|
|
||||||
ELECTRON_PID=$!
|
|
||||||
DISPLAY=":$DISP" xsetroot -solid "#009999"
|
|
||||||
|
|
||||||
while :; do
|
|
||||||
if ! kill -0 "$ELECTRON_PID" 2>/dev/null; then
|
|
||||||
echo "Electron exited — killing Xephyr"
|
|
||||||
kill "$XEPHYR_PID" 2>/dev/null || true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! kill -0 "$XEPHYR_PID" 2>/dev/null; then
|
|
||||||
echo "Xephyr exited — killing Electron"
|
|
||||||
kill "$ELECTRON_PID" 2>/dev/null || true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
|
|
||||||
wait "$ELECTRON_PID" 2>/dev/null || true
|
|
||||||
wait "$XEPHYR_PID" 2>/dev/null || true
|
|
||||||
Reference in New Issue
Block a user