chore: auto-commit after execute-task
GSD-Unit: M013/S01/T01
This commit is contained in:
parent
87a84eb0ac
commit
41878e92ef
10 changed files with 14904 additions and 16 deletions
24
.gitignore
vendored
24
.gitignore
vendored
|
|
@ -38,3 +38,27 @@ Thumbs.db
|
||||||
# Misc
|
# Misc
|
||||||
*.bak
|
*.bak
|
||||||
*.orig
|
*.orig
|
||||||
|
|
||||||
|
# ── GSD baseline (auto-generated) ──
|
||||||
|
.gsd
|
||||||
|
.gsd-id
|
||||||
|
.mcp.json
|
||||||
|
.bg-shell/
|
||||||
|
*~
|
||||||
|
*.code-workspace
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
target/
|
||||||
|
vendor/
|
||||||
|
*.log
|
||||||
|
coverage/
|
||||||
|
.cache/
|
||||||
|
tmp/
|
||||||
|
|
|
||||||
214
build.sh
Executable file
214
build.sh
Executable file
|
|
@ -0,0 +1,214 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="${1:-1.0.0}"
|
||||||
|
PRODUCT="schneespur"
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
SOURCE_DIR="${PROJECT_DIR}/${PRODUCT}"
|
||||||
|
BUILD_DIR="${PROJECT_DIR}/release/${PRODUCT}-${VERSION}"
|
||||||
|
ZIP_FILE="${PROJECT_DIR}/release/${PRODUCT}-${VERSION}.zip"
|
||||||
|
|
||||||
|
echo "════════════════════════════════════════════"
|
||||||
|
echo " Building ${PRODUCT} v${VERSION}"
|
||||||
|
echo "════════════════════════════════════════════"
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
# ── Clean previous build ──
|
||||||
|
rm -rf "$BUILD_DIR" "$ZIP_FILE"
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
|
# ── 1. Frontend build ──
|
||||||
|
echo ""
|
||||||
|
echo "▸ Installing npm dependencies..."
|
||||||
|
(cd "$SOURCE_DIR" && (npm ci --silent 2>/dev/null || npm install --silent))
|
||||||
|
|
||||||
|
echo "▸ Building frontend assets..."
|
||||||
|
(cd "$SOURCE_DIR" && npm run build)
|
||||||
|
|
||||||
|
# ── 2. Composer production install ──
|
||||||
|
echo ""
|
||||||
|
echo "▸ Installing composer dependencies (production)..."
|
||||||
|
(cd "$SOURCE_DIR" && composer install --no-dev --optimize-autoloader --no-interaction --quiet)
|
||||||
|
|
||||||
|
# ── 3. Copy files ──
|
||||||
|
echo ""
|
||||||
|
echo "▸ Copying project files..."
|
||||||
|
|
||||||
|
# Core Laravel → build root (flat layout, like 1.0.0)
|
||||||
|
cp "$SOURCE_DIR/artisan" "$BUILD_DIR/"
|
||||||
|
cp "$SOURCE_DIR/composer.json" "$BUILD_DIR/"
|
||||||
|
cp "$SOURCE_DIR/composer.lock" "$BUILD_DIR/"
|
||||||
|
cp "$SOURCE_DIR/.env.example" "$BUILD_DIR/"
|
||||||
|
cp "$SOURCE_DIR/.editorconfig" "$BUILD_DIR/" 2>/dev/null || true
|
||||||
|
cp "$SOURCE_DIR/.htaccess" "$BUILD_DIR/" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Application code → build root
|
||||||
|
cp -r "$SOURCE_DIR/app" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/bootstrap" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/config" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/database" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/lang" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/public" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/resources" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/routes" "$BUILD_DIR/"
|
||||||
|
cp -r "$SOURCE_DIR/vendor" "$BUILD_DIR/"
|
||||||
|
|
||||||
|
# Modules directory (example module for reference)
|
||||||
|
if [ -d "$SOURCE_DIR/modules" ]; then
|
||||||
|
cp -r "$SOURCE_DIR/modules" "$BUILD_DIR/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Documentation and legal → build root (flat, alongside code)
|
||||||
|
cp "$PROJECT_DIR/README.md" "$BUILD_DIR/" 2>/dev/null || true
|
||||||
|
cp "$PROJECT_DIR/LICENSE" "$BUILD_DIR/" 2>/dev/null || true
|
||||||
|
cp "$PROJECT_DIR/INSTALL.de.md" "$BUILD_DIR/" 2>/dev/null || true
|
||||||
|
cp "$PROJECT_DIR/INSTALL.en.md" "$BUILD_DIR/" 2>/dev/null || true
|
||||||
|
|
||||||
|
# ── 4. Prepare storage structure (empty, writable) ──
|
||||||
|
echo "▸ Preparing storage structure..."
|
||||||
|
rm -rf "$BUILD_DIR/storage"
|
||||||
|
mkdir -p "$BUILD_DIR/storage/app/private"
|
||||||
|
mkdir -p "$BUILD_DIR/storage/app/public"
|
||||||
|
mkdir -p "$BUILD_DIR/storage/framework/cache/data"
|
||||||
|
mkdir -p "$BUILD_DIR/storage/framework/sessions"
|
||||||
|
mkdir -p "$BUILD_DIR/storage/framework/testing"
|
||||||
|
mkdir -p "$BUILD_DIR/storage/framework/views"
|
||||||
|
mkdir -p "$BUILD_DIR/storage/logs"
|
||||||
|
|
||||||
|
for dir in "$BUILD_DIR/storage/app/private" \
|
||||||
|
"$BUILD_DIR/storage/app/public" \
|
||||||
|
"$BUILD_DIR/storage/framework/cache/data" \
|
||||||
|
"$BUILD_DIR/storage/framework/sessions" \
|
||||||
|
"$BUILD_DIR/storage/framework/testing" \
|
||||||
|
"$BUILD_DIR/storage/framework/views" \
|
||||||
|
"$BUILD_DIR/storage/logs"; do
|
||||||
|
touch "$dir/.gitkeep"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── 5. Remove dev/unnecessary files ──
|
||||||
|
echo "▸ Cleaning up dev files..."
|
||||||
|
|
||||||
|
# Remove installed.lock (fresh install!)
|
||||||
|
rm -f "$BUILD_DIR/storage/app/installed.lock"
|
||||||
|
|
||||||
|
# Remove public/storage symlink (installer creates it)
|
||||||
|
rm -f "$BUILD_DIR/public/storage"
|
||||||
|
|
||||||
|
# Remove bootstrap cache (regenerated on first run)
|
||||||
|
rm -f "$BUILD_DIR/bootstrap/cache/"*.php 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove test files
|
||||||
|
rm -rf "$BUILD_DIR/tests"
|
||||||
|
|
||||||
|
# Remove dev config/tooling
|
||||||
|
rm -f "$BUILD_DIR/phpunit.xml"
|
||||||
|
rm -f "$BUILD_DIR/.styleci.yml"
|
||||||
|
rm -f "$BUILD_DIR/.gitignore"
|
||||||
|
rm -f "$BUILD_DIR/.gitattributes"
|
||||||
|
rm -f "$BUILD_DIR/package.json"
|
||||||
|
rm -f "$BUILD_DIR/package-lock.json"
|
||||||
|
rm -f "$BUILD_DIR/vite.config.js"
|
||||||
|
|
||||||
|
# Remove GSD/AI/editor artifacts
|
||||||
|
rm -rf "$BUILD_DIR/.gsd"
|
||||||
|
rm -f "$BUILD_DIR/.gsd-id"
|
||||||
|
rm -f "$BUILD_DIR/CLAUDE.md"
|
||||||
|
rm -f "$BUILD_DIR/gpt.md"
|
||||||
|
rm -f "$BUILD_DIR/module.md"
|
||||||
|
rm -f "$BUILD_DIR/site.md"
|
||||||
|
rm -f "$BUILD_DIR/.mcp.json"
|
||||||
|
|
||||||
|
# Remove test/scratch files
|
||||||
|
rm -f "$BUILD_DIR/test.txt"
|
||||||
|
rm -f "$BUILD_DIR/test-file.txt"
|
||||||
|
rm -f "$BUILD_DIR/app/test.php"
|
||||||
|
|
||||||
|
# Remove database factories/seeders (not needed in production)
|
||||||
|
rm -rf "$BUILD_DIR/database/factories"
|
||||||
|
rm -rf "$BUILD_DIR/database/seeders"
|
||||||
|
|
||||||
|
# ── 6. Slim vendor directory ──
|
||||||
|
echo "▸ Slimming vendor directory..."
|
||||||
|
|
||||||
|
# Remove .git directories from vendor packages (source installs)
|
||||||
|
find "$BUILD_DIR/vendor" -type d -name ".git" -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove tests, docs, and other non-runtime directories
|
||||||
|
find "$BUILD_DIR/vendor" -type d \( \
|
||||||
|
-name "tests" -o \
|
||||||
|
-name "Tests" -o \
|
||||||
|
-name "test" -o \
|
||||||
|
-name "Test" -o \
|
||||||
|
-name "test_files" -o \
|
||||||
|
-name "docs" -o \
|
||||||
|
-name "doc" -o \
|
||||||
|
-name "examples" -o \
|
||||||
|
-name "example" -o \
|
||||||
|
-name ".github" \
|
||||||
|
\) -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove non-runtime files
|
||||||
|
find "$BUILD_DIR/vendor" -type f \( \
|
||||||
|
-name "*.md" -o \
|
||||||
|
-name "*.markdown" -o \
|
||||||
|
-name "CHANGELOG*" -o \
|
||||||
|
-name "CHANGE_LOG*" -o \
|
||||||
|
-name "CHANGES*" -o \
|
||||||
|
-name "UPGRADING*" -o \
|
||||||
|
-name "UPGRADE*" -o \
|
||||||
|
-name "SECURITY*" -o \
|
||||||
|
-name "CONTRIBUTING*" -o \
|
||||||
|
-name "CODE_OF_CONDUCT*" -o \
|
||||||
|
-name ".editorconfig" -o \
|
||||||
|
-name ".gitignore" -o \
|
||||||
|
-name ".gitattributes" -o \
|
||||||
|
-name ".php-cs-fixer*" -o \
|
||||||
|
-name ".php_cs*" -o \
|
||||||
|
-name "phpunit.xml*" -o \
|
||||||
|
-name "phpstan*" -o \
|
||||||
|
-name ".styleci.yml" -o \
|
||||||
|
-name ".travis.yml" -o \
|
||||||
|
-name "Makefile" -o \
|
||||||
|
-name "Dockerfile" -o \
|
||||||
|
-name "docker-compose*" \
|
||||||
|
\) -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
# ── 7. Write version file ──
|
||||||
|
echo "▸ Writing version file..."
|
||||||
|
cat > "$BUILD_DIR/VERSION" << EOF
|
||||||
|
${VERSION}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ── 8. Write initial update state (prevents self-update to same version) ──
|
||||||
|
echo "▸ Writing initial update state..."
|
||||||
|
cat > "$BUILD_DIR/storage/app/schneespur_update_state.json" << EOF
|
||||||
|
{"current_version":"${VERSION}","last_counter":1}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ── 9. Create ZIP ──
|
||||||
|
echo ""
|
||||||
|
echo "▸ Creating ZIP archive..."
|
||||||
|
rm -f "${ZIP_FILE}.filepart"
|
||||||
|
(cd "${BUILD_DIR}" && zip -r -q "${ZIP_FILE}" .)
|
||||||
|
|
||||||
|
# ── 10. Summary ──
|
||||||
|
ZIP_SIZE=$(du -h "$ZIP_FILE" | cut -f1)
|
||||||
|
FILE_COUNT=$(find "$BUILD_DIR" -type f | wc -l)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "════════════════════════════════════════════"
|
||||||
|
echo " ✓ Build complete!"
|
||||||
|
echo ""
|
||||||
|
echo " Version: ${VERSION}"
|
||||||
|
echo " Files: ${FILE_COUNT}"
|
||||||
|
echo " ZIP: ${ZIP_FILE} (${ZIP_SIZE})"
|
||||||
|
echo "════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
echo " The ZIP is ready for distribution."
|
||||||
|
echo " Users: unzip → FTP upload → open in browser → installer starts"
|
||||||
|
|
||||||
|
# ── 11. Restore dev dependencies ──
|
||||||
|
echo ""
|
||||||
|
echo "▸ Restoring dev composer dependencies..."
|
||||||
|
(cd "$SOURCE_DIR" && composer install --no-interaction --quiet)
|
||||||
66
moduldoku.md
66
moduldoku.md
|
|
@ -167,7 +167,9 @@ class MeinModulServiceProvider extends ServiceProvider
|
||||||
slug: 'mein-modul',
|
slug: 'mein-modul',
|
||||||
label: 'Mein Modul',
|
label: 'Mein Modul',
|
||||||
route: 'admin.mein-modul.settings',
|
route: 'admin.mein-modul.settings',
|
||||||
icon: 'heroicon-o-puzzle-piece',
|
// Wichtig: ROHE SVG-Path-Geometrie (`d=...`), KEIN Heroicon-Name.
|
||||||
|
// Mehrere Pfade mit `||` trennen. Siehe Abschnitt "Navigation" unten.
|
||||||
|
icon: 'M2.25 12l8.954-8.955a1.126 1.126 0 011.591 0L21.75 12...',
|
||||||
order: 200,
|
order: 200,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +197,9 @@ class MeinModulServiceProvider extends ServiceProvider
|
||||||
|
|
||||||
protected function registerRoutes(): void
|
protected function registerRoutes(): void
|
||||||
{
|
{
|
||||||
Route::middleware(['web', 'auth'])
|
// Für Admin-Routen: `'admin'`-Alias (EnsureAdmin) ist Pflicht.
|
||||||
|
// Sonst kann jeder eingeloggte Nutzer (auch Fahrer/Kunden) die Seite öffnen.
|
||||||
|
Route::middleware(['web', 'auth', 'admin'])
|
||||||
->prefix('admin/mein-modul')
|
->prefix('admin/mein-modul')
|
||||||
->name('admin.mein-modul.')
|
->name('admin.mein-modul.')
|
||||||
->group(function () {
|
->group(function () {
|
||||||
|
|
@ -223,7 +227,9 @@ $nav->addItem(
|
||||||
slug: 'mein-modul', // Eindeutiger Bezeichner
|
slug: 'mein-modul', // Eindeutiger Bezeichner
|
||||||
label: 'Mein Modul', // Anzeige-Label
|
label: 'Mein Modul', // Anzeige-Label
|
||||||
route: 'admin.mein-modul.settings', // Laravel-Route-Name
|
route: 'admin.mein-modul.settings', // Laravel-Route-Name
|
||||||
icon: 'heroicon-o-cog-6-tooth', // Heroicon-Bezeichner
|
icon: 'M2.25 12l8.954-8.955...', // ROHE SVG-Path-Geometrie (`d=...`),
|
||||||
|
// KEIN Heroicon-Name. Mehrere Pfade mit `||` trennen.
|
||||||
|
// Beispiele: AppServiceProvider.php der Schneespur-Core-Items.
|
||||||
order: 200, // Sortierung (höher = weiter unten)
|
order: 200, // Sortierung (höher = weiter unten)
|
||||||
permission: null, // Optional: Berechtigungsprüfung
|
permission: null, // Optional: Berechtigungsprüfung
|
||||||
routeCheck: null, // Optional: Route-Existenzprüfung
|
routeCheck: null, // Optional: Route-Existenzprüfung
|
||||||
|
|
@ -300,18 +306,31 @@ $widgets->registerWidget('mein-widget', [
|
||||||
|
|
||||||
**Blade-View des Widgets:**
|
**Blade-View des Widgets:**
|
||||||
|
|
||||||
Das View erhält die Daten aus `dataCallback` als `$data`-Variable:
|
Das View erhält das gesamte Widget-Config-Array als `$widget`-Variable.
|
||||||
|
Die Daten aus `dataCallback` liegen unter `$widget['data']`. **Achtung:**
|
||||||
|
nicht `$data` — diese Variable existiert im Widget-Render-Kontext nicht.
|
||||||
|
|
||||||
```blade
|
```blade
|
||||||
{{-- resources/views/widgets/status-card.blade.php --}}
|
{{-- resources/views/widgets/status-card.blade.php --}}
|
||||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
|
||||||
<h3 class="text-sm font-medium text-gray-900">Mein Widget</h3>
|
<h3 class="text-sm font-medium text-gray-900">Mein Widget</h3>
|
||||||
@if(isset($data['anzahl']))
|
@if(isset($widget['data']['anzahl']))
|
||||||
<p class="text-2xl font-bold">{{ $data['anzahl'] }}</p>
|
<p class="text-2xl font-bold">{{ $widget['data']['anzahl'] }}</p>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Bei häufigem Zugriff lohnt sich ein lokaler Alias:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
@php $data = $widget['data'] ?? []; @endphp
|
||||||
|
{{-- ab hier wie gewohnt $data['...'] verwenden --}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Schneespurs eigene Dashboard-Widgets (`resources/views/admin/dashboard/widgets/*.blade.php`)
|
||||||
|
greifen einheitlich über `$widget['data']` zu — guter Referenzort für
|
||||||
|
Beispiele.
|
||||||
|
|
||||||
### 3. Wetter-Provider (WeatherProviderRegistry)
|
### 3. Wetter-Provider (WeatherProviderRegistry)
|
||||||
|
|
||||||
Module können eigene Wetterdatenquellen registrieren.
|
Module können eigene Wetterdatenquellen registrieren.
|
||||||
|
|
@ -452,8 +471,9 @@ use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
protected function registerRoutes(): void
|
protected function registerRoutes(): void
|
||||||
{
|
{
|
||||||
// Admin-Bereich (authentifiziert)
|
// Admin-Bereich: `'admin'`-Alias (EnsureAdmin) ist Pflicht, sonst können
|
||||||
Route::middleware(['web', 'auth'])
|
// auch Fahrer und Kunden mit Portal-Login die Route aufrufen.
|
||||||
|
Route::middleware(['web', 'auth', 'admin'])
|
||||||
->prefix('admin/mein-modul')
|
->prefix('admin/mein-modul')
|
||||||
->name('admin.mein-modul.')
|
->name('admin.mein-modul.')
|
||||||
->group(function () {
|
->group(function () {
|
||||||
|
|
@ -469,11 +489,16 @@ protected function registerRoutes(): void
|
||||||
|
|
||||||
| Kontext | Middleware | Prefix |
|
| Kontext | Middleware | Prefix |
|
||||||
|---------|-----------|--------|
|
|---------|-----------|--------|
|
||||||
| Admin-Seiten | `['web', 'auth']` | `admin/mein-modul` |
|
| Admin-Seiten | `['web', 'auth', 'admin']` | `admin/mein-modul` |
|
||||||
| API-Endpoints | `['api']` | `api/mein-modul` |
|
| API-Endpoints | `['api']` | `api/mein-modul` |
|
||||||
| Öffentlich | `['web']` | Nach Bedarf |
|
| Öffentlich | `['web']` | Nach Bedarf |
|
||||||
|
|
||||||
**Empfehlung:** Für Admin-Routen immer `auth`-Middleware verwenden. Route-Namen mit `admin.mein-modul.` prefixen, damit die Navigation korrekt markiert wird.
|
**Empfehlung:** Für Admin-Routen **immer** `'admin'`-Alias zusätzlich zu `'auth'`
|
||||||
|
verwenden. `'auth'` allein lässt jeden eingeloggten Nutzer durch — auch
|
||||||
|
Fahrer und Kunden mit Portal-Login. Der Alias `'admin'` ist in
|
||||||
|
`bootstrap/app.php` auf `App\Http\Middleware\EnsureAdmin::class` gemappt.
|
||||||
|
Route-Namen mit `admin.mein-modul.` prefixen, damit die Navigation
|
||||||
|
korrekt markiert wird.
|
||||||
|
|
||||||
### 6. Views laden
|
### 6. Views laden
|
||||||
|
|
||||||
|
|
@ -490,10 +515,25 @@ return view('mein-modul::settings', ['key' => 'value']);
|
||||||
'view' => 'mein-modul::widgets.status-card',
|
'view' => 'mein-modul::widgets.status-card',
|
||||||
```
|
```
|
||||||
|
|
||||||
Der View-Namespace (`mein-modul`) isoliert die Templates vom Rest der Anwendung. In Blade-Templates können alle Schneespur-Layouts und Components verwendet werden:
|
Der View-Namespace (`mein-modul`) isoliert die Templates vom Rest der Anwendung. In Blade-Templates können alle Schneespur-Layouts und Components verwendet werden.
|
||||||
|
|
||||||
|
**Wichtig — richtiges Layout wählen:**
|
||||||
|
|
||||||
|
| Layout | Wann | Liefert |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `<x-admin-layout>` | Admin-Seiten (alles unter `/admin/...`) | Admin-Sidebar, Top-Bar, Schneespur-Chrome |
|
||||||
|
| `<x-app-layout>` | Allgemeine eingeloggte Seiten (Breeze-Standard) | Nur Top-Bar, **ohne** Admin-Sidebar |
|
||||||
|
| `<x-driver-layout>` | Fahrer-Bereich | Fahrer-Chrome |
|
||||||
|
| `<x-portal-layout>` | Kunden-Portal | Kunden-Chrome |
|
||||||
|
|
||||||
|
Für ein **Admin-Modul** ist `<x-admin-layout>` praktisch immer richtig.
|
||||||
|
`<x-app-layout>` zu nehmen ist ein häufiger Stolperfall — die Seite
|
||||||
|
rendert ohne Sidebar, sieht aus als wäre sie aus dem Admin-Bereich
|
||||||
|
herausgefallen.
|
||||||
|
|
||||||
```blade
|
```blade
|
||||||
<x-app-layout>
|
{{-- Admin-Seite: <x-admin-layout> — Slot-Signaturen sind identisch zu app-layout --}}
|
||||||
|
<x-admin-layout>
|
||||||
<x-slot name="header">
|
<x-slot name="header">
|
||||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||||
Mein Modul — Einstellungen
|
Mein Modul — Einstellungen
|
||||||
|
|
@ -505,7 +545,7 @@ Der View-Namespace (`mein-modul`) isoliert die Templates vom Rest der Anwendung.
|
||||||
{{-- Modul-Inhalte --}}
|
{{-- Modul-Inhalte --}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-app-layout>
|
</x-admin-layout>
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
7283
package-lock.json
generated
Normal file
7283
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -15,6 +15,7 @@ use App\Services\Diagnostic\DiagnosticManager;
|
||||||
use App\Services\Diagnostic\DiagnosticPayloadSanitizer;
|
use App\Services\Diagnostic\DiagnosticPayloadSanitizer;
|
||||||
use App\Services\Diagnostic\DiagnosticReporterRegistry;
|
use App\Services\Diagnostic\DiagnosticReporterRegistry;
|
||||||
use App\Services\Extension\DashboardWidgetRegistry;
|
use App\Services\Extension\DashboardWidgetRegistry;
|
||||||
|
use App\Services\Extension\FilterRegistry;
|
||||||
use App\Services\Extension\NavigationRegistry;
|
use App\Services\Extension\NavigationRegistry;
|
||||||
use App\Services\ForecastService;
|
use App\Services\ForecastService;
|
||||||
use App\Services\ModuleManager;
|
use App\Services\ModuleManager;
|
||||||
|
|
@ -46,6 +47,7 @@ class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
$this->app->singleton(AlertService::class);
|
$this->app->singleton(AlertService::class);
|
||||||
$this->app->singleton(DashboardWidgetRegistry::class);
|
$this->app->singleton(DashboardWidgetRegistry::class);
|
||||||
|
$this->app->singleton(FilterRegistry::class);
|
||||||
$this->app->singleton(NavigationRegistry::class);
|
$this->app->singleton(NavigationRegistry::class);
|
||||||
$this->app->singleton(DiagnosticPayloadSanitizer::class);
|
$this->app->singleton(DiagnosticPayloadSanitizer::class);
|
||||||
$this->app->singleton(DiagnosticReporterRegistry::class, fn ($app) => new DiagnosticReporterRegistry($app));
|
$this->app->singleton(DiagnosticReporterRegistry::class, fn ($app) => new DiagnosticReporterRegistry($app));
|
||||||
|
|
|
||||||
43
schneespur/app/Services/Extension/FilterRegistry.php
Normal file
43
schneespur/app/Services/Extension/FilterRegistry.php
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Extension;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class FilterRegistry
|
||||||
|
{
|
||||||
|
private array $hooks = [];
|
||||||
|
|
||||||
|
private int $insertionCounter = 0;
|
||||||
|
|
||||||
|
public function register(string $hook, callable $callback, int $priority = 100): void
|
||||||
|
{
|
||||||
|
$this->hooks[$hook][] = [$priority, $this->insertionCounter++, $callback];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply(string $hook, mixed $value, mixed ...$context): mixed
|
||||||
|
{
|
||||||
|
if (empty($this->hooks[$hook])) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$callbacks = $this->hooks[$hook];
|
||||||
|
usort($callbacks, fn (array $a, array $b) => $a[0] <=> $b[0] ?: $a[1] <=> $b[1]);
|
||||||
|
|
||||||
|
foreach ($callbacks as $entry) {
|
||||||
|
$previousValue = $value;
|
||||||
|
try {
|
||||||
|
$value = $entry[2]($value, ...$context);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('FilterRegistry: callback failed', [
|
||||||
|
'hook' => $hook,
|
||||||
|
'index' => $entry[1],
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
$value = $previousValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
7283
schneespur/package-lock.json
generated
Normal file
7283
schneespur/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -12,7 +12,7 @@
|
||||||
"src": "node_modules/leaflet/dist/images/marker-icon.png"
|
"src": "node_modules/leaflet/dist/images/marker-icon.png"
|
||||||
},
|
},
|
||||||
"resources/css/app.css": {
|
"resources/css/app.css": {
|
||||||
"file": "assets/app-DTM5xC6O.css",
|
"file": "assets/app-Gkl9XGUK.css",
|
||||||
"src": "resources/css/app.css",
|
"src": "resources/css/app.css",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"name": "app",
|
"name": "app",
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
if(!self.define){let e,s={};const n=(n,r)=>(n=new URL(n+".js",r).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(r,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let o={};const l=e=>n(e,t),c={module:{uri:t},exports:o,require:l};s[t]=Promise.all(r.map(e=>c[e]||l(e))).then(e=>(i(...e),o))}}define(["./workbox-d7f7d914"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"2d094791c49e920331981a2d203b8cdb"},{url:"assets/marker-icon-hN30_KVU.png",revision:null},{url:"assets/layers-BWBAp2CZ.png",revision:null},{url:"assets/layers-2x-Bpkbi35X.png",revision:null},{url:"assets/app-GNqTWY09.js",revision:null},{url:"assets/app-DTM5xC6O.css",revision:null},{url:"manifest.webmanifest",revision:"d9cbc35793758c64f87f24da203c23b4"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(/\.(?:woff2?|ttf|eot|otf)$/,new e.CacheFirst({cacheName:"fonts-cache",plugins:[new e.ExpirationPlugin({maxEntries:30,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/\/driver\/.*/,new e.NetworkFirst({cacheName:"driver-pages-cache",plugins:[new e.ExpirationPlugin({maxEntries:50,maxAgeSeconds:86400})]}),"GET")});
|
if(!self.define){let e,s={};const n=(n,r)=>(n=new URL(n+".js",r).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(r,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let o={};const l=e=>n(e,t),c={module:{uri:t},exports:o,require:l};s[t]=Promise.all(r.map(e=>c[e]||l(e))).then(e=>(i(...e),o))}}define(["./workbox-d7f7d914"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"2d094791c49e920331981a2d203b8cdb"},{url:"assets/marker-icon-hN30_KVU.png",revision:null},{url:"assets/layers-BWBAp2CZ.png",revision:null},{url:"assets/layers-2x-Bpkbi35X.png",revision:null},{url:"assets/app-Gkl9XGUK.css",revision:null},{url:"assets/app-GNqTWY09.js",revision:null},{url:"manifest.webmanifest",revision:"d9cbc35793758c64f87f24da203c23b4"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(/\.(?:woff2?|ttf|eot|otf)$/,new e.CacheFirst({cacheName:"fonts-cache",plugins:[new e.ExpirationPlugin({maxEntries:30,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/\/driver\/.*/,new e.NetworkFirst({cacheName:"driver-pages-cache",plugins:[new e.ExpirationPlugin({maxEntries:50,maxAgeSeconds:86400})]}),"GET")});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue