initial release

This commit is contained in:
sguenter 2024-01-03 22:56:14 +01:00
commit d88d61bb3b
127 changed files with 20940 additions and 0 deletions

0
.env.build.testing Normal file
View File

1
.env.development Normal file
View File

@ -0,0 +1 @@
NEXT_PUBLIC_HOST=http://127.0.0.1:3000

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
NEXT_PUBLIC_HOST=https://dmscreen.silas.link
SOCKET_TIMEOUT=10000

3
.env.testing Normal file
View File

@ -0,0 +1,3 @@
SOCKET_TIMEOUT=400
SOCKET_POLLING=1

310
.eslintrc.json Normal file
View File

@ -0,0 +1,310 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"plugin:@typescript-eslint/recommended",
"eslint:recommended",
"plugin:react/recommended",
"airbnb",
"plugin:@typescript-eslint/recommended",
"prettier",
"prettier/prettier",
"plugin:import/typescript"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json",
"tsconfigRootDir": "./"
},
"plugins": [
"react",
"@typescript-eslint",
"react-hooks",
"sort-imports-es6-autofix",
"no-relative-import-paths"
],
"rules": {
"linebreak-style": [
"error",
"unix"
],
"semi": [
"error",
"always"
],
"react/jsx-uses-react": [
"error"
],
"react/jsx-uses-vars": [
"error"
],
"react-hooks/rules-of-hooks": "error",
// Checks rules of Hooks
"react-hooks/exhaustive-deps": [
"warn",
{
"additionalHooks": "(useToolHandler|useOnDemandFrame|useAbort|useSecondaryButtonClicked|useAnimatedSpring|useHotkeyDown|useHotkey|useEvent|useServerUpdate)"
}
],
// Checks effect dependencies
"jsx-a11y/no-static-element-interactions": [
"off"
],
"sort-imports-es6-autofix/sort-imports-es6": [
2,
{
"ignoreCase": false,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": [
"none",
"all",
"multiple",
"single"
]
}
],
"no-relative-import-paths/no-relative-import-paths": [
"error",
{
"allowSameFolder": false,
"rootDir": "src",
"prefix": "@"
}
],
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".tsx"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
],
"no-shadow": "off",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-shadow": [
"error"
],
"lines-between-class-members": [
"warn",
"always",
{
"exceptAfterSingleLine": true
}
],
"react/sort-comp": [
"warn",
{
"order": [
"static-variables",
"instance-variables",
"static-methods",
"lifecycle",
"render",
"/^render.+$/",
"instance-methods",
"everything-else"
]
}
],
"react/destructuring-assignment": [
"error",
"always",
{
"ignoreClassFields": true
}
],
"react/state-in-constructor": [
"error",
"never"
],
"react/no-unknown-property": "off",
"no-bitwise": "off",
"import/no-unresolved": [
"error",
{
"ignore": [
"^mdast$"
]
}
],
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"test/**/*.ts"
]
}
],
"no-return-assign": [
"error",
"except-parens"
],
"react/require-default-props": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_"
}
],
"class-methods-use-this": [
"error",
{
"enforceForClassFields": false
}
],
"no-promise-executor-return": "off",
"no-empty-function": "off",
"no-console": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-explicit-any": "off",
"import/order": "off",
"import/prefer-default-export": "off",
"react/prop-types": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"react/jsx-props-no-spreading": "off",
"react/jsx-boolean-value": "off",
"no-plusplus": "off",
"no-param-reassign": "off",
"default-case": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/anchor-has-content": "off"
},
"settings": {
"react": {
"version": "detect"
},
"import/resolver": {
"typescript": {
"extensions": [
".js",
".jsx",
".ts",
".tsx"
]
},
"node": {
"extensions": [
".js",
".jsx",
".ts",
".tsx"
],
"moduleDirectory": [
"node_modules",
"extension/src/"
]
}
}
},
"overrides": [
{
"files": [
"src/application/{**,*.*}"
],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
"@/plugins/*",
"@/pages/*",
"@dicetable/plugin-shell",
"@dicetable/plugin-shell/*"
]
}
]
}
},
{
"files": [
"src/pages/{**,*.*}"
],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
"@/plugins/*",
"@dicetable/plugin-shell/*"
]
}
]
}
},
{
"files": [
"src/shared/{**,*.*}"
],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
"@/plugins/*",
"@/pages/*",
"@/pluginShell/*"
]
}
]
}
},
{
"files": [
"src/plugins/*/{**,*.*}"
],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
"@/models/*",
"@/pages/*",
"@/definitions/*",
"@/application/*",
"@/pluginShell/*"
]
}
]
}
},
{
"files": [
"src/pluginShell/{**,*.*}"
],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
"@/models/*",
"@/pages/*",
"@/plugins/*",
"@dicetable/plugin-shell/*"
]
}
]
}
}
]
}

