From 42a879e9616097286efca9e45d935b0f1c9aafdc Mon Sep 17 00:00:00 2001 From: 7IN0SAN9 Date: Tue, 24 Mar 2026 09:21:21 +0800 Subject: [PATCH] fix: tree & guide --- app/Filament/Resources/DocumentResource.php | 2 +- .../GuideResource/Pages/ManageGuidePages.php | 289 ++- .../Controllers/Api/TerminalApiController.php | 133 +- app/Models/Guide.php | 20 +- app/Models/GuidePage.php | 53 +- app/Models/GuidePageEdge.php | 31 + composer.json | 1 - composer.lock | 72 +- .../2026_03_13_010100_create_guides_table.php | 24 +- database/seeders/GuideSeeder.php | 139 +- public/vendor/drawflow/drawflow.min.css | 1 + public/vendor/drawflow/drawflow.min.js | 1 + resources/views/documents/preview.blade.php | 79 +- .../resources/guide/manage-pages.blade.php | 352 +++ resources/views/welcome.blade.php | 2023 ++++++++++++++--- 15 files changed, 2619 insertions(+), 601 deletions(-) create mode 100644 app/Models/GuidePageEdge.php create mode 100644 public/vendor/drawflow/drawflow.min.css create mode 100644 public/vendor/drawflow/drawflow.min.js create mode 100644 resources/views/filament/resources/guide/manage-pages.blade.php diff --git a/app/Filament/Resources/DocumentResource.php b/app/Filament/Resources/DocumentResource.php index d54ca5a..e2cfb16 100644 --- a/app/Filament/Resources/DocumentResource.php +++ b/app/Filament/Resources/DocumentResource.php @@ -305,7 +305,7 @@ class DocumentResource extends Resource public static function formatFileSize(?int $bytes): string { if ($bytes === null) { - return '—'; + return '-'; } $units = ['B', 'KB', 'MB', 'GB']; diff --git a/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php b/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php index 087a84e..fc5ca18 100644 --- a/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php +++ b/app/Filament/Resources/GuideResource/Pages/ManageGuidePages.php @@ -3,47 +3,259 @@ namespace App\Filament\Resources\GuideResource\Pages; use App\Filament\Resources\GuideResource; -use App\Models\Guide; use App\Models\GuidePage; +use App\Models\GuidePageEdge; +use Filament\Actions\Action; use Filament\Forms; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Builder; -use SolutionForest\FilamentTree\Actions; -use SolutionForest\FilamentTree\Resources\Pages\TreePage; +use Filament\Resources\Pages\Concerns\InteractsWithRecord; +use Filament\Resources\Pages\Page; -class ManageGuidePages extends TreePage +class ManageGuidePages extends Page { + use InteractsWithRecord; + protected static string $resource = GuideResource::class; - protected ?string $treeTitle = '指引页面'; - protected bool $enableTreeTitle = true; + protected static string $view = 'filament.resources.guide.manage-pages'; - protected static string $model = GuidePage::class; + public array $nodes = []; - protected function getTreeQuery(): Builder + public array $edges = []; + + public function mount(int|string $record): void { - return GuidePage::query() - ->where('guide_id', $this->getOwnerRecord()->id); + $this->record = $this->resolveRecord($record); + $this->loadGraph(); } - public function getTreeRecordTitle(?Model $record = null): string + public function getTitle(): string { - if (!$record) { - return ''; + return $this->getRecord()->name . ' - 页面流程'; + } + + public function loadGraph(): void + { + $pages = $this->getRecord()->pages()->get(); + $edgeModels = $this->getRecord()->edges()->orderBy('sort')->get(); + + // Build adjacency list + $children = []; + foreach ($pages as $p) { + $children[$p->id] = []; } - $prefix = $record->branch_option ? "[{$record->branch_option}] " : ''; - $suffix = !empty($record->options) ? ' 📋' : ''; - return $prefix . $record->title . $suffix; + foreach ($edgeModels as $e) { + if (isset($children[$e->from_page_id])) { + $children[$e->from_page_id][] = $e->to_page_id; + } + } + + // BFS from entry nodes (no incoming edges) to assign levels + $hasIncoming = array_flip($edgeModels->pluck('to_page_id')->toArray()); + $levels = []; + $visited = []; + $queue = []; + + foreach ($pages as $p) { + if (!isset($hasIncoming[$p->id])) { + $queue[] = $p->id; + $levels[$p->id] = 0; + $visited[$p->id] = true; + } + } + + while (!empty($queue)) { + $cur = array_shift($queue); + foreach ($children[$cur] ?? [] as $child) { + if (!isset($visited[$child])) { + $visited[$child] = true; + $levels[$child] = $levels[$cur] + 1; + $queue[] = $child; + } + } + } + + // Orphans at bottom + $maxLevel = empty($levels) ? 0 : max($levels); + foreach ($pages as $p) { + if (!isset($levels[$p->id])) { + $levels[$p->id] = $maxLevel + 1; + } + } + + // Group by level + $levelGroups = []; + foreach ($pages as $p) { + $levelGroups[$levels[$p->id]][] = $p->id; + } + ksort($levelGroups); + + // Compute positions: center each level horizontally, stack vertically + $nodeWidth = 240; + $gapX = 40; + $gapY = 150; + $positions = []; + + foreach ($levelGroups as $level => $ids) { + $count = count($ids); + $totalWidth = $count * $nodeWidth + ($count - 1) * $gapX; + $startX = max(20, (800 - $totalWidth) / 2); + foreach ($ids as $i => $id) { + $positions[$id] = [ + 'x' => (int) ($startX + $i * ($nodeWidth + $gapX)), + 'y' => 40 + $level * $gapY, + ]; + } + } + + $this->nodes = $pages->map(fn(GuidePage $p) => [ + 'id' => $p->id, + 'title' => $p->title, + 'html_url' => $p->html_url, + 'is_entry' => !isset($hasIncoming[$p->id]), + 'options' => $p->options ?? [], + 'x' => $positions[$p->id]['x'] ?? 50, + 'y' => $positions[$p->id]['y'] ?? 50, + ])->values()->toArray(); + + $this->edges = $edgeModels->map(fn(GuidePageEdge $e) => [ + 'id' => $e->id, + 'from' => $e->from_page_id, + 'to' => $e->to_page_id, + 'label' => $e->label, + ])->values()->toArray(); } - protected function getFormSchema(): array + // -- Livewire methods called by Drawflow events -- + + public function addEdge(int $fromPageId, int $toPageId, string $outputClass = 'output_1'): void + { + $guide = $this->getRecord(); + + if ( + !$guide->pages()->where('id', $fromPageId)->exists() || + !$guide->pages()->where('id', $toPageId)->exists() + ) { + return; + } + + if ($fromPageId === $toPageId) { + return; + } + + $exists = $guide->edges() + ->where('from_page_id', $fromPageId) + ->where('to_page_id', $toPageId) + ->exists(); + + if ($exists) { + return; + } + + // Derive label from output port → page options mapping + $page = $guide->pages()->find($fromPageId); + $options = $page->options ?? []; + $label = null; + + if (!empty($options)) { + $outputIndex = (int) str_replace('output_', '', $outputClass) - 1; + $label = $options[$outputIndex] ?? null; + } + + $guide->edges()->create([ + 'from_page_id' => $fromPageId, + 'to_page_id' => $toPageId, + 'label' => $label, + ]); + + $this->loadGraph(); + $this->dispatch('graphUpdated'); + } + + public function removeEdge(int $fromPageId, int $toPageId): void + { + $this->getRecord()->edges() + ->where('from_page_id', $fromPageId) + ->where('to_page_id', $toPageId) + ->delete(); + + $this->loadGraph(); + $this->dispatch('graphUpdated'); + } + + // -- Filament Actions -- + + public function createPageAction(): Action + { + return Action::make('createPage') + ->label('添加页面') + ->icon('heroicon-o-plus') + ->form($this->getPageFormSchema()) + ->action(function (array $data): void { + $this->getRecord()->pages()->create($data); + + $this->loadGraph(); + $this->dispatch('graphUpdated'); + }); + } + + public function editPageAction(): Action + { + return Action::make('editPage') + ->label('编辑页面') + ->icon('heroicon-o-pencil-square') + ->mountUsing(function (Forms\Form $form, array $arguments): void { + $page = $this->getRecord()->pages()->findOrFail($arguments['id']); + $form->fill([ + 'title' => $page->title, + 'html_url' => $page->html_url, + 'options' => $page->options ?? [], + ]); + }) + ->form($this->getPageFormSchema()) + ->action(function (array $data, array $arguments): void { + $page = $this->getRecord()->pages()->findOrFail($arguments['id']); + $page->update($data); + + $this->loadGraph(); + $this->dispatch('graphUpdated'); + }); + } + + public function deletePageAction(): Action + { + return Action::make('deletePage') + ->label('删除页面') + ->icon('heroicon-o-trash') + ->color('danger') + ->requiresConfirmation() + ->action(function (array $arguments): void { + $page = $this->getRecord()->pages()->findOrFail($arguments['id']); + $page->delete(); + + $this->loadGraph(); + $this->dispatch('graphUpdated'); + }); + } + + public function deleteEdgeAction(): Action + { + return Action::make('deleteEdge') + ->label('删除连线') + ->icon('heroicon-o-trash') + ->color('danger') + ->requiresConfirmation() + ->action(function (array $arguments): void { + $edge = $this->getRecord()->edges()->findOrFail($arguments['id']); + $edge->delete(); + + $this->loadGraph(); + $this->dispatch('graphUpdated'); + }); + } + + private function getPageFormSchema(): array { return [ - Forms\Components\TextInput::make('page_number') - ->label('页码') - ->numeric() - ->minValue(1), - Forms\Components\TextInput::make('title') ->label('页面标题') ->required() @@ -56,33 +268,8 @@ class ManageGuidePages extends TreePage ->maxLength(500), Forms\Components\TagsInput::make('options') - ->label('选项按钮') - ->helperText('此页面展示的选项按钮,如"前门12"、"后门"。留空=无分支。'), - - Forms\Components\TextInput::make('branch_option') - ->label('所属分支选项') - ->maxLength(100) - ->helperText('此页面对应父页面的哪个选项值(根页面留空)'), + ->label('分支选项') + ->helperText('定义此页面的分支按钮(每个选项对应一个输出端口)。留空 = 顺序页面。'), ]; } - - protected function getTreeActions(): array - { - return [ - Actions\ViewAction::make(), - Actions\EditAction::make()->slideOver(), - Actions\DeleteAction::make(), - ]; - } - - protected function mutateFormDataBeforeCreate(array $data): array - { - $data['guide_id'] = $this->getOwnerRecord()->id; - return $data; - } - - protected function getOwnerRecord(): Guide - { - return Guide::findOrFail(request()->route('record')); - } } diff --git a/app/Http/Controllers/Api/TerminalApiController.php b/app/Http/Controllers/Api/TerminalApiController.php index 7446249..97ff009 100644 --- a/app/Http/Controllers/Api/TerminalApiController.php +++ b/app/Http/Controllers/Api/TerminalApiController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Guide; use App\Models\GuidePage; +use App\Models\GuidePageEdge; use App\Services\KnowledgeContextService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -90,7 +91,7 @@ class TerminalApiController extends Controller /** * POST /api/terminal/guides/pages - * 组合多个指引的页面,返回递归树形结构 + * 返回指引页面(状态机格式,每页带 next 指针) */ public function guidePages(Request $request): JsonResponse { @@ -102,104 +103,54 @@ class TerminalApiController extends Controller $terminal = $request->attributes->get('terminal'); $accessibleIds = $this->getTerminalGuides($terminal)->pluck('guides.id')->toArray(); - $guideIds = $request->input('guide_ids'); - $pages = []; + $guideIds = collect($request->input('guide_ids')) + ->intersect($accessibleIds) + ->values() + ->toArray(); - foreach ($guideIds as $guideId) { - if (!in_array($guideId, $accessibleIds)) { - continue; + $pages = GuidePage::whereIn('guide_id', $guideIds)->get(); + $edges = GuidePageEdge::whereIn('guide_id', $guideIds) + ->orderBy('from_page_id') + ->orderBy('sort') + ->get(); + + $edgesByFrom = $edges->groupBy('from_page_id'); + $hasIncoming = $edges->pluck('to_page_id')->unique()->flip(); + + $guides = []; + foreach ($pages->groupBy('guide_id') as $guideId => $guidePages) { + $entryPage = $guidePages->first(fn($p) => !$hasIncoming->has($p->id)); + + $pagesMap = []; + foreach ($guidePages as $page) { + $next = $edgesByFrom->get($page->id, collect()) + ->map(function (GuidePageEdge $e) { + $item = ['page_id' => $e->to_page_id]; + if ($e->label !== null) { + $item['label'] = $e->label; + } + return $item; + })->values()->toArray(); + + $pagesMap[$page->id] = [ + 'id' => $page->id, + 'title' => $page->title, + 'html_url' => $page->html_url, + 'next' => $next, + ]; } - $guide = Guide::with( - $this->buildEagerLoadArray('trunkPages', 5) - )->find($guideId); - - if (!$guide) { - continue; - } - - foreach ($guide->trunkPages as $page) { - $pages = array_merge($pages, $this->flattenSequentialPages($page, $guide->name, $guide->id)); - } + $guides[$guideId] = [ + 'entry_page_id' => $entryPage?->id, + 'pages' => $pagesMap, + ]; } return response()->json([ - 'pages' => $pages, - 'total_pages' => count($pages), + 'guides' => $guides, ]); } - /** - * 将树形页面结构展平:顺序节点(无 options)平铺,分支节点保留嵌套 - */ - private function flattenSequentialPages(GuidePage $page, string $guideName, int $guideId): array - { - $data = [ - 'id' => $page->id, - 'guide_id' => $guideId, - 'guide_name' => $guideName, - 'page_number' => $page->page_number, - 'title' => $page->title, - 'html_url' => $page->html_url, - ]; - - if ($page->options && $page->branchChildren->isNotEmpty()) { - $data['options'] = $page->options; - $branches = []; - foreach ($page->branchChildren as $child) { - $branches[$child->branch_option][] = - $this->buildPageTree($child, $guideName, $guideId); - } - $data['branches'] = $branches; - return [$data]; - } - - if ($page->branchChildren->isNotEmpty()) { - $result = [$data]; - foreach ($page->branchChildren as $child) { - $result = array_merge($result, $this->flattenSequentialPages($child, $guideName, $guideId)); - } - return $result; - } - - return [$data]; - } - - private function buildPageTree(GuidePage $page, string $guideName, int $guideId): array - { - $data = [ - 'id' => $page->id, - 'guide_id' => $guideId, - 'guide_name' => $guideName, - 'page_number' => $page->page_number, - 'title' => $page->title, - 'html_url' => $page->html_url, - ]; - - if ($page->options && $page->branchChildren->isNotEmpty()) { - $data['options'] = $page->options; - $branches = []; - foreach ($page->branchChildren as $child) { - $branches[$child->branch_option][] = - $this->buildPageTree($child, $guideName, $guideId); - } - $data['branches'] = $branches; - } - - return $data; - } - - private function buildEagerLoadArray(string $base, int $depth): array - { - $loads = [$base => fn($q) => $q->orderBy('sort_order')]; - $current = $base; - for ($i = 0; $i < $depth; $i++) { - $current .= '.branchChildren'; - $loads[$current] = fn($q) => $q->orderBy('sort_order'); - } - return $loads; - } - /** * 获取终端可见的指引(线站关联 + 全局) */ @@ -210,7 +161,7 @@ class TerminalApiController extends Controller return Guide::published()->where(function ($q) use ($stationId) { $q->whereDoesntHave('stations'); // 全局指引 if ($stationId) { - $q->orWhereHas('stations', fn ($sq) => $sq->where('stations.id', $stationId)); + $q->orWhereHas('stations', fn($sq) => $sq->where('stations.id', $stationId)); } }); } diff --git a/app/Models/Guide.php b/app/Models/Guide.php index 5b5a404..7618d0e 100644 --- a/app/Models/Guide.php +++ b/app/Models/Guide.php @@ -33,14 +33,22 @@ class Guide extends Model public function pages() { - return $this->hasMany(GuidePage::class)->orderBy('sort_order'); + return $this->hasMany(GuidePage::class); } - public function trunkPages() + public function edges() { - return $this->hasMany(GuidePage::class) - ->where('parent_id', -1) - ->orderBy('sort_order'); + return $this->hasMany(GuidePageEdge::class); + } + + public function entryPage() + { + return $this->hasOne(GuidePage::class) + ->whereNotIn('guide_pages.id', function ($q) { + $q->select('to_page_id') + ->from('guide_page_edges') + ->whereColumn('guide_page_edges.guide_id', 'guide_pages.guide_id'); + }); } public function creator() @@ -76,7 +84,7 @@ class Guide extends Model return $query->where(function (Builder $q) use ($stationIds) { $q->whereDoesntHave('stations') - ->orWhereHas('stations', fn ($sq) => $sq->whereIn('stations.id', $stationIds)); + ->orWhereHas('stations', fn($sq) => $sq->whereIn('stations.id', $stationIds)); }); } diff --git a/app/Models/GuidePage.php b/app/Models/GuidePage.php index 1b88d21..582f9cc 100644 --- a/app/Models/GuidePage.php +++ b/app/Models/GuidePage.php @@ -3,42 +3,26 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; -use SolutionForest\FilamentTree\Concern\ModelTree; class GuidePage extends Model { - use ModelTree; - protected $fillable = [ 'guide_id', - 'page_number', 'title', 'html_url', - 'sort_order', - 'parent_id', 'options', - 'branch_option', ]; protected $casts = [ 'options' => 'array', - 'parent_id' => 'int', ]; - // filament-tree column name mapping - public function determineParentColumnName(): string + protected static function booted(): void { - return 'parent_id'; - } - - public function determineOrderColumnName(): string - { - return 'sort_order'; - } - - public function determineTitleColumnName(): string - { - return 'title'; + static::deleting(function (GuidePage $page) { + // CASCADE on from_page_id is handled by FK, but incoming edges need cleanup + GuidePageEdge::where('to_page_id', $page->id)->delete(); + }); } public function guide() @@ -46,13 +30,32 @@ class GuidePage extends Model return $this->belongsTo(Guide::class); } - public function branchChildren() + public function outgoingEdges() { - return $this->hasMany(self::class, 'parent_id')->orderBy('sort_order'); + return $this->hasMany(GuidePageEdge::class, 'from_page_id')->orderBy('sort'); } - public function parentPage() + public function incomingEdges() { - return $this->belongsTo(self::class, 'parent_id'); + return $this->hasMany(GuidePageEdge::class, 'to_page_id'); } + + public function nextPages() + { + return $this->belongsToMany(self::class, 'guide_page_edges', 'from_page_id', 'to_page_id') + ->withPivot('label', 'sort') + ->orderByPivot('sort'); + } + + public function previousPages() + { + return $this->belongsToMany(self::class, 'guide_page_edges', 'to_page_id', 'from_page_id') + ->withPivot('label', 'sort'); + } + + public function isEntry(): bool + { + return !$this->incomingEdges()->exists(); + } + } diff --git a/app/Models/GuidePageEdge.php b/app/Models/GuidePageEdge.php new file mode 100644 index 0000000..990c66e --- /dev/null +++ b/app/Models/GuidePageEdge.php @@ -0,0 +1,31 @@ +belongsTo(Guide::class); + } + + public function fromPage() + { + return $this->belongsTo(GuidePage::class, 'from_page_id'); + } + + public function toPage() + { + return $this->belongsTo(GuidePage::class, 'to_page_id'); + } +} diff --git a/composer.json b/composer.json index cafc00e..dfb8469 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,6 @@ "maatwebsite/excel": "^3.1", "meilisearch/meilisearch-php": "^1.16", "paperdoc-dev/paperdoc-lib": "^0.3.5", - "solution-forest/filament-tree": "^2.0", "spatie/laravel-activitylog": "^4.12", "spatie/laravel-permission": "^6.24" }, diff --git a/composer.lock b/composer.lock index 4cc3b62..41fccdb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7ac0bdce6d17a09797646ea53c4b658a", + "content-hash": "fc9b62a7a28737298d8a172dd410dab9", "packages": [ { "name": "abdelhamiderrahmouni/filament-monaco-editor", @@ -6143,76 +6143,6 @@ ], "time": "2026-03-19T10:36:26+00:00" }, - { - "name": "solution-forest/filament-tree", - "version": "2.1.8", - "source": { - "type": "git", - "url": "https://github.com/solutionforest/filament-tree.git", - "reference": "de8b27c7c58f1e8c8e1a3081dff2e477b4327301" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/solutionforest/filament-tree/zipball/de8b27c7c58f1e8c8e1a3081dff2e477b4327301", - "reference": "de8b27c7c58f1e8c8e1a3081dff2e477b4327301", - "shasum": "" - }, - "require": { - "filament/filament": "^3.0", - "filament/support": "^3.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.15.0" - }, - "require-dev": { - "laravel/pint": "^1.0", - "nunomaduro/collision": "^7.9", - "nunomaduro/larastan": "^2.0.1", - "orchestra/testbench": "^8.0", - "pestphp/pest": "^2.0", - "pestphp/pest-plugin-arch": "^2.0", - "pestphp/pest-plugin-laravel": "^2.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "spatie/laravel-ray": "^1.26" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "SolutionForest\\FilamentTree\\FilamentTreeServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "SolutionForest\\FilamentTree\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Carly", - "email": "info@solutionforest.net", - "role": "Developer" - } - ], - "description": "This is a tree layout plugin for Filament Admin", - "homepage": "https://github.com/solution-forest/filament-tree", - "keywords": [ - "Solution Forest", - "filament-tree", - "laravel" - ], - "support": { - "issues": "https://github.com/solution-forest/filament-tree/issues", - "source": "https://github.com/solution-forest/filament-tree" - }, - "time": "2025-08-11T09:35:33+00:00" - }, { "name": "spatie/color", "version": "1.8.0", diff --git a/database/migrations/2026_03_13_010100_create_guides_table.php b/database/migrations/2026_03_13_010100_create_guides_table.php index 5d02195..a61323c 100644 --- a/database/migrations/2026_03_13_010100_create_guides_table.php +++ b/database/migrations/2026_03_13_010100_create_guides_table.php @@ -27,17 +27,26 @@ return new class extends Migration Schema::create('guide_pages', function (Blueprint $table) { $table->id(); $table->foreignId('guide_id')->constrained()->cascadeOnDelete(); - $table->unsignedInteger('page_number')->comment('页码'); $table->string('title')->comment('页面标题'); $table->string('html_url', 500)->comment('HTML页面链接'); - $table->integer('parent_id')->default(-1); - $table->unsignedInteger('sort_order')->default(0)->comment('排序'); - $table->json('options')->nullable(); - $table->string('branch_option', 100)->nullable(); + $table->json('options')->nullable()->comment('分支选项(定义输出端口)'); $table->timestamps(); - $table->index('parent_id'); - $table->index(['guide_id', 'sort_order']); + $table->index('guide_id'); + }); + + Schema::create('guide_page_edges', function (Blueprint $table) { + $table->id(); + $table->foreignId('guide_id')->constrained()->cascadeOnDelete(); + $table->foreignId('from_page_id')->constrained('guide_pages')->cascadeOnDelete(); + $table->foreignId('to_page_id')->constrained('guide_pages')->cascadeOnDelete(); + $table->string('label', 100)->nullable()->comment('选项按钮文字, null=顺序连接'); + $table->unsignedInteger('sort')->default(0)->comment('选项排序'); + $table->timestamps(); + + $table->unique(['from_page_id', 'label']); + $table->index('guide_id'); + $table->index('to_page_id'); }); Schema::create('guide_station', function (Blueprint $table) { @@ -53,6 +62,7 @@ return new class extends Migration public function down(): void { Schema::dropIfExists('guide_station'); + Schema::dropIfExists('guide_page_edges'); Schema::dropIfExists('guide_pages'); Schema::dropIfExists('guides'); } diff --git a/database/seeders/GuideSeeder.php b/database/seeders/GuideSeeder.php index 144fe0e..00c52e5 100644 --- a/database/seeders/GuideSeeder.php +++ b/database/seeders/GuideSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use App\Models\Guide; use App\Models\GuidePage; +use App\Models\GuidePageEdge; use App\Models\Station; use App\Models\User; use Illuminate\Database\Seeder; @@ -41,6 +42,7 @@ class GuideSeeder extends Seeder $this->command->info('操作指引数据创建完成!'); $this->command->info(' - 指引数量: ' . Guide::count()); $this->command->info(' - 指引页面数量: ' . GuidePage::count()); + $this->command->info(' - 指引边数量: ' . GuidePageEdge::count()); $this->command->info(' - 关联线站数量: ' . $stations->count()); } @@ -60,77 +62,110 @@ class GuideSeeder extends Seeder $baseUrl = self::BASE_URL . '/how-to-use-beam'; - // 步骤1: 打开光子光闸 PS1(根节点) $step1 = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => 1, 'title' => '打开光子光闸 PS1', 'html_url' => "{$baseUrl}/step-1.html", - 'parent_id' => -1, - 'sort_order' => 0, ]); - // 步骤2: 搜索光学棚屋(带选项:前门12 / 后门) $step2 = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => 2, 'title' => '搜索光学棚屋', 'html_url' => "{$baseUrl}/step-2.html", - 'parent_id' => $step1->id, - 'sort_order' => 1, 'options' => ['前门12', '后门'], ]); - // 步骤3a: 前门12路径 - 检查设备状态 $step3a = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => 3, 'title' => '前门12路径 - 检查设备状态', 'html_url' => "{$baseUrl}/step-3a.html", - 'parent_id' => $step2->id, - 'sort_order' => 0, - 'branch_option' => '前门12', ]); - // 步骤3b: 后门路径 - 安全确认 $step3b = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => 3, 'title' => '后门路径 - 安全确认', 'html_url' => "{$baseUrl}/step-3b.html", - 'parent_id' => $step2->id, - 'sort_order' => 1, - 'branch_option' => '后门', ]); - // 步骤4a: 前门12路径 - 打开实验站光闸 - GuidePage::create([ + $step4a = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => 4, 'title' => '前门12路径 - 打开实验站光闸', 'html_url' => "{$baseUrl}/step-4a.html", - 'parent_id' => $step3a->id, - 'sort_order' => 0, ]); - // 步骤4b: 后门路径 - 设备检查 - GuidePage::create([ + $step4b = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => 4, 'title' => '后门路径 - 设备检查', 'html_url' => "{$baseUrl}/step-4b.html", - 'parent_id' => $step3b->id, - 'sort_order' => 0, ]); - // 步骤5: 完成(根节点,最终汇合) - GuidePage::create([ + $step5 = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => 5, 'title' => '完成', 'html_url' => "{$baseUrl}/step-5.html", - 'parent_id' => -1, - 'sort_order' => 1, + ]); + + // step1 → step2 (sequential) + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $step1->id, + 'to_page_id' => $step2->id, + 'label' => null, + 'sort' => 0, + ]); + + // step2 → step3a (branching option: 前门12) + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $step2->id, + 'to_page_id' => $step3a->id, + 'label' => '前门12', + 'sort' => 0, + ]); + + // step2 → step3b (branching option: 后门) + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $step2->id, + 'to_page_id' => $step3b->id, + 'label' => '后门', + 'sort' => 1, + ]); + + // step3a → step4a (sequential) + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $step3a->id, + 'to_page_id' => $step4a->id, + 'label' => null, + 'sort' => 0, + ]); + + // step3b → step4b (sequential) + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $step3b->id, + 'to_page_id' => $step4b->id, + 'label' => null, + 'sort' => 0, + ]); + + // step4a → step5 (convergence) + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $step4a->id, + 'to_page_id' => $step5->id, + 'label' => null, + 'sort' => 0, + ]); + + // step4b → step5 (convergence) + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $step4b->id, + 'to_page_id' => $step5->id, + 'label' => null, + 'sort' => 0, ]); return $guide; @@ -159,17 +194,24 @@ class GuideSeeder extends Seeder ['title' => '联系维护人员', 'file' => 'step-5.html'], ]; - $parentId = -1; + $pages = []; foreach ($steps as $i => $step) { - $page = GuidePage::create([ + $pages[] = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => $i + 1, 'title' => $step['title'], 'html_url' => "{$baseUrl}/{$step['file']}", - 'parent_id' => $parentId, - 'sort_order' => $parentId === -1 ? $i : 0, ]); - $parentId = $page->id; + } + + // Sequential edges: A→B→C→D→E + for ($i = 0; $i < count($pages) - 1; $i++) { + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $pages[$i]->id, + 'to_page_id' => $pages[$i + 1]->id, + 'label' => null, + 'sort' => 0, + ]); } return $guide; @@ -198,17 +240,24 @@ class GuideSeeder extends Seeder ['title' => '完成', 'file' => 'step-5.html'], ]; - $parentId = -1; + $pages = []; foreach ($steps as $i => $step) { - $page = GuidePage::create([ + $pages[] = GuidePage::create([ 'guide_id' => $guide->id, - 'page_number' => $i + 1, 'title' => $step['title'], 'html_url' => "{$baseUrl}/{$step['file']}", - 'parent_id' => $parentId, - 'sort_order' => $parentId === -1 ? $i : 0, ]); - $parentId = $page->id; + } + + // Sequential edges: A→B→C→D→E + for ($i = 0; $i < count($pages) - 1; $i++) { + GuidePageEdge::create([ + 'guide_id' => $guide->id, + 'from_page_id' => $pages[$i]->id, + 'to_page_id' => $pages[$i + 1]->id, + 'label' => null, + 'sort' => 0, + ]); } return $guide; diff --git a/public/vendor/drawflow/drawflow.min.css b/public/vendor/drawflow/drawflow.min.css new file mode 100644 index 0000000..c9c9442 --- /dev/null +++ b/public/vendor/drawflow/drawflow.min.css @@ -0,0 +1 @@ +.drawflow,.drawflow .parent-node{position:relative}.parent-drawflow{display:flex;overflow:hidden;touch-action:none;outline:0}.drawflow{width:100%;height:100%;user-select:none;perspective:0}.drawflow .drawflow-node{display:flex;align-items:center;position:absolute;background:#0ff;width:160px;min-height:40px;border-radius:4px;border:2px solid #000;color:#000;z-index:2;padding:15px}.drawflow .drawflow-node.selected{background:red}.drawflow .drawflow-node:hover{cursor:move}.drawflow .drawflow-node .inputs,.drawflow .drawflow-node .outputs{width:0}.drawflow .drawflow-node .drawflow_content_node{width:100%;display:block}.drawflow .drawflow-node .input,.drawflow .drawflow-node .output{position:relative;width:20px;height:20px;background:#fff;border-radius:50%;border:2px solid #000;cursor:crosshair;z-index:1;margin-bottom:5px}.drawflow .drawflow-node .input{left:-27px;top:2px;background:#ff0}.drawflow .drawflow-node .output{right:-3px;top:2px}.drawflow svg{z-index:0;position:absolute;overflow:visible!important}.drawflow .connection{position:absolute;pointer-events:none;aspect-ratio:1/1}.drawflow .connection .main-path{fill:none;stroke-width:5px;stroke:#4682b4;pointer-events:all}.drawflow .connection .main-path:hover{stroke:#1266ab;cursor:pointer}.drawflow .connection .main-path.selected{stroke:#43b993}.drawflow .connection .point{cursor:move;stroke:#000;stroke-width:2;fill:#fff;pointer-events:all}.drawflow .connection .point.selected,.drawflow .connection .point:hover{fill:#1266ab}.drawflow .main-path{fill:none;stroke-width:5px;stroke:#4682b4}.drawflow-delete{position:absolute;display:block;width:30px;height:30px;background:#000;color:#fff;z-index:4;border:2px solid #fff;line-height:30px;font-weight:700;text-align:center;border-radius:50%;font-family:monospace;cursor:pointer}.drawflow>.drawflow-delete{margin-left:-15px;margin-top:15px}.parent-node .drawflow-delete{right:-15px;top:-15px} \ No newline at end of file diff --git a/public/vendor/drawflow/drawflow.min.js b/public/vendor/drawflow/drawflow.min.js new file mode 100644 index 0000000..012df79 --- /dev/null +++ b/public/vendor/drawflow/drawflow.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Drawflow=t():e.Drawflow=t()}("undefined"!=typeof self?self:this,(function(){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var s=t[i]={i:i,l:!1,exports:{}};return e[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)n.d(i,s,function(t){return e[t]}.bind(null,s));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t),n.d(t,"default",(function(){return i}));class i{constructor(e,t=null,n=null){this.events={},this.container=e,this.precanvas=null,this.nodeId=1,this.ele_selected=null,this.node_selected=null,this.drag=!1,this.reroute=!1,this.reroute_fix_curvature=!1,this.curvature=.5,this.reroute_curvature_start_end=.5,this.reroute_curvature=.5,this.reroute_width=6,this.drag_point=!1,this.editor_selected=!1,this.connection=!1,this.connection_ele=null,this.connection_selected=null,this.canvas_x=0,this.canvas_y=0,this.pos_x=0,this.pos_x_start=0,this.pos_y=0,this.pos_y_start=0,this.mouse_x=0,this.mouse_y=0,this.line_path=5,this.first_click=null,this.force_first_input=!1,this.draggable_inputs=!0,this.useuuid=!1,this.parent=n,this.noderegister={},this.render=t,this.drawflow={drawflow:{Home:{data:{}}}},this.module="Home",this.editor_mode="edit",this.zoom=1,this.zoom_max=1.6,this.zoom_min=.5,this.zoom_value=.1,this.zoom_last_value=1,this.evCache=new Array,this.prevDiff=-1}start(){this.container.classList.add("parent-drawflow"),this.container.tabIndex=0,this.precanvas=document.createElement("div"),this.precanvas.classList.add("drawflow"),this.container.appendChild(this.precanvas),this.container.addEventListener("mouseup",this.dragEnd.bind(this)),this.container.addEventListener("mousemove",this.position.bind(this)),this.container.addEventListener("mousedown",this.click.bind(this)),this.container.addEventListener("touchend",this.dragEnd.bind(this)),this.container.addEventListener("touchmove",this.position.bind(this)),this.container.addEventListener("touchstart",this.click.bind(this)),this.container.addEventListener("contextmenu",this.contextmenu.bind(this)),this.container.addEventListener("keydown",this.key.bind(this)),this.container.addEventListener("wheel",this.zoom_enter.bind(this)),this.container.addEventListener("input",this.updateNodeValue.bind(this)),this.container.addEventListener("dblclick",this.dblclick.bind(this)),this.container.onpointerdown=this.pointerdown_handler.bind(this),this.container.onpointermove=this.pointermove_handler.bind(this),this.container.onpointerup=this.pointerup_handler.bind(this),this.container.onpointercancel=this.pointerup_handler.bind(this),this.container.onpointerout=this.pointerup_handler.bind(this),this.container.onpointerleave=this.pointerup_handler.bind(this),this.load()}pointerdown_handler(e){this.evCache.push(e)}pointermove_handler(e){for(var t=0;t100&&(n>this.prevDiff&&this.zoom_in(),n=n&&(n=parseInt(e)+1)}))})),this.nodeId=n}removeReouteConnectionSelected(){this.dispatch("connectionUnselected",!0),this.reroute_fix_curvature&&this.connection_selected.parentElement.querySelectorAll(".main-path").forEach((e,t)=>{e.classList.remove("selected")})}click(e){if(this.dispatch("click",e),"fixed"===this.editor_mode){if(e.preventDefault(),"parent-drawflow"!==e.target.classList[0]&&"drawflow"!==e.target.classList[0])return!1;this.ele_selected=e.target.closest(".parent-drawflow")}else"view"===this.editor_mode?(null!=e.target.closest(".drawflow")||e.target.matches(".parent-drawflow"))&&(this.ele_selected=e.target.closest(".parent-drawflow"),e.preventDefault()):(this.first_click=e.target,this.ele_selected=e.target,0===e.button&&this.contextmenuDel(),null!=e.target.closest(".drawflow_content_node")&&(this.ele_selected=e.target.closest(".drawflow_content_node").parentElement));switch(this.ele_selected.classList[0]){case"drawflow-node":null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected!=this.ele_selected&&this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.node_selected!=this.ele_selected&&this.dispatch("nodeSelected",this.ele_selected.id.slice(5)),this.node_selected=this.ele_selected,this.node_selected.classList.add("selected"),this.draggable_inputs?"SELECT"!==e.target.tagName&&(this.drag=!0):"INPUT"!==e.target.tagName&&"TEXTAREA"!==e.target.tagName&&"SELECT"!==e.target.tagName&&!0!==e.target.hasAttribute("contenteditable")&&(this.drag=!0);break;case"output":this.connection=!0,null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.drawConnection(e.target);break;case"parent-drawflow":case"drawflow":null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.editor_selected=!0;break;case"main-path":null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null),this.connection_selected=this.ele_selected,this.connection_selected.classList.add("selected");const t=this.connection_selected.parentElement.classList;t.length>1&&(this.dispatch("connectionSelected",{output_id:t[2].slice(14),input_id:t[1].slice(13),output_class:t[3],input_class:t[4]}),this.reroute_fix_curvature&&this.connection_selected.parentElement.querySelectorAll(".main-path").forEach((e,t)=>{e.classList.add("selected")}));break;case"point":this.drag_point=!0,this.ele_selected.classList.add("selected");break;case"drawflow-delete":this.node_selected&&this.removeNodeId(this.node_selected.id),this.connection_selected&&this.removeConnection(),null!=this.node_selected&&(this.node_selected.classList.remove("selected"),this.node_selected=null,this.dispatch("nodeUnselected",!0)),null!=this.connection_selected&&(this.connection_selected.classList.remove("selected"),this.removeReouteConnectionSelected(),this.connection_selected=null)}"touchstart"===e.type?(this.pos_x=e.touches[0].clientX,this.pos_x_start=e.touches[0].clientX,this.pos_y=e.touches[0].clientY,this.pos_y_start=e.touches[0].clientY,this.mouse_x=e.touches[0].clientX,this.mouse_y=e.touches[0].clientY):(this.pos_x=e.clientX,this.pos_x_start=e.clientX,this.pos_y=e.clientY,this.pos_y_start=e.clientY),["input","output","main-path"].includes(this.ele_selected.classList[0])&&e.preventDefault(),this.dispatch("clickEnd",e)}position(e){if("touchmove"===e.type)var t=e.touches[0].clientX,n=e.touches[0].clientY;else t=e.clientX,n=e.clientY;if(this.connection&&this.updateConnection(t,n),this.editor_selected&&(i=this.canvas_x+-(this.pos_x-t),s=this.canvas_y+-(this.pos_y-n),this.dispatch("translate",{x:i,y:s}),this.precanvas.style.transform="translate("+i+"px, "+s+"px) scale("+this.zoom+")"),this.drag){e.preventDefault();var i=(this.pos_x-t)*this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom),s=(this.pos_y-n)*this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom);this.pos_x=t,this.pos_y=n,this.ele_selected.style.top=this.ele_selected.offsetTop-s+"px",this.ele_selected.style.left=this.ele_selected.offsetLeft-i+"px",this.drawflow.drawflow[this.module].data[this.ele_selected.id.slice(5)].pos_x=this.ele_selected.offsetLeft-i,this.drawflow.drawflow[this.module].data[this.ele_selected.id.slice(5)].pos_y=this.ele_selected.offsetTop-s,this.updateConnectionNodes(this.ele_selected.id)}if(this.drag_point){i=(this.pos_x-t)*this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom),s=(this.pos_y-n)*this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom);this.pos_x=t,this.pos_y=n;var o=this.pos_x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom)),l=this.pos_y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom));this.ele_selected.setAttributeNS(null,"cx",o),this.ele_selected.setAttributeNS(null,"cy",l);const e=this.ele_selected.parentElement.classList[2].slice(9),c=this.ele_selected.parentElement.classList[1].slice(13),d=this.ele_selected.parentElement.classList[3],a=this.ele_selected.parentElement.classList[4];let r=Array.from(this.ele_selected.parentElement.children).indexOf(this.ele_selected)-1;if(this.reroute_fix_curvature){r-=this.ele_selected.parentElement.querySelectorAll(".main-path").length-1,r<0&&(r=0)}const h=e.slice(5),u=this.drawflow.drawflow[this.module].data[h].outputs[d].connections.findIndex((function(e,t){return e.node===c&&e.output===a}));this.drawflow.drawflow[this.module].data[h].outputs[d].connections[u].points[r]={pos_x:o,pos_y:l};const p=this.ele_selected.parentElement.classList[2].slice(9);this.updateConnectionNodes(p)}"touchmove"===e.type&&(this.mouse_x=t,this.mouse_y=n),this.dispatch("mouseMove",{x:t,y:n})}dragEnd(e){if("touchend"===e.type)var t=this.mouse_x,n=this.mouse_y,i=document.elementFromPoint(t,n);else t=e.clientX,n=e.clientY,i=e.target;if(this.drag&&(this.pos_x_start==t&&this.pos_y_start==n||this.dispatch("nodeMoved",this.ele_selected.id.slice(5))),this.drag_point&&(this.ele_selected.classList.remove("selected"),this.pos_x_start==t&&this.pos_y_start==n||this.dispatch("rerouteMoved",this.ele_selected.parentElement.classList[2].slice(14))),this.editor_selected&&(this.canvas_x=this.canvas_x+-(this.pos_x-t),this.canvas_y=this.canvas_y+-(this.pos_y-n),this.editor_selected=!1),!0===this.connection)if("input"===i.classList[0]||this.force_first_input&&(null!=i.closest(".drawflow_content_node")||"drawflow-node"===i.classList[0])){if(!this.force_first_input||null==i.closest(".drawflow_content_node")&&"drawflow-node"!==i.classList[0])s=i.parentElement.parentElement.id,o=i.classList[1];else{if(null!=i.closest(".drawflow_content_node"))var s=i.closest(".drawflow_content_node").parentElement.id;else var s=i.id;if(0===Object.keys(this.getNodeFromId(s.slice(5)).inputs).length)var o=!1;else var o="input_1"}var l=this.ele_selected.parentElement.parentElement.id,c=this.ele_selected.classList[1];if(l!==s&&!1!==o){if(0===this.container.querySelectorAll(".connection.node_in_"+s+".node_out_"+l+"."+c+"."+o).length){this.connection_ele.classList.add("node_in_"+s),this.connection_ele.classList.add("node_out_"+l),this.connection_ele.classList.add(c),this.connection_ele.classList.add(o);var d=s.slice(5),a=l.slice(5);this.drawflow.drawflow[this.module].data[a].outputs[c].connections.push({node:d,output:o}),this.drawflow.drawflow[this.module].data[d].inputs[o].connections.push({node:a,input:c}),this.updateConnectionNodes("node-"+a),this.updateConnectionNodes("node-"+d),this.dispatch("connectionCreated",{output_id:a,input_id:d,output_class:c,input_class:o})}else this.dispatch("connectionCancel",!0),this.connection_ele.remove();this.connection_ele=null}else this.dispatch("connectionCancel",!0),this.connection_ele.remove(),this.connection_ele=null}else this.dispatch("connectionCancel",!0),this.connection_ele.remove(),this.connection_ele=null;this.drag=!1,this.drag_point=!1,this.connection=!1,this.ele_selected=null,this.editor_selected=!1,this.dispatch("mouseUp",e)}contextmenu(e){if(this.dispatch("contextmenu",e),e.preventDefault(),"fixed"===this.editor_mode||"view"===this.editor_mode)return!1;if(this.precanvas.getElementsByClassName("drawflow-delete").length&&this.precanvas.getElementsByClassName("drawflow-delete")[0].remove(),this.node_selected||this.connection_selected){var t=document.createElement("div");t.classList.add("drawflow-delete"),t.innerHTML="x",this.node_selected&&this.node_selected.appendChild(t),this.connection_selected&&this.connection_selected.parentElement.classList.length>1&&(t.style.top=e.clientY*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))+"px",t.style.left=e.clientX*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))+"px",this.precanvas.appendChild(t))}}contextmenuDel(){this.precanvas.getElementsByClassName("drawflow-delete").length&&this.precanvas.getElementsByClassName("drawflow-delete")[0].remove()}key(e){if(this.dispatch("keydown",e),"fixed"===this.editor_mode||"view"===this.editor_mode)return!1;("Delete"===e.key||"Backspace"===e.key&&e.metaKey)&&(null!=this.node_selected&&"INPUT"!==this.first_click.tagName&&"TEXTAREA"!==this.first_click.tagName&&!0!==this.first_click.hasAttribute("contenteditable")&&this.removeNodeId(this.node_selected.id),null!=this.connection_selected&&this.removeConnection())}zoom_enter(e,t){e.ctrlKey&&(e.preventDefault(),e.deltaY>0?this.zoom_out():this.zoom_in())}zoom_refresh(){this.dispatch("zoom",this.zoom),this.canvas_x=this.canvas_x/this.zoom_last_value*this.zoom,this.canvas_y=this.canvas_y/this.zoom_last_value*this.zoom,this.zoom_last_value=this.zoom,this.precanvas.style.transform="translate("+this.canvas_x+"px, "+this.canvas_y+"px) scale("+this.zoom+")"}zoom_in(){this.zoomthis.zoom_min&&(this.zoom-=this.zoom_value,this.zoom_refresh())}zoom_reset(){1!=this.zoom&&(this.zoom=1,this.zoom_refresh())}createCurvature(e,t,n,i,s,o){var l=e,c=t,d=n,a=i,r=s;switch(o){case"open":if(e>=n)var h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*(-1*r);else h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*r;return" M "+l+" "+c+" C "+h+" "+c+" "+u+" "+a+" "+d+" "+a;case"close":if(e>=n)h=l+Math.abs(d-l)*(-1*r),u=d-Math.abs(d-l)*r;else h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*r;return" M "+l+" "+c+" C "+h+" "+c+" "+u+" "+a+" "+d+" "+a;case"other":if(e>=n)h=l+Math.abs(d-l)*(-1*r),u=d-Math.abs(d-l)*(-1*r);else h=l+Math.abs(d-l)*r,u=d-Math.abs(d-l)*r;return" M "+l+" "+c+" C "+h+" "+c+" "+u+" "+a+" "+d+" "+a;default:return" M "+l+" "+c+" C "+(h=l+Math.abs(d-l)*r)+" "+c+" "+(u=d-Math.abs(d-l)*r)+" "+a+" "+d+" "+a}}drawConnection(e){var t=document.createElementNS("http://www.w3.org/2000/svg","svg");this.connection_ele=t;var n=document.createElementNS("http://www.w3.org/2000/svg","path");n.classList.add("main-path"),n.setAttributeNS(null,"d",""),t.classList.add("connection"),t.appendChild(n),this.precanvas.appendChild(t);var i=e.parentElement.parentElement.id.slice(5),s=e.classList[1];this.dispatch("connectionStart",{output_id:i,output_class:s})}updateConnection(e,t){const n=this.precanvas,i=this.zoom;let s=n.clientWidth/(n.clientWidth*i);s=s||0;let o=n.clientHeight/(n.clientHeight*i);o=o||0;var l=this.connection_ele.children[0],c=this.ele_selected.offsetWidth/2+(this.ele_selected.getBoundingClientRect().x-n.getBoundingClientRect().x)*s,d=this.ele_selected.offsetHeight/2+(this.ele_selected.getBoundingClientRect().y-n.getBoundingClientRect().y)*o,a=e*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom)),r=t*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom)),h=this.curvature,u=this.createCurvature(c,d,a,r,h,"openclose");l.setAttributeNS(null,"d",u)}addConnection(e,t,n,i){var s=this.getModuleFromNodeId(e);if(s===this.getModuleFromNodeId(t)){var o=this.getNodeFromId(e),l=!1;for(var c in o.outputs[n].connections){var d=o.outputs[n].connections[c];d.node==t&&d.output==i&&(l=!0)}if(!1===l){if(this.drawflow.drawflow[s].data[e].outputs[n].connections.push({node:t.toString(),output:i}),this.drawflow.drawflow[s].data[t].inputs[i].connections.push({node:e.toString(),input:n}),this.module===s){var a=document.createElementNS("http://www.w3.org/2000/svg","svg"),r=document.createElementNS("http://www.w3.org/2000/svg","path");r.classList.add("main-path"),r.setAttributeNS(null,"d",""),a.classList.add("connection"),a.classList.add("node_in_node-"+t),a.classList.add("node_out_node-"+e),a.classList.add(n),a.classList.add(i),a.appendChild(r),this.precanvas.appendChild(a),this.updateConnectionNodes("node-"+e),this.updateConnectionNodes("node-"+t)}this.dispatch("connectionCreated",{output_id:e,input_id:t,output_class:n,input_class:i})}}}updateConnectionNodes(e){const t="node_in_"+e,n="node_out_"+e;this.line_path;const i=this.container,s=this.precanvas,o=this.curvature,l=this.createCurvature,c=this.reroute_curvature,d=this.reroute_curvature_start_end,a=this.reroute_fix_curvature,r=this.reroute_width,h=this.zoom;let u=s.clientWidth/(s.clientWidth*h);u=u||0;let p=s.clientHeight/(s.clientHeight*h);p=p||0;const f=i.querySelectorAll("."+n);Object.keys(f).map((function(t,n){if(null===f[t].querySelector(".point")){var m=i.querySelector("#"+e),g=f[t].classList[1].replace("node_in_",""),_=i.querySelector("#"+g).querySelectorAll("."+f[t].classList[4])[0],w=_.offsetWidth/2+(_.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,v=_.offsetHeight/2+(_.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,y=m.querySelectorAll("."+f[t].classList[3])[0],C=y.offsetWidth/2+(y.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,x=y.offsetHeight/2+(y.getBoundingClientRect().y-s.getBoundingClientRect().y)*p;const n=l(C,x,w,v,o,"openclose");f[t].children[0].setAttributeNS(null,"d",n)}else{const n=f[t].querySelectorAll(".point");let o="";const m=[];n.forEach((t,a)=>{if(0===a&&n.length-1==0){var f=i.querySelector("#"+e),g=((x=t).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,w=(L=f.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(L.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,v=L.offsetHeight/2+(L.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,y=l(w,v,g,_,d,"open");o+=y,m.push(y);f=t;var C=t.parentElement.classList[1].replace("node_in_",""),x=(E=i.querySelector("#"+C)).querySelectorAll("."+t.parentElement.classList[4])[0];g=(R=E.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(R.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,_=R.offsetHeight/2+(R.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,y=l(w,v,g,_,d,"close");o+=y,m.push(y)}else if(0===a){var L;f=i.querySelector("#"+e),g=((x=t).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,w=(L=f.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(L.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,v=L.offsetHeight/2+(L.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,y=l(w,v,g,_,d,"open");o+=y,m.push(y);f=t,g=((x=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,y=l(w,v,g,_,c,"other");o+=y,m.push(y)}else if(a===n.length-1){var E,R;f=t,C=t.parentElement.classList[1].replace("node_in_",""),x=(E=i.querySelector("#"+C)).querySelectorAll("."+t.parentElement.classList[4])[0],g=(R=E.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(R.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,_=R.offsetHeight/2+(R.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*(s.clientWidth/(s.clientWidth*h))+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*(s.clientHeight/(s.clientHeight*h))+r,y=l(w,v,g,_,d,"close");o+=y,m.push(y)}else{f=t,g=((x=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*(s.clientWidth/(s.clientWidth*h))+r,_=(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*(s.clientHeight/(s.clientHeight*h))+r,w=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*(s.clientWidth/(s.clientWidth*h))+r,v=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*(s.clientHeight/(s.clientHeight*h))+r,y=l(w,v,g,_,c,"other");o+=y,m.push(y)}}),a?m.forEach((e,n)=>{f[t].children[n].setAttributeNS(null,"d",e)}):f[t].children[0].setAttributeNS(null,"d",o)}}));const m=i.querySelectorAll("."+t);Object.keys(m).map((function(t,n){if(null===m[t].querySelector(".point")){var h=i.querySelector("#"+e),f=m[t].classList[2].replace("node_out_",""),g=i.querySelector("#"+f).querySelectorAll("."+m[t].classList[3])[0],_=g.offsetWidth/2+(g.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,w=g.offsetHeight/2+(g.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,v=(h=h.querySelectorAll("."+m[t].classList[4])[0]).offsetWidth/2+(h.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,y=h.offsetHeight/2+(h.getBoundingClientRect().y-s.getBoundingClientRect().y)*p;const n=l(_,w,v,y,o,"openclose");m[t].children[0].setAttributeNS(null,"d",n)}else{const n=m[t].querySelectorAll(".point");let o="";const h=[];n.forEach((t,a)=>{if(0===a&&n.length-1==0){var f=i.querySelector("#"+e),m=((C=t).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(C.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,_=(E=f.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(E.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,w=E.offsetHeight/2+(E.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,v=l(m,g,_,w,d,"close");o+=v,h.push(v);f=t;var y=t.parentElement.classList[2].replace("node_out_",""),C=(L=i.querySelector("#"+y)).querySelectorAll("."+t.parentElement.classList[3])[0];m=(x=L.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(x.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,g=x.offsetHeight/2+(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,_=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,d,"open");o+=v,h.push(v)}else if(0===a){var x;f=t,y=t.parentElement.classList[2].replace("node_out_",""),C=(L=i.querySelector("#"+y)).querySelectorAll("."+t.parentElement.classList[3])[0],m=(x=L.querySelectorAll("."+t.parentElement.classList[3])[0]).offsetWidth/2+(x.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,g=x.offsetHeight/2+(x.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,_=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,d,"open");o+=v,h.push(v);f=t,_=((C=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(C.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,m=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,c,"other");o+=v,h.push(v)}else if(a===n.length-1){var L,E;f=t,y=t.parentElement.classList[1].replace("node_in_",""),C=(L=i.querySelector("#"+y)).querySelectorAll("."+t.parentElement.classList[4])[0],_=(E=L.querySelectorAll("."+t.parentElement.classList[4])[0]).offsetWidth/2+(E.getBoundingClientRect().x-s.getBoundingClientRect().x)*u,w=E.offsetHeight/2+(E.getBoundingClientRect().y-s.getBoundingClientRect().y)*p,m=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,d,"close");o+=v,h.push(v)}else{f=t,_=((C=n[a+1]).getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,w=(C.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,m=(f.getBoundingClientRect().x-s.getBoundingClientRect().x)*u+r,g=(f.getBoundingClientRect().y-s.getBoundingClientRect().y)*p+r,v=l(m,g,_,w,c,"other");o+=v,h.push(v)}}),a?h.forEach((e,n)=>{m[t].children[n].setAttributeNS(null,"d",e)}):m[t].children[0].setAttributeNS(null,"d",o)}}))}dblclick(e){null!=this.connection_selected&&this.reroute&&this.createReroutePoint(this.connection_selected),"point"===e.target.classList[0]&&this.removeReroutePoint(e.target)}createReroutePoint(e){this.connection_selected.classList.remove("selected");const t=this.connection_selected.parentElement.classList[2].slice(9),n=this.connection_selected.parentElement.classList[1].slice(13),i=this.connection_selected.parentElement.classList[3],s=this.connection_selected.parentElement.classList[4];this.connection_selected=null;const o=document.createElementNS("http://www.w3.org/2000/svg","circle");o.classList.add("point");var l=this.pos_x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom))-this.precanvas.getBoundingClientRect().x*(this.precanvas.clientWidth/(this.precanvas.clientWidth*this.zoom)),c=this.pos_y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom))-this.precanvas.getBoundingClientRect().y*(this.precanvas.clientHeight/(this.precanvas.clientHeight*this.zoom));o.setAttributeNS(null,"cx",l),o.setAttributeNS(null,"cy",c),o.setAttributeNS(null,"r",this.reroute_width);let d=0;if(this.reroute_fix_curvature){const t=e.parentElement.querySelectorAll(".main-path").length;var a=document.createElementNS("http://www.w3.org/2000/svg","path");if(a.classList.add("main-path"),a.setAttributeNS(null,"d",""),e.parentElement.insertBefore(a,e.parentElement.children[t]),1===t)e.parentElement.appendChild(o);else{const n=Array.from(e.parentElement.children).indexOf(e);d=n,e.parentElement.insertBefore(o,e.parentElement.children[n+t+1])}}else e.parentElement.appendChild(o);const r=t.slice(5),h=this.drawflow.drawflow[this.module].data[r].outputs[i].connections.findIndex((function(e,t){return e.node===n&&e.output===s}));void 0===this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points&&(this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points=[]),this.reroute_fix_curvature?(d>0||this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points!==[]?this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points.splice(d,0,{pos_x:l,pos_y:c}):this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points.push({pos_x:l,pos_y:c}),e.parentElement.querySelectorAll(".main-path").forEach((e,t)=>{e.classList.remove("selected")})):this.drawflow.drawflow[this.module].data[r].outputs[i].connections[h].points.push({pos_x:l,pos_y:c}),this.dispatch("addReroute",r),this.updateConnectionNodes(t)}removeReroutePoint(e){const t=e.parentElement.classList[2].slice(9),n=e.parentElement.classList[1].slice(13),i=e.parentElement.classList[3],s=e.parentElement.classList[4];let o=Array.from(e.parentElement.children).indexOf(e);const l=t.slice(5),c=this.drawflow.drawflow[this.module].data[l].outputs[i].connections.findIndex((function(e,t){return e.node===n&&e.output===s}));if(this.reroute_fix_curvature){const t=e.parentElement.querySelectorAll(".main-path").length;e.parentElement.children[t-1].remove(),o-=t,o<0&&(o=0)}else o--;this.drawflow.drawflow[this.module].data[l].outputs[i].connections[c].points.splice(o,1),e.remove(),this.dispatch("removeReroute",l),this.updateConnectionNodes(t)}registerNode(e,t,n=null,i=null){this.noderegister[e]={html:t,props:n,options:i}}getNodeFromId(e){var t=this.getModuleFromNodeId(e);return JSON.parse(JSON.stringify(this.drawflow.drawflow[t].data[e]))}getNodesFromName(e){var t=[];const n=this.drawflow.drawflow;return Object.keys(n).map((function(i,s){for(var o in n[i].data)n[i].data[o].name==e&&t.push(n[i].data[o].id)})),t}addNode(e,t,n,i,s,o,l,c,d=!1){if(this.useuuid)var a=this.getUuid();else a=this.nodeId;const r=document.createElement("div");r.classList.add("parent-node");const h=document.createElement("div");h.innerHTML="",h.setAttribute("id","node-"+a),h.classList.add("drawflow-node"),""!=o&&h.classList.add(...o.split(" "));const u=document.createElement("div");u.classList.add("inputs");const p=document.createElement("div");p.classList.add("outputs");const f={};for(var m=0;me(this.noderegister[c].html,{props:this.noderegister[c].props}),...this.noderegister[c].options}).$mount();_.appendChild(e.$el)}Object.entries(l).forEach((function(e,t){if("object"==typeof e[1])!function e(t,n,i){if(null===t)t=l[n];else t=t[n];null!==t&&Object.entries(t).forEach((function(n,s){if("object"==typeof n[1])e(t,n[0],i+"-"+n[0]);else for(var o=_.querySelectorAll("[df-"+i+"-"+n[0]+"]"),l=0;lt(this.noderegister[e.html].html,{props:this.noderegister[e.html].props}),...this.noderegister[e.html].options}).$mount();c.appendChild(t.$el)}Object.entries(e.data).forEach((function(t,n){if("object"==typeof t[1])!function t(n,i,s){if(null===n)n=e.data[i];else n=n[i];null!==n&&Object.entries(n).forEach((function(e,i){if("object"==typeof e[1])t(n,e[0],s+"-"+e[0]);else for(var o=c.querySelectorAll("[df-"+s+"-"+e[0]+"]"),l=0;l{const a=e.outputs[s].connections[o].node,r=e.outputs[s].connections[o].output,h=i.querySelector(".connection.node_in_node-"+a+".node_out_node-"+e.id+"."+s+"."+r);if(n&&0===d)for(var u=0;u{this.removeSingleConnection(e.id_output,e.id,e.output_class,e.input_class)}),delete this.drawflow.drawflow[n].data[e].inputs[t];const o=[],l=this.drawflow.drawflow[n].data[e].inputs;Object.keys(l).map((function(e,t){o.push(l[e])})),this.drawflow.drawflow[n].data[e].inputs={};const c=t.slice(6);let d=[];if(o.forEach((t,i)=>{t.connections.forEach((e,t)=>{d.push(e)}),this.drawflow.drawflow[n].data[e].inputs["input_"+(i+1)]=t}),d=new Set(d.map(e=>JSON.stringify(e))),d=Array.from(d).map(e=>JSON.parse(e)),this.module===n){this.container.querySelectorAll("#node-"+e+" .inputs .input").forEach((e,t)=>{const n=e.classList[1].slice(6);parseInt(c){this.drawflow.drawflow[n].data[t.node].outputs[t.input].connections.forEach((i,s)=>{if(i.node==e){const o=i.output.slice(6);if(parseInt(c){this.removeSingleConnection(e.id,e.id_input,e.output_class,e.input_class)}),delete this.drawflow.drawflow[n].data[e].outputs[t];const o=[],l=this.drawflow.drawflow[n].data[e].outputs;Object.keys(l).map((function(e,t){o.push(l[e])})),this.drawflow.drawflow[n].data[e].outputs={};const c=t.slice(7);let d=[];if(o.forEach((t,i)=>{t.connections.forEach((e,t)=>{d.push({node:e.node,output:e.output})}),this.drawflow.drawflow[n].data[e].outputs["output_"+(i+1)]=t}),d=new Set(d.map(e=>JSON.stringify(e))),d=Array.from(d).map(e=>JSON.parse(e)),this.module===n){this.container.querySelectorAll("#node-"+e+" .outputs .output").forEach((e,t)=>{const n=e.classList[1].slice(7);parseInt(c){this.drawflow.drawflow[n].data[t.node].inputs[t.output].connections.forEach((i,s)=>{if(i.node==e){const o=i.input.slice(7);if(parseInt(c)-1){this.module===s&&this.container.querySelector(".connection.node_in_node-"+t+".node_out_node-"+e+"."+n+"."+i).remove();var o=this.drawflow.drawflow[s].data[e].outputs[n].connections.findIndex((function(e,n){return e.node==t&&e.output===i}));this.drawflow.drawflow[s].data[e].outputs[n].connections.splice(o,1);var l=this.drawflow.drawflow[s].data[t].inputs[i].connections.findIndex((function(t,i){return t.node==e&&t.input===n}));return this.drawflow.drawflow[s].data[t].inputs[i].connections.splice(l,1),this.dispatch("connectionRemoved",{output_id:e,input_id:t,output_class:n,input_class:i}),!0}return!1}return!1}removeConnectionNodeId(e){const t="node_in_"+e,n="node_out_"+e,i=this.container.querySelectorAll("."+n);for(var s=i.length-1;s>=0;s--){var o=i[s].classList,l=this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.findIndex((function(e,t){return e.node===o[2].slice(14)&&e.input===o[3]}));this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.splice(l,1);var c=this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.findIndex((function(e,t){return e.node===o[1].slice(13)&&e.output===o[4]}));this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.splice(c,1),i[s].remove(),this.dispatch("connectionRemoved",{output_id:o[2].slice(14),input_id:o[1].slice(13),output_class:o[3],input_class:o[4]})}const d=this.container.querySelectorAll("."+t);for(s=d.length-1;s>=0;s--){o=d[s].classList,c=this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.findIndex((function(e,t){return e.node===o[1].slice(13)&&e.output===o[4]}));this.drawflow.drawflow[this.module].data[o[2].slice(14)].outputs[o[3]].connections.splice(c,1);l=this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.findIndex((function(e,t){return e.node===o[2].slice(14)&&e.input===o[3]}));this.drawflow.drawflow[this.module].data[o[1].slice(13)].inputs[o[4]].connections.splice(l,1),d[s].remove(),this.dispatch("connectionRemoved",{output_id:o[2].slice(14),input_id:o[1].slice(13),output_class:o[3],input_class:o[4]})}}getModuleFromNodeId(e){var t;const n=this.drawflow.drawflow;return Object.keys(n).map((function(i,s){Object.keys(n[i].data).map((function(n,s){n==e&&(t=i)}))})),t}addModule(e){this.drawflow.drawflow[e]={data:{}},this.dispatch("moduleCreated",e)}changeModule(e){this.dispatch("moduleChanged",e),this.module=e,this.precanvas.innerHTML="",this.canvas_x=0,this.canvas_y=0,this.pos_x=0,this.pos_y=0,this.mouse_x=0,this.mouse_y=0,this.zoom=1,this.zoom_last_value=1,this.precanvas.style.transform="",this.import(this.drawflow,!1)}removeModule(e){this.module===e&&this.changeModule("Home"),delete this.drawflow.drawflow[e],this.dispatch("moduleRemoved",e)}clearModuleSelected(){this.precanvas.innerHTML="",this.drawflow.drawflow[this.module]={data:{}}}clear(){this.precanvas.innerHTML="",this.drawflow={drawflow:{Home:{data:{}}}}}export(){const e=JSON.parse(JSON.stringify(this.drawflow));return this.dispatch("export",e),e}import(e,t=!0){this.clear(),this.drawflow=JSON.parse(JSON.stringify(e)),this.load(),t&&this.dispatch("import","import")}on(e,t){return"function"!=typeof t?(console.error("The listener callback must be a function, the given type is "+typeof t),!1):"string"!=typeof e?(console.error("The event name must be a string, the given type is "+typeof e),!1):(void 0===this.events[e]&&(this.events[e]={listeners:[]}),void this.events[e].listeners.push(t))}removeListener(e,t){if(!this.events[e])return!1;const n=this.events[e].listeners,i=n.indexOf(t);i>-1&&n.splice(i,1)}dispatch(e,t){if(void 0===this.events[e])return!1;this.events[e].listeners.forEach(e=>{e(t)})}getUuid(){for(var e=[],t=0;t<36;t++)e[t]="0123456789abcdef".substr(Math.floor(16*Math.random()),1);return e[14]="4",e[19]="0123456789abcdef".substr(3&e[19]|8,1),e[8]=e[13]=e[18]=e[23]="-",e.join("")}}}]).default})); \ No newline at end of file diff --git a/resources/views/documents/preview.blade.php b/resources/views/documents/preview.blade.php index a61a52f..fb8e9f0 100644 --- a/resources/views/documents/preview.blade.php +++ b/resources/views/documents/preview.blade.php @@ -1,26 +1,27 @@ + {{ $document->title }} - Markdown 预览 - + - - + @vite(['resources/css/app.css']) + +
@@ -82,7 +84,7 @@

{{ $document->title }}

- +
@@ -90,7 +92,7 @@ {{ $document->type === 'global' ? '全局知识库' : '专用知识库' }}
- + @if($document->group)
@@ -99,14 +101,14 @@ {{ $document->group->name }}
@endif - +
{{ $document->uploader->name }}
- +
@@ -114,23 +116,23 @@ {{ $document->created_at->format('Y年m月d日 H:i') }}
- + @if($document->description)

{{ $document->description }}

@endif
- +
- + 下载原文档 - -
- +
@if($markdownHtml) - {!! $markdownHtml !!} + {!! $markdownHtml !!} @else -
-
📄
-

Markdown 内容为空

-

该文档的 Markdown 内容尚未生成或为空

- - - - - 下载原始文档 - -
+
+
📄
+

Markdown 内容为空

+

该文档的 Markdown 内容尚未生成或为空

+ + + + + 下载原始文档 + +
@endif
+ diff --git a/resources/views/filament/resources/guide/manage-pages.blade.php b/resources/views/filament/resources/guide/manage-pages.blade.php new file mode 100644 index 0000000..49a3d90 --- /dev/null +++ b/resources/views/filament/resources/guide/manage-pages.blade.php @@ -0,0 +1,352 @@ + + + + +
+ {{-- Header actions --}} +
+ {{ $this->createPageAction }} +
+ + {{-- Drawflow canvas --}} +
+
+
+
+ + + + + + +
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index b7355d7..0cb0ccd 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -1,277 +1,1770 @@ - - - - {{ config('app.name', 'Laravel') }} + + + - - - + {{ config('app.name', 'Laravel') }} - - @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) - @vite(['resources/css/app.css', 'resources/js/app.js']) - @else - - @endif - - -
- @if (Route::has('login')) - - @endif -
-
-
-
-

Let's get started

-

Laravel has an incredibly rich ecosystem.
We suggest starting with the following.

- - -
-
- {{-- Laravel Logo --}} - - - - - - - - - + + @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) + @vite(['resources/css/app.css', 'resources/js/app.js']) + @else + + @endif + + + +
@if (Route::has('login')) - + @endif - +
+
+
+
+

Let's get started

+

Laravel has an incredibly rich ecosystem.
We suggest starting with the following.

+ + +
+
+ {{-- Laravel Logo --}} + + + + + + + + + + + {{-- Light Mode 12 SVG --}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{-- Dark Mode 12 SVG --}} + +
+
+
+
+ + @if (Route::has('login')) + + @endif + +