map(fn (array $pack): array => ['id' => null, ...$pack]) ->values(); $templatePacks = collect((array) config('nova_cards.template_packs', [])) ->map(fn (array $pack): array => ['id' => null, ...$pack]) ->values(); $databasePacks = NovaCardAssetPack::query() ->where('active', true) ->orderBy('order_num') ->orderBy('name') ->get() ->map(fn (NovaCardAssetPack $pack): array => [ 'id' => (int) $pack->id, 'slug' => (string) $pack->slug, 'name' => (string) $pack->name, 'description' => $pack->description, 'type' => (string) $pack->type, 'preview_image' => $pack->preview_image, 'manifest_json' => $pack->manifest_json, 'official' => (bool) $pack->official, 'active' => (bool) $pack->active, ]); return [ 'formats' => collect((array) config('nova_cards.formats', [])) ->map(fn (array $format, string $key): array => [ 'key' => $key, 'label' => (string) ($format['label'] ?? ucfirst($key)), 'width' => (int) ($format['width'] ?? 1080), 'height' => (int) ($format['height'] ?? 1080), ]) ->values() ->all(), 'font_presets' => collect((array) config('nova_cards.font_presets', [])) ->map(fn (array $font, string $key): array => ['key' => $key, ...$font]) ->values() ->all(), 'gradient_presets' => collect((array) config('nova_cards.gradient_presets', [])) ->map(fn (array $gradient, string $key): array => ['key' => $key, ...$gradient]) ->values() ->all(), 'decor_presets' => array_values((array) config('nova_cards.decor_presets', [])), 'background_modes' => array_values((array) config('nova_cards.background_modes', [])), 'layout_presets' => array_values((array) config('nova_cards.layout_presets', [])), 'alignment_presets' => array_values((array) config('nova_cards.alignment_presets', [])), 'position_presets' => array_values((array) config('nova_cards.position_presets', [])), 'padding_presets' => array_values((array) config('nova_cards.padding_presets', [])), 'max_width_presets' => array_values((array) config('nova_cards.max_width_presets', [])), 'line_height_presets' => array_values((array) config('nova_cards.line_height_presets', [])), 'shadow_presets' => array_values((array) config('nova_cards.shadow_presets', [])), 'focal_positions' => array_values((array) config('nova_cards.focal_positions', [])), 'categories' => NovaCardCategory::query() ->where('active', true) ->orderBy('order_num') ->orderBy('name') ->get() ->map(fn (NovaCardCategory $category): array => [ 'id' => (int) $category->id, 'slug' => (string) $category->slug, 'name' => (string) $category->name, 'description' => $category->description, ]) ->values() ->all(), 'templates' => NovaCardTemplate::query() ->where('active', true) ->orderBy('order_num') ->orderBy('name') ->get() ->map(fn (NovaCardTemplate $template): array => [ 'id' => (int) $template->id, 'slug' => (string) $template->slug, 'name' => (string) $template->name, 'description' => $template->description, 'preview_image' => $template->preview_image, 'supported_formats' => $template->supported_formats, 'config_json' => $template->config_json, 'official' => (bool) $template->official, ]) ->values() ->all(), 'asset_packs' => $assetPacks ->concat($databasePacks->where('type', NovaCardAssetPack::TYPE_ASSET)->values()) ->values() ->all(), 'template_packs' => $templatePacks ->concat($databasePacks->where('type', NovaCardAssetPack::TYPE_TEMPLATE)->values()) ->values() ->all(), 'challenge_feed' => NovaCardChallenge::query() ->whereIn('status', [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED]) ->orderByDesc('featured') ->orderBy('starts_at') ->limit(8) ->get() ->map(fn (NovaCardChallenge $challenge): array => [ 'id' => (int) $challenge->id, 'slug' => (string) $challenge->slug, 'title' => (string) $challenge->title, 'description' => $challenge->description, 'prompt' => $challenge->prompt, 'status' => (string) $challenge->status, 'official' => (bool) $challenge->official, 'featured' => (bool) $challenge->featured, 'entries_count' => (int) $challenge->entries_count, 'starts_at' => optional($challenge->starts_at)?->toISOString(), 'ends_at' => optional($challenge->ends_at)?->toISOString(), ]) ->values() ->all(), 'validation' => (array) config('nova_cards.validation', []), // v3 additions 'quote_mark_presets' => array_values((array) config('nova_cards.quote_mark_presets', [])), 'text_panel_styles' => array_values((array) config('nova_cards.text_panel_styles', [])), 'frame_presets' => array_values((array) config('nova_cards.frame_presets', [])), 'color_grade_presets' => array_values((array) config('nova_cards.color_grade_presets', [])), 'effect_presets' => array_values((array) config('nova_cards.effect_presets', [])), 'style_families' => array_values((array) config('nova_cards.style_families', [])), 'export_formats' => collect((array) config('nova_cards.export_formats', [])) ->map(fn ($fmt, $key) => array_merge(['key' => $key], (array) $fmt)) ->values() ->all(), ]; } public function optionsWithPresets(array $options, User $user): array { $presets = NovaCardCreatorPreset::query() ->where('user_id', $user->id) ->orderByDesc('is_default') ->orderBy('name') ->get() ->groupBy('preset_type') ->map(fn ($group) => $group->map(fn ($p) => [ 'id' => (int) $p->id, 'name' => (string) $p->name, 'preset_type' => (string) $p->preset_type, 'config_json' => $p->config_json ?? [], 'is_default' => (bool) $p->is_default, ])->values()->all()) ->all(); return array_merge($options, ['creator_presets' => $presets]); } public function card(NovaCard $card, bool $withProject = false, ?User $viewer = null): array { $card->loadMissing(['user.profile', 'category', 'template', 'backgroundImage', 'tags', 'originalCard', 'rootCard']); $project = $this->normalizer->normalizeForCard($card); $viewerCollections = $viewer ? NovaCardCollection::query() ->where('user_id', $viewer->id) ->whereHas('cards', fn ($query) => $query->where('nova_cards.id', $card->id)) ->pluck('id') ->map(fn ($id): int => (int) $id) ->values() ->all() : []; $viewerReactions = $viewer ? NovaCardReaction::query() ->where('user_id', $viewer->id) ->where('card_id', $card->id) ->pluck('type') ->all() : []; $moderationReasons = $this->moderation->storedReasons($card); $moderationOverride = $this->moderation->latestOverride($card); return [ 'id' => (int) $card->id, 'uuid' => (string) $card->uuid, 'title' => (string) $card->title, 'slug' => (string) $card->slug, 'quote_text' => (string) $card->quote_text, 'quote_author' => $card->quote_author, 'quote_source' => $card->quote_source, 'description' => $card->description, 'format' => (string) $card->format, 'visibility' => (string) $card->visibility, 'status' => (string) $card->status, 'moderation_status' => (string) $card->moderation_status, 'moderation_reasons' => $moderationReasons, 'moderation_reason_labels' => $this->moderation->labelsFor($moderationReasons), 'moderation_source' => $this->moderation->storedSource($card), 'moderation_override' => $moderationOverride, 'moderation_override_history' => $this->moderation->overrideHistory($card), 'featured' => (bool) $card->featured, 'allow_download' => (bool) $card->allow_download, 'background_type' => (string) $card->background_type, 'background_image_id' => $card->background_image_id ? (int) $card->background_image_id : null, 'template_id' => $card->template_id ? (int) $card->template_id : null, 'category_id' => $card->category_id ? (int) $card->category_id : null, 'preview_url' => $card->previewUrl(), 'og_preview_url' => $card->ogPreviewUrl(), 'public_url' => $card->publicUrl(), 'published_at' => optional($card->published_at)?->toISOString(), 'render_version' => (int) $card->render_version, 'schema_version' => (int) $card->schema_version, 'views_count' => (int) $card->views_count, 'shares_count' => (int) $card->shares_count, 'downloads_count' => (int) $card->downloads_count, 'likes_count' => (int) $card->likes_count, 'favorites_count' => (int) $card->favorites_count, 'saves_count' => (int) $card->saves_count, 'remixes_count' => (int) $card->remixes_count, 'comments_count' => (int) $card->comments_count, 'challenge_entries_count' => (int) $card->challenge_entries_count, 'allow_remix' => (bool) $card->allow_remix, 'allow_background_reuse' => (bool) $card->allow_background_reuse, 'allow_export' => (bool) $card->allow_export, 'style_family' => $card->style_family, 'palette_family' => $card->palette_family, 'editor_mode_last_used' => $card->editor_mode_last_used, 'featured_score' => $card->featured_score !== null ? (float) $card->featured_score : null, 'last_engaged_at' => optional($card->last_engaged_at)?->toISOString(), 'last_ranked_at' => optional($card->last_ranked_at)?->toISOString(), 'creator' => [ 'id' => (int) $card->user->id, 'username' => (string) $card->user->username, 'name' => $card->user->name, ], 'category' => $card->category ? [ 'id' => (int) $card->category->id, 'slug' => (string) $card->category->slug, 'name' => (string) $card->category->name, ] : null, 'template' => $card->template ? [ 'id' => (int) $card->template->id, 'slug' => (string) $card->template->slug, 'name' => (string) $card->template->name, 'description' => $card->template->description, 'config_json' => $card->template->config_json, 'supported_formats' => $card->template->supported_formats, ] : null, 'background_image' => $card->backgroundImage ? [ 'id' => (int) $card->backgroundImage->id, 'processed_url' => $card->backgroundImage->processedUrl(), 'width' => (int) $card->backgroundImage->width, 'height' => (int) $card->backgroundImage->height, ] : null, 'tags' => $card->tags->map(fn ($tag): array => [ 'id' => (int) $tag->id, 'slug' => (string) $tag->slug, 'name' => (string) $tag->name, ])->values()->all(), 'lineage' => [ 'original_card_id' => $card->original_card_id ? (int) $card->original_card_id : null, 'root_card_id' => $card->root_card_id ? (int) $card->root_card_id : null, 'original_card' => $card->originalCard ? [ 'id' => (int) $card->originalCard->id, 'title' => (string) $card->originalCard->title, 'slug' => (string) $card->originalCard->slug, ] : null, 'root_card' => $card->rootCard ? [ 'id' => (int) $card->rootCard->id, 'title' => (string) $card->rootCard->title, 'slug' => (string) $card->rootCard->slug, ] : null, ], 'version_count' => $card->relationLoaded('versions') ? $card->versions->count() : $card->versions()->count(), 'can_edit' => $viewer ? $card->isOwnedBy($viewer) : false, 'viewer_state' => [ 'liked' => in_array(NovaCardReaction::TYPE_LIKE, $viewerReactions, true), 'favorited' => in_array(NovaCardReaction::TYPE_FAVORITE, $viewerReactions, true), 'saved_collection_ids' => $viewerCollections, ], 'project_json' => $withProject ? $project : null, ]; } public function cards(iterable $cards, bool $withProject = false, ?User $viewer = null): array { return collect($cards) ->map(fn (NovaCard $card): array => $this->card($card, $withProject, $viewer)) ->values() ->all(); } public function collection(NovaCardCollection $collection, ?User $viewer = null, bool $withCards = false): array { $collection->loadMissing(['user', 'items.card.user.profile', 'items.card.category', 'items.card.template', 'items.card.backgroundImage', 'items.card.tags']); $items = $collection->items ->filter(fn ($item): bool => $item->card !== null && $item->card->canBeViewedBy($viewer)) ->sortBy('sort_order') ->values(); return [ 'id' => (int) $collection->id, 'slug' => (string) $collection->slug, 'name' => (string) $collection->name, 'description' => $collection->description, 'visibility' => (string) $collection->visibility, 'official' => (bool) $collection->official, 'featured' => (bool) $collection->featured, 'cards_count' => (int) $collection->cards_count, 'public_url' => $collection->publicUrl(), 'owner' => [ 'id' => (int) $collection->user->id, 'username' => (string) $collection->user->username, 'name' => $collection->user->name, ], 'cover_card' => $items->isNotEmpty() ? $this->card($items->first()->card, false, $viewer) : null, 'items' => $withCards ? $items->map(fn ($item): array => [ 'id' => (int) $item->id, 'note' => $item->note, 'sort_order' => (int) $item->sort_order, 'card' => $this->card($item->card, false, $viewer), ])->values()->all() : [], ]; } public function paginator(LengthAwarePaginator $paginator, bool $withProject = false, ?User $viewer = null): array { return [ 'data' => $this->cards($paginator->items(), $withProject, $viewer), 'meta' => [ 'current_page' => $paginator->currentPage(), 'last_page' => $paginator->lastPage(), 'per_page' => $paginator->perPage(), 'total' => $paginator->total(), 'from' => $paginator->firstItem(), 'to' => $paginator->lastItem(), ], ]; } }