37
.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
### ApacheCordova ###
# Apache Cordova generated files and directories
.next/
bin/patchReactNativeDriver.sh
!/plugins
!/plugins/android.json
!/plugins/fetch.json
plugins/*
platforms/*
./tmp
test/drivers/*
/www/*
node_modules/*
/node_modules/*
www/index.js
js/**/*.js
js/**/*.js.map
.env
#idea-ide
/.idea/*
out
.env.local
.eslintcache
tsconfig.tsbuildinfo
public/localforage.js
public/sql-wasm.wasm
/pluginShell/dist/
electron/src/Actions.ts
electron/src/Bridge.ts

2
.npmignore Normal file
View File

@ -0,0 +1,2 @@
# OS X
.DS_Store

1
.npmrc Normal file
View File

@ -0,0 +1 @@
legacy-peer-deps=true

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"printWidth": 120,
"tabWidth": 4,
"singleQuote": true
}

132
CHANGELOG.md Normal file
View File

@ -0,0 +1,132 @@
#### Bugfixes
- Die Userauswahl beim Login ist nur noch sichtbar, wenn es User gibt
- Fog of War wird sofort aufgedeckt bei den Spielern und nicht erst nach einem Bewegen der Karte
- Wenn eine Szene oder Ebene gelöscht wird, werden die betroffenen Tokens ebenfalls aus der Initiative entfernt
- Wenn ein Token per Pfeiltasten nach oben/unten und gleichzeitig zur Seite bewegt wird, bewegt dieser sich wieder
richtig
-
# Version 0.5.3 (29.12.2023)
#### Bugfixes
- Upload von SaveState löscht nicht mehr die Verbindung zwischen Szene und Layer
# Version 0.5.2 (29.12.2023)
### Bufixes
- Upload von SaveStates mit Plugins funktioniert wieder, auch wenn diese bereits installiert sind.
# Version 0.5.0 (27.12.2023)
### Features
- Plugin System
- Visitors können nun als Bearbeiter zu Tokens hinzugefügt werden. Visitors können aber keine Tokens erstellen oder
löschen.
### Bugfixes
- Wenn sich der Server ändert, ist danach die Initiative wieder benutzbar.
- Fog of War ist performanter
- Die Entfernungsmessungmethode wird wieder korrekt gespeichert.
# Version 0.4.0 (9.12.2023)
### Features
- Chat und was dazu gehört
- Rollen von Würfeln durch Chat-Befehle, z.B. `/r 1d20+8`
- Flüstern durch `/w '<name>' <nachricht>` oder `/w gm <nachricht>`
- Markdown-Unterstützung im Chat
- Beyond20-Integration (dicetable.net muss im Plugin hinzugefügt werden)
- Spuranzeige berücksichtigt den aktuellen Zug
- Es kann jetzt eingestellt werden ob neue Nutzer akzeptiert werden und ob für diese automatisch ein Username
genereriert wird
### Feature-Änderungen
- Im Ebenenmenü, um Tokens zu verschieben oder die Ebene zu wechseln werden nur noch Ebenen angezeigt, die der User auch
sieht.
### Bugfixes
- Usernames sind nun unabhängig von Groß/Kleinschreibung identisch.
- Toast-Benachrichtigungen werden nicht mehr von offenen Fenstern überlagert
# Version 0.3.6 (11.11.2023)
#### Bugfixes
- Spuren sind begrenzt auf 50 Stellen und Bewegungsanimationen werden maximal innerhalb von ein paar Sekunden
abgearbeitet
- Der Server fällt nicht mehr aus, wenn ein Spieler eine große Speicherdatei hochlädt
- Der native Browser-Zoom wird nicht mehr aktiviert, wenn man sich in einem Input-Feld befindet und `STRG` & `+`/`-`
drückt
- Status-Icons die nur für den Gamemaster sichtbar sind, werden nicht mehr für andere Spieler in der Initiative
angezeigt
- Die Schriftgröße von gezeichnetem Text muss jetzt mindestens 1 sein
- Fenster wackeln nicht mehr in ihrer Größe
# Version 0.3.4 (3.11.2023)
### Features
- Textfarbe, Rahmenfarbe und Schriftgröße wird gespeichert
### Bugfixes
- Der Changelog wächst nicht mehr über das Modal hinaus
- Beim partiellen Hochladen von Savestates wird wirklich nur noch das benötigte hochgeladen
# Version 0.3.0 (1.11.2023)
### Features
- Touch-Support für Tablets (und Smartphones, wobei Smartphones nicht empfohlen sind. Das Verschieben von Fenstern
funktioniert noch nicht.)
- Text kann "gezeichnet" werden, inklusive Text- & Rahmenfarbe und Schriftgröße.
- Fenster können durch einen Button unten links oder `STRG+r` auf ihre initiale Position zurückgesetzt werden.
- Bugs können nun direkt aus dem VTT heraus gemeldet werden.
### Bugfixes
- Der Color-Picker hat nicht mehr den falschen Kamera-Ausschnitt, wenn während der Auswahl gezoomt oder die Kamera
bewegt wird.
- Ein Bug, der die Dimension eines Objektes nicht verändert, wenn dieses markiert war und ein anderer User das
bearbeitet, wurde behoben.
- Ein Bug, wo ab und zu Hotkeys nicht wieder deaktiviert wurden, wurde behoben.
# Version 0.2.1 (11.10.2023)
### Features
- Das X zum markieren eines besiegten Gegners wird nun in der Initiative groß dargestellt, wenn alle Tokens das X haben.
- Text im VTT ist generell lesbarer geworden.
- Das Bewegen mittels den Pfeiltasten hinterlässt jetzt eine Spur.
- Das Anzeigen der Spuren mittels Tab funktioniert nur noch für aktuell ausgewählte Tokens. Das sorgt für mehr
Übersichtlichkeit.
- Die Spuranzeige ist nun vor der Selektierungsbox.
### Bugfixes
- Name, Lebensanzeige und Statussymbole beachten nun die Transparenz der Ebene
- Die Anzeige wer sonst online ist funktioniert nun auch wieder nachdem man den Spielstand exportiert hat.
- Wenn ein Polygon-Tool ausgewählt ist, wird durch ein Click nicht mehr ein Token selektiert, sondern direkt die
Polygonzeichnung begonnen.
- Die Spur von rotierten Tokens wird nun korrekt dargestellt.
- Name, Lebensanzeige und Statussymbole wandern nun mit, wenn ein Token mit den Pfeiltasten bewegt wird.
- Wenn ein Versions-Fehler auftritt, wird das entsprechende Objekt direkt neu geladen, sodass es nicht erneut vorkommen
sollte.
- Die Animation der Rotation/Skalierung wird nicht mehr erneut ausgelöst, wenn sich etwas anderes an dem Token ändert.
- Lebensanzeigen sollte jetzt für Tokens von 35px und Werte bis 999 nicht mehr aus der Lebensanzeige rauslaufen.
- Die Aktionen zur Initiative im Kontext-Menü sind nur noch möglich, wenn man GM ist oder die Initiative aktiviert ist.
- Bildänderungen an Tokens lassen das Bild nicht mehr verschwinden.
- Fenster lassen sich wieder in der Größe verändern.
- Lebensanzeige und Namen werden nicht mehr von unseleketierten Tokens verdeckt, wenn man den Token markiert hat. Das
ermöglicht die Inline-Bearbeitung.
- Ein Bug, der andere Tokens verschwinden lässt, wenn man einen Token bewegt, wurde behoben.
- Wenn man einen Token von einer Layer auf eine andere verschiebt, wird die Position nicht mehr verschoben.
- Das Kopieren und Einfügen von Tokens kopiert den Token wieder an die Position der Maus.

135
TODO.md Normal file
View File

@ -0,0 +1,135 @@
ToDo:
- [ ] Translation of errors in server
- [ ] Users
- [ ] useSortedUsers include Visitors
- [x] User wechseln funktioniert nicht mehr
- [x] User wechseln übersetzen
- [x] User passwort Flow übersetzen
- [ ] Objekte
- [x] Löschen
- [x] Via entf/backspace
- [x] Via Window
- [x] Via Kontextmenü?
- [x] Locked-Layer beachten
- [x] Unselect nach/bei Löschung
- [ ] Kontextmenü
- [x] Auf Ebene...
- [x] Nach oben
- [x] Nach unten
- [ ] Bei Roll20/dnd.silas.link nach weiteren Actions schauen
- [x] Löschen
- [ ] Zur Initiaitve hinzufügen
- [x] Kamera zentrieren (?) => In ToolWindow hinzugefügt
- [ ] Floating/Quick-Actions (Health, Statussymbole)
- [x] Name
- [x] Healthbar
- [x] Statussymbole
- [ ] Armor-Class-Switch (?)
- [ ] Statussymbole
- [x] DM
- [ ] Bearbeiter (?)
- [x] Für alle
- [x] Multiple Drag and Drop einfügen (nicht alle auf die gleiche Position)
- Aus Dateiexplorer
- [x] LayerEditor
- [x] Layer reorder in window
- [x] New layer button nach oben und ein +
- [x] Vorschau wie bei Objekten bei Änderungen
- [x] Änderungen an Server übertragen
- [x] Renaming von Layern
- [x] Layer reordering via dragAndDrop
- [x] Drag von 2 zu 1 endet mit 1 und 1
- [x] Preview
- [x] Design von Opacity-Range auf Material und Flat Design
- [x] Locking von Layer
- [x] gerade bearbeitete Selektion anpassen
- [x] Anlegen von Objekten
- [x] Bearbeiten von Objekten
- [x] Sichtbarkeit einstellen
- [x] Beachten beim
- [x] Anzeigen von Objekten
- [x] Selektiertes Objekten
- [x] Hinzufügen von Objekten
- [x] Ändern der Sichtbarkeit von Layern
- [x] ActiveLayer change
- [x] Was, wenn keine aktive Layer gesetzt?
- [x] Element einbauen
- [x] Übersetzung
- [x] Löschen von einer Ebene
- [x] Checken, ob Layer wirklich gelöscht oder nur referenz zur Scene entfernt
- [x] order anpassen
- [x] Growing von Name und Sichtbarkeitssliders
- [x] Lagging beim Hinzufügen von Layern
- [x] Layer nur für aktuelle Szene laden
- Scenes
- [x] Aktive Szene im Fenster grau hinterlegen
- Wie bei Layer
- [x] New scene button nach unten und ein +
- [ ] Szene archivieren (?)
- Window-Management
- [ ] Resize to content: Input-Felder beachten
- [ ] Reset-Funktion
- [ ] Initiale Positionen
- [x] translate menu buttons
- [x] title for buttons
- [x] ResizeWindow erlaubt die Fenster aus dem Viewport zu schieben
- [x] Verkleinern bis minimum-größe erreicht
- [x] Wenn bei Resize außerhalb des Viewports, Fenster verschieben
- [x] Resize to content
- [x] Wenn sich Content ändert beachten
- [x] Pin Fenster
- [ ] Settings Window
- [ ] Optisch aufarbeiten
- [ ] Welche Settings machen wirklich Sinn?
- [ ] Tool selection and setting
- [x] RectangleTool
- [ ] PointerTool
- [x] Context-Menu
- [ ] Hotkeys
- [x] Move Map
- [x] PolygonTool
- [ ] Spezieller Modus für Kartenerstellung => Integriert in VTT
- [ ] Polygone nicht Selektierbar machen
- [ ] Wand hinzufügen
- [ ] Bearbeitbar von Wand
- [x] Speichern/Laden
- [ ] In allen Browsern testen
- [ ] Firefox
- [ ] Chrome
- [ ] Brave
- [ ] Safari
- [ ] Edge
- [ ] Fog of War
- [x] Rectangle
- [x] Circle
- [x] Polygon
- [x] Reset
- [x] Add/Remove fog
- [x] Healthbars
- [ ] Chat
- [ ] Schnelle Möglichkeit gleichzeitig mehreren zu schreiben
- [ ] Alles Mögliche als Befehl ausführen
- [ ] Klicken auf Dice-Rollers-Ergebnisse um Leben hinzuzufügen/abzuziehen
- [ ] Dice-Rollers
- [ ] Spielersicht/Simulation
- Anschauen, was der Spieler sieht
- [ ] Einloggen als der Spieler in einem neuen Tab (?)
- [ ] Achievements (vom DM erstellt, die er schnell verteilen kann) (?)
- [ ] Background-Music (?)
- https://www.reddit.com/r/dndnext/comments/11vt5ik/if_youre_looking_for_background_music_i_have_a/
- [ ] ~~Raycaster für Single Selektierung?~~
- Nicht sinnvoll für Layer, locked layers, ect

101
android/.gitignore vendored Normal file
View File

@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml

0
android/app/.gitignore vendored Normal file
View File

0
android/app/build.gradle Normal file
View File

View File

0
android/app/proguard-rules.pro vendored Normal file
View File

View File

0
android/build.gradle Normal file
View File

View File

View File

View File

View File

0
android/gradlew vendored Normal file
View File

0
android/gradlew.bat vendored Normal file
View File

0
android/settings.gradle Normal file
View File

0
android/variables.gradle Normal file
View File

5
bin/export.sh Executable file
View File

@ -0,0 +1,5 @@
mv next.config.js next.base.config.js
mv next.export.config.js next.config.js
next build
mv next.config.js next.export.config.js
mv next.base.config.js next.config.js

13
capacitor.config.ts Normal file
View File

@ -0,0 +1,13 @@
import { CapacitorElectronConfig } from "@capacitor-community/electron";
const config: CapacitorElectronConfig = {
appId: 'link.silas.yt',
appName: 'Easy Youtube Fullscreen',
webDir: 'out',
server: {
androidScheme: 'https'
},
electron: {}
};
export default config;

25
config.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.hellocordova" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>HelloCordova</name>
<description>
A sample Apache Cordova application that responds to the deviceready event.
</description>
<author email="dev@cordova.apache.org" href="http://cordova.io">
Apache Cordova Team
</author>
<content src="index.html" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />
<allow-intent href="sms:*" />
<allow-intent href="mailto:*" />
<allow-intent href="geo:*" />
<platform name="android">
<allow-intent href="market:*" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
</platform>
</widget>

8
electron/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# NPM renames .gitignore to .npmignore
# In order to prevent that, we remove the initial "."
# And the CLI then renames it
app
node_modules
build
dist
logs

BIN
electron/assets/appIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
electron/assets/appIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
electron/assets/splash.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
electron/assets/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,13 @@
import { CapacitorElectronConfig } from "@capacitor-community/electron";
const config: CapacitorElectronConfig = {
appId: 'link.silas.yt',
appName: 'Easy Youtube Fullscreen',
webDir: 'out',
server: {
androidScheme: 'https'
},
electron: {}
};
export default config;

View File

@ -0,0 +1,28 @@
{
"appId": "com.yourdoamnin.yourapp",
"directories": {
"buildResources": "resources"
},
"files": [
"assets/**/*",
"build/**/*",
"capacitor.config.*",
"app/**/*"
],
"publish": {
"provider": "github"
},
"nsis": {
"allowElevation": true,
"oneClick": false,
"allowToChangeInstallationDirectory": true
},
"win": {
"target": "nsis",
"icon": "assets/appIcon.ico"
},
"mac": {
"category": "your.app.category.type",
"target": "dmg"
}
}

