mirror of
https://github.com/imgfloat/client.git
synced 2026-05-08 10:39:34 +00:00
Migrate from personal repo
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
res/icon/macos.icns filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/banner.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/128x128.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/16x16.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/256x256.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/appicon.ico filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/32x32.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/48x48.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/512x512.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
res/icon/linux/64x64.png filter=lfs diff=lfs merge=lfs -text
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
name: Electron CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['**']
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- run: npm ci
|
||||||
|
- name: Check version tag
|
||||||
|
id: version_guard
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
- name: Build (no publish)
|
||||||
|
if: github.ref != 'refs/heads/master'
|
||||||
|
run: npx electron-builder --publish=never
|
||||||
|
- name: Build & publish
|
||||||
|
if: github.ref == 'refs/heads/master' && steps.version_guard.outputs.exists == 'false'
|
||||||
|
run: npx electron-builder --publish=always
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Publish skipped
|
||||||
|
if: github.ref == 'refs/heads/master' && steps.version_guard.outputs.exists == 'true'
|
||||||
|
run: echo "Version already released — skipping publish"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/target
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
*.iml
|
||||||
|
local/
|
||||||
|
*.log
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-java"],
|
||||||
|
"printWidth":120,
|
||||||
|
"tabWidth":4,
|
||||||
|
"useTabs":false,
|
||||||
|
"semi":true,
|
||||||
|
"singleQuote":false,
|
||||||
|
"trailingComma":"all",
|
||||||
|
"bracketSpacing":true,
|
||||||
|
"arrowParens":"always",
|
||||||
|
"requirePragma":false,
|
||||||
|
"insertPragma":false,
|
||||||
|
"proseWrap":"always"
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
.ONESHELL:
|
||||||
|
.POSIX:
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := run
|
||||||
|
|
||||||
|
ELECTRON := $(shell \
|
||||||
|
if [ -f /etc/os-release ] && grep -q '^ID=nixos' /etc/os-release; then \
|
||||||
|
echo electron; \
|
||||||
|
else \
|
||||||
|
echo "npx electron"; \
|
||||||
|
fi)
|
||||||
|
|
||||||
|
node_modules: package-lock.json
|
||||||
|
npm install
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run:
|
||||||
|
$(ELECTRON) src/main.js
|
||||||
|
|
||||||
|
.PHONY: run-x
|
||||||
|
run-x:
|
||||||
|
./util/run-xorg $(ELECTRON)
|
||||||
|
|
||||||
|
.PHONY: run-wl
|
||||||
|
run-wl:
|
||||||
|
./util/run-wl $(ELECTRON)
|
||||||
|
|
||||||
|
.PHONY: fix
|
||||||
|
fix: node_modules
|
||||||
|
./node_modules/.bin/prettier --write src
|
||||||
|
|
||||||
Generated
+4303
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "imgfloat-client",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "Electron wrapper for the Imgfloat overlay",
|
||||||
|
"main": "src/main.js",
|
||||||
|
"build": {
|
||||||
|
"appId": "dev.kruhlmann.imgfloat",
|
||||||
|
"productName": "Imgfloat",
|
||||||
|
"files": [
|
||||||
|
"src/main.js",
|
||||||
|
"res/**"
|
||||||
|
],
|
||||||
|
"publish": {
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "Kruhlmann",
|
||||||
|
"repo": "imgfloat-j"
|
||||||
|
},
|
||||||
|
"asar": false,
|
||||||
|
"directories": {
|
||||||
|
"output": "dist"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
"AppImage"
|
||||||
|
],
|
||||||
|
"category": "Utility",
|
||||||
|
"icon": "res/icon/linux"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"nsis"
|
||||||
|
],
|
||||||
|
"icon": "res/icon/appicon.ico"
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": true,
|
||||||
|
"perMachine": true,
|
||||||
|
"allowElevation": true,
|
||||||
|
"allowToChangeInstallationDirectory": false,
|
||||||
|
"createDesktopShortcut": true,
|
||||||
|
"createStartMenuShortcut": true,
|
||||||
|
"shortcutName": "Imgfloat",
|
||||||
|
"installerIcon": "res/icon/appicon.ico",
|
||||||
|
"uninstallerIcon": "res/icon/appicon.ico"
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity",
|
||||||
|
"icon": "res/icon/macos.icns",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "dmg",
|
||||||
|
"arch": [
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"electron-updater": "^6.7.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron": "^35.7.5",
|
||||||
|
"electron-builder": "^24.13.3",
|
||||||
|
"prettier": "^3.7.4",
|
||||||
|
"prettier-plugin-java": "^2.7.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,16 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
pkgs.electron
|
||||||
|
pkgs.mesa
|
||||||
|
pkgs.nodejs
|
||||||
|
pkgs.openbox
|
||||||
|
pkgs.vulkan-loader
|
||||||
|
pkgs.wayland
|
||||||
|
pkgs.wayland-protocols
|
||||||
|
pkgs.weston
|
||||||
|
pkgs.xorg.xorgserver
|
||||||
|
pkgs.xwayland
|
||||||
|
];
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
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,
|
||||||
|
backgroundColor: "#00000000",
|
||||||
|
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);
|
||||||
|
});
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ELECTRON="$1"
|
||||||
|
APP_ENTRY="src/main.js"
|
||||||
|
SIZE="1280x800"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
kill "$ELECTRON_PID" "$WESTON_PID" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
weston \
|
||||||
|
--backend=x11-backend.so \
|
||||||
|
--width="${SIZE%x*}" \
|
||||||
|
--height="${SIZE#*x}" \
|
||||||
|
--xwayland &
|
||||||
|
WESTON_PID=$!
|
||||||
|
|
||||||
|
# Wait for Weston to create its socket
|
||||||
|
for i in $(seq 1 50); do
|
||||||
|
SOCKET=$(ls "$XDG_RUNTIME_DIR"/wayland-* 2>/dev/null | head -n1 || true)
|
||||||
|
[ -n "$SOCKET" ] && break
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "${SOCKET:-}" ] || exit 1
|
||||||
|
|
||||||
|
export WAYLAND_DISPLAY="$(basename "$SOCKET")"
|
||||||
|
export ELECTRON_OZONE_PLATFORM_HINT=wayland
|
||||||
|
export NIXOS_OZONE_WL=1
|
||||||
|
|
||||||
|
"$ELECTRON" "$APP_ENTRY" &
|
||||||
|
ELECTRON_PID=$!
|
||||||
|
|
||||||
|
wait
|
||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ELECTRON="$1"
|
||||||
|
APP_ENTRY="src/main.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