75
electron/live-runner.js Normal file
View File

@ -0,0 +1,75 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
const cp = require('child_process');
const chokidar = require('chokidar');
const electron = require('electron');
let child = null;
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const reloadWatcher = {
debouncer: null,
ready: false,
watcher: null,
restarting: false,
};
///*
function runBuild() {
return new Promise((resolve, _reject) => {
let tempChild = cp.spawn(npmCmd, ['run', 'build']);
tempChild.once('exit', () => {
resolve();
});
tempChild.stdout.pipe(process.stdout);
});
}
//*/
async function spawnElectron() {
if (child !== null) {
child.stdin.pause();
child.kill();
child = null;
await runBuild();
}
child = cp.spawn(electron, ['--inspect=5858', './']);
child.on('exit', () => {
if (!reloadWatcher.restarting) {
process.exit(0);
}
});
child.stdout.pipe(process.stdout);
}
function setupReloadWatcher() {
reloadWatcher.watcher = chokidar
.watch('./src/**/*', {
ignored: /[/\\]\./,
persistent: true,
})
.on('ready', () => {
reloadWatcher.ready = true;
})
.on('all', (_event, _path) => {
if (reloadWatcher.ready) {
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = setTimeout(async () => {
console.log('Restarting');
reloadWatcher.restarting = true;
await spawnElectron();
reloadWatcher.restarting = false;
reloadWatcher.ready = false;
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = null;
reloadWatcher.watcher = null;
setupReloadWatcher();
}, 500);
}
});
}
(async () => {
await runBuild();
await spawnElectron();
setupReloadWatcher();
})();

4758
electron/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

41
electron/package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "EasyYoutubeFullscreen",
"version": "1.0.0",
"description": "An Amazing Capacitor App",
"author": {
"name": "",
"email": ""
},
"repository": {
"type": "git",
"url": ""
},
"license": "MIT",
"main": "build/src/index.js",
"scripts": {
"build": "tsc && electron-rebuild",
"electron:start-live": "node ./live-runner.js",
"electron:start": "npm run build && electron --inspect=5858 ./",
"electron:pack": "npm run build && electron-builder build --dir -c ./electron-builder.config.json",
"electron:make": "npm run build && electron-builder build -c ./electron-builder.config.json -p always"
},
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
"chokidar": "~3.5.3",
"electron-is-dev": "~2.0.0",
"electron-serve": "~1.1.0",
"electron-unhandled": "~4.0.1",
"electron-updater": "^5.3.0",
"electron-window-state": "^5.0.3"
},
"devDependencies": {
"electron": "^26.2.2",
"electron-builder": "~23.6.0",
"electron-rebuild": "^3.2.9",
"typescript": "^5.0.4"
},
"keywords": [
"capacitor",
"electron"
]
}

View File

@ -0,0 +1,10 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
const electronPublish = require('electron-publish');
class Publisher extends electronPublish.Publisher {
async upload(task) {
console.log('electron-publisher-custom', task.file);
}
}
module.exports = Publisher;

78
electron/src/index.ts Normal file
View File

@ -0,0 +1,78 @@
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron';
import type { MenuItemConstructorOptions } from 'electron';
import { app, MenuItem } from 'electron';
import electronIsDev from 'electron-is-dev';
import unhandled from 'electron-unhandled';
import { autoUpdater } from 'electron-updater';
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
// Graceful handling of unhandled errors.
unhandled();
// Define our menu templates (these are optional)
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({label: 'Quit App', role: 'quit'})];
const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
{role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu'},
{role: 'viewMenu'},
{
role: "editMenu", submenu: [
{role: 'copy', accelerator: "CmdOrCtrl+C", label: "Copy"},
{role: 'paste', accelerator: 'CmdOrCtrl+V', label: "Paste"},
{role: 'cut', accelerator: 'CmdOrCtrl+X', label: "Cut"},
{role: 'selectAll', accelerator: 'CmdOrCtrl+A', label: "Select All"},
]
}
];
// Get Config options from capacitor.config
const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig();
// Initialize our app. You can pass menu templates into the app here.
// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig);
const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
// If deeplinking is enabled then we will set it up here.
if (capacitorFileConfig.electron?.deepLinkingEnabled) {
setupElectronDeepLinking(myCapacitorApp, {
customProtocol: capacitorFileConfig.electron.deepLinkingCustomProtocol ?? 'mycapacitorapp',
});
}
// If we are in Dev mode, use the file watcher components.
if (electronIsDev) {
setupReloadWatcher(myCapacitorApp);
}
// Run Application
(async () => {
// Wait for electron app to be ready.
await app.whenReady();
// Security - Set Content-Security-Policy based on whether or not we are in dev mode.
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme());
// Initialize our app, build windows, and load content.
await myCapacitorApp.init();
// Check for updates if we are in a packaged app.
autoUpdater.checkForUpdatesAndNotify();
})();
// Handle when all of our windows are close (platforms have their own expectations).
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
// When the dock icon is clicked.
app.on('activate', async function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (myCapacitorApp.getMainWindow().isDestroyed()) {
await myCapacitorApp.init();
}
});
// Place all ipc or other electron api calls and custom functionality under this line

26
electron/src/preload.ts Normal file
View File

@ -0,0 +1,26 @@
import { contextBridge, ipcRenderer } from "electron";
import { Actions } from "./Actions";
import { Bridge } from "./Bridge";
require('./rt/electron-rt');
//////////////////////////////
// User Defined Preload scripts below
console.log('User Preload!');
const preparedActions = Object.entries(Actions).reduce((acc, [key, value]) => {
if (typeof value !== "function") {
acc[key] = value;
} else {
acc[key] = (...args: any[]) => {
ipcRenderer.send("action", {prop: key, args});
}
}
return acc;
}, {} as typeof Actions);
ipcRenderer.on("port", event => {
Bridge.setPort(event.ports[0]);
});
contextBridge.exposeInMainWorld('Actions', preparedActions);
contextBridge.exposeInMainWorld('Bridge', Bridge);

View File

@ -0,0 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
module.exports = {
}

View File

@ -0,0 +1,88 @@
import { randomBytes } from 'crypto';
import { ipcRenderer, contextBridge } from 'electron';
import { EventEmitter } from 'events';
////////////////////////////////////////////////////////
// eslint-disable-next-line @typescript-eslint/no-var-requires
const plugins = require('./electron-plugins');
const randomId = (length = 5) => randomBytes(length).toString('hex');
const contextApi: {
[plugin: string]: { [functionName: string]: () => Promise<any> };
} = {};
Object.keys(plugins).forEach((pluginKey) => {
Object.keys(plugins[pluginKey])
.filter((className) => className !== 'default')
.forEach((classKey) => {
const functionList = Object.getOwnPropertyNames(plugins[pluginKey][classKey].prototype).filter(
(v) => v !== 'constructor'
);
if (!contextApi[classKey]) {
contextApi[classKey] = {};
}
functionList.forEach((functionName) => {
if (!contextApi[classKey][functionName]) {
contextApi[classKey][functionName] = (...args) => ipcRenderer.invoke(`${classKey}-${functionName}`, ...args);
}
});
// Events
if (plugins[pluginKey][classKey].prototype instanceof EventEmitter) {
const listeners: { [key: string]: { type: string; listener: (...args: any[]) => void } } = {};
const listenersOfTypeExist = (type) =>
!!Object.values(listeners).find((listenerObj) => listenerObj.type === type);
Object.assign(contextApi[classKey], {
addListener(type: string, callback: (...args) => void) {
const id = randomId();
// Deduplicate events
if (!listenersOfTypeExist(type)) {
ipcRenderer.send(`event-add-${classKey}`, type);
}
const eventHandler = (_, ...args) => callback(...args);
ipcRenderer.addListener(`event-${classKey}-${type}`, eventHandler);
listeners[id] = { type, listener: eventHandler };
return id;
},
removeListener(id: string) {
if (!listeners[id]) {
throw new Error('Invalid id');
}
const { type, listener } = listeners[id];
ipcRenderer.removeListener(`event-${classKey}-${type}`, listener);
delete listeners[id];
if (!listenersOfTypeExist(type)) {
ipcRenderer.send(`event-remove-${classKey}-${type}`);
}
},
removeAllListeners(type: string) {
Object.entries(listeners).forEach(([id, listenerObj]) => {
if (!type || listenerObj.type === type) {
ipcRenderer.removeListener(`event-${classKey}-${listenerObj.type}`, listenerObj.listener);
ipcRenderer.send(`event-remove-${classKey}-${listenerObj.type}`);
delete listeners[id];
}
});
},
});
}
});
});
contextBridge.exposeInMainWorld('CapacitorCustomPlatform', {
name: 'electron',
plugins: contextApi,
});
////////////////////////////////////////////////////////

264
electron/src/setup.ts Normal file
View File

@ -0,0 +1,264 @@
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
import {
CapacitorSplashScreen,
CapElectronEventEmitter,
setupCapacitorElectronPlugins,
} from '@capacitor-community/electron';
import chokidar from 'chokidar';
import {
app,
BrowserWindow,
ipcMain,
Menu,
MenuItem,
MenuItemConstructorOptions,
MessageChannelMain,
nativeImage,
session,
Tray,
} from 'electron';
import electronIsDev from 'electron-is-dev';
import electronServe from 'electron-serve';
import windowStateKeeper from 'electron-window-state';
import { join } from 'path';
import { Actions } from "./Actions";
// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode.
const reloadWatcher = {
debouncer: null,
ready: false,
watcher: null,
};
export function setupReloadWatcher(electronCapacitorApp: ElectronCapacitorApp): void {
reloadWatcher.watcher = chokidar
.watch(join(app.getAppPath(), 'app'), {
ignored: /[/\\]\./,
persistent: true,
})
.on('ready', () => {
reloadWatcher.ready = true;
})
.on('all', (_event, _path) => {
if (reloadWatcher.ready) {
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = setTimeout(async () => {
electronCapacitorApp.getMainWindow().webContents.reload();
reloadWatcher.ready = false;
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = null;
reloadWatcher.watcher = null;
setupReloadWatcher(electronCapacitorApp);
}, 1500);
}
});
}
// Define our class to manage our app.
export class ElectronCapacitorApp {
private MainWindow: BrowserWindow | null = null;
private SplashScreen: CapacitorSplashScreen | null = null;
private TrayIcon: Tray | null = null;
private CapacitorFileConfig: CapacitorElectronConfig;
private TrayMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
new MenuItem({label: 'Quit App', role: 'quit'}),
];
private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
{role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu'},
{role: 'viewMenu'},
];
private mainWindowState;
private loadWebApp;
private customScheme: string;
private channels = new MessageChannelMain();
constructor(
capacitorFileConfig: CapacitorElectronConfig,
trayMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[],
appMenuBarMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[]
) {
this.CapacitorFileConfig = capacitorFileConfig;
this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron';
if (trayMenuTemplate) {
this.TrayMenuTemplate = trayMenuTemplate;
}
if (appMenuBarMenuTemplate) {
this.AppMenuBarMenuTemplate = appMenuBarMenuTemplate;
}
// Setup our web app loader, this lets us load apps like react, vue, and angular without changing their build chains.
this.loadWebApp = electronServe({
directory: join(app.getAppPath(), 'app'),
scheme: this.customScheme,
});
}
// Expose the mainWindow ref for use outside of the class.
getMainWindow(): BrowserWindow {
return this.MainWindow;
}
getCustomURLScheme(): string {
return this.customScheme;
}
async init(): Promise<void> {
const icon = nativeImage.createFromPath(
join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png')
);
this.mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800,
});
// Setup preload script path and construct our main window.
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js');
this.MainWindow = new BrowserWindow({
icon,
show: false,
x: this.mainWindowState.x,
y: this.mainWindowState.y,
width: this.mainWindowState.width,
height: this.mainWindowState.height,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
// Use preload to inject the electron varriant overrides for capacitor plugins.
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
preload: preloadPath,
},
});
this.mainWindowState.manage(this.MainWindow);
this.MainWindow.webContents.postMessage("port", null, [this.channels.port1]);
Actions.setVideoWindowPort(this.channels.port2);
if (this.CapacitorFileConfig.backgroundColor) {
this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor);
}
// If we close the main window with the splashscreen enabled we need to destory the ref.
this.MainWindow.on('closed', () => {
if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) {
this.SplashScreen.getSplashWindow().close();
}
});
// When the tray icon is enabled, setup the options.
if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) {
this.TrayIcon = new Tray(icon);
this.TrayIcon.on('double-click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.on('click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.setToolTip(app.getName());
this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate));
}
// Setup the main manu bar at the top of our window.
Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate));
// If the splashscreen is enabled, show it first while the main window loads then switch it out for the main window, or just load the main window from the start.
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen = new CapacitorSplashScreen({
imageFilePath: join(
app.getAppPath(),
'assets',
this.CapacitorFileConfig.electron?.splashScreenImageName ?? 'splash.png'
),
windowWidth: 400,
windowHeight: 400,
});
this.SplashScreen.init(this.loadMainWindow, this);
} else {
this.loadMainWindow(this);
}
// Security
this.MainWindow.webContents.setWindowOpenHandler((details) => {
return {
action: 'allow', overrideBrowserWindowOptions: {}
};
});
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => {
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) {
event.preventDefault();
}
});
// Link electron plugins into the system.
setupCapacitorElectronPlugins();
// When the web app is loaded we hide the splashscreen if needed and show the mainwindow.
this.MainWindow.webContents.on('dom-ready', () => {
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen.getSplashWindow().hide();
}
if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) {
this.MainWindow.show();
}
setTimeout(() => {
if (electronIsDev) {
this.MainWindow.webContents.openDevTools();
}
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', '');
}, 400);
});
}
// Helper function to load in the app.
private async loadMainWindow(thisRef: any) {
await thisRef.loadWebApp(thisRef.MainWindow);
}
}
// Set a CSP up for our application based on the custom scheme
export function setupContentSecurityPolicy(customScheme: string): void {
const allowed = `default-src ${customScheme}://* https://* blob: 'unsafe-inline' data:`
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
electronIsDev
? `${allowed} devtools://* 'unsafe-eval'`
: allowed,
],
},
});
});
}
ipcMain.on("action", (event, arg) => {
if (typeof arg === "object" && arg && "prop" in arg && "args" in arg) {
const {prop, args} = arg;
if (prop in Actions && typeof Actions[prop] === "function") {
Actions[prop](...args);
}
}
})
ipcMain.on("redirect", (event, arg) => {
console.log("LOG-d redirect...", event, arg);
ipcMain.emit("redirected", arg);
})
Actions.setIsDev(electronIsDev);

22
electron/tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compileOnSave": true,
"include": [
"./src/**/*",
"./capacitor.config.ts",
"./capacitor.config.js"
],
"compilerOptions": {
"outDir": "./build",
"importHelpers": true,
"target": "ES2017",
"module": "CommonJS",
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"typeRoots": [
"./node_modules/@types"
],
"allowJs": true,
"rootDir": "."
}
}

13
ios/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
App/build
App/Pods
App/output
App/App/public
DerivedData
xcuserdata
# Cordova plugins for Capacitor
capacitor-cordova-ios-plugins
# Generated Config files
App/App/capacitor.config.json
App/App/config.xml

View File

View File

View File

View File

0
ios/App/App/Info.plist Normal file
View File

Some files were not shown because too many files have changed in this diff Show More