11import { Component , signal , computed , effect , inject , OnInit , viewChild } from '@angular/core' ;
22import { ActivatedRoute } from '@angular/router' ;
33import { Title } from '@angular/platform-browser' ;
4- import { FormsModule } from '@angular/forms' ;
5- import { CommonModule } from '@angular/common' ;
64import { CommandService } from '../../services/command.service' ;
7- import { Command , Flag , Option } from '../../models/command.model' ;
5+ import { Command } from '../../models/command.model' ;
86import { CommandHistoryComponent } from '../command-history/command-history.component' ;
7+ import { LoadingSpinnerComponent } from './loading-spinner.component' ;
8+ import { CommandHeaderComponent } from './command-header.component' ;
9+ import { GeneratedCommandDisplayComponent } from './generated-command-display.component' ;
10+ import { FlagItemComponent } from './flag-item.component' ;
11+ import { OptionItemComponent } from './option-item.component' ;
12+ import { ExampleCardComponent , CommandExample } from './example-card.component' ;
913
1014interface FlagState {
1115 id : string ;
@@ -20,154 +24,54 @@ interface OptionState {
2024
2125@Component ( {
2226 selector : 'app-command-builder' ,
23- imports : [ FormsModule , CommonModule , CommandHistoryComponent ] ,
27+ imports : [
28+ CommandHistoryComponent ,
29+ LoadingSpinnerComponent ,
30+ CommandHeaderComponent ,
31+ GeneratedCommandDisplayComponent ,
32+ FlagItemComponent ,
33+ OptionItemComponent ,
34+ ExampleCardComponent
35+ ] ,
2436 template : `
2537 @if (isLoading()) {
26- <div class="container mx-auto px-4 py-8">
27- <div class="bg-white rounded-lg shadow-md p-12 text-center">
28- <div class="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mb-4"></div>
29- <h2 class="text-xl font-semibold text-gray-800">Loading command...</h2>
30- </div>
31- </div>
38+ <app-loading-spinner />
3239 } @else if (command(); as cmd) {
3340 <div class="container mx-auto px-4 py-8 max-w-6xl">
34- <!-- Command Header -->
35- <div class="bg-white rounded-lg shadow-md p-6 mb-6">
36- <h1 class="text-3xl font-bold mb-2 font-mono">{{ cmd.name }}</h1>
37- <p class="text-gray-600">
38- {{ cmd.description }}
39- @if (cmd.link) {
40- <a [href]="cmd.link" target="_blank" rel="noopener noreferrer" class="ml-2 text-blue-500 hover:text-blue-700 underline">
41- View documentation
42- </a>
43- }
44- </p>
45- </div>
41+ <app-command-header [command]="cmd" />
4642
47- <!-- Generated Command -->
48- <div class="bg-gray-900 text-green-400 rounded-lg shadow-md p-6 mb-6 sticky top-0 z-10 ">
49- <div class="flex items-center justify-between mb-2">
50- <h2 class="text-lg font-semibold">Generated Command</h2>
51- <button
52- (click)="copyToClipboard()"
53- class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors"
54- [attr.aria-label]="'Copy command to clipboard'"
55- >
56- {{ copyButtonText() }}
57- </button>
58- </div>
59- <code class="text-xl font-mono block break-all">{{ generatedCommand() }}</code>
60- </div>
43+ <app-generated-command-display [command]="generatedCommand()" />
6144
6245 <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
6346 <!-- Left Column: Flags and Options -->
6447 <div class="lg:col-span-2 space-y-6">
65- <!-- Flags Section -->
6648 @if (cmd.flags.length > 0) {
6749 <div class="bg-white rounded-lg shadow-md p-6">
6850 <h2 class="text-2xl font-bold mb-4">Flags</h2>
6951 <div class="space-y-3">
7052 @for (flag of cmd.flags; track flag.id) {
71- <div class="flex items-start space-x-3 p-3 hover:bg-gray-50 rounded-md">
72- <input
73- type="checkbox"
74- [id]="'flag-' + flag.id"
75- [checked]="getFlagState(flag.id)"
76- (change)="toggleFlag(flag.id)"
77- class="mt-1 w-4 h-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
78- />
79- <label [attr.for]="'flag-' + flag.id" class="flex-1 cursor-pointer">
80- <div class="font-mono font-semibold text-blue-600">{{ flag.flag }}</div>
81- <div class="text-sm text-gray-600">
82- {{ flag.description }}
83- @if (flag.link) {
84- <a [href]="flag.link" target="_blank" rel="noopener noreferrer" class="ml-1 text-blue-500 hover:text-blue-700 underline" (click)="$event.stopPropagation()">
85- Learn more
86- </a>
87- }
88- </div>
89- </label>
90- </div>
53+ <app-flag-item
54+ [flag]="flag"
55+ [selected]="getFlagState(flag.id)"
56+ (toggle)="toggleFlag(flag.id)"
57+ />
9158 }
9259 </div>
9360 </div>
9461 }
9562
96- <!-- Options Section -->
9763 @if (cmd.options.length > 0) {
9864 <div class="bg-white rounded-lg shadow-md p-6">
9965 <h2 class="text-2xl font-bold mb-4">Options</h2>
10066 <div class="space-y-4">
10167 @for (option of cmd.options; track option.id) {
102- <div class="p-4 border border-gray-200 rounded-md hover:border-blue-300 transition-colors">
103- <div class="flex items-start space-x-3 mb-3">
104- <input
105- type="checkbox"
106- [id]="'option-' + option.id"
107- [checked]="getOptionState(option.id).selected"
108- (change)="toggleOption(option.id)"
109- class="mt-1 w-4 h-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
110- />
111- <label [attr.for]="'option-' + option.id" class="flex-1 cursor-pointer">
112- @if (option.option) {
113- <div class="font-mono font-semibold text-blue-600">{{ option.option }}</div>
114- }
115- <div class="text-sm text-gray-600">
116- {{ option.description }}
117- @if (option.link) {
118- <a [href]="option.link" target="_blank" rel="noopener noreferrer" class="ml-1 text-blue-500 hover:text-blue-700 underline" (click)="$event.stopPropagation()">
119- Learn more
120- </a>
121- }
122- </div>
123- </label>
124- </div>
125-
126- @if (option.parameter && getOptionState(option.id).selected) {
127- <div class="ml-7 mt-2">
128- @if (option.parameter.type === 'text') {
129- <input
130- type="text"
131- [id]="'param-' + option.id"
132- [value]="getOptionState(option.id).value || ''"
133- (input)="updateOptionValue(option.id, $event)"
134- [placeholder]="option.parameter.placeholder || ''"
135- class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
136- [attr.aria-label]="option.description + ' value'"
137- />
138- }
139-
140- @if (option.parameter.type === 'number') {
141- <input
142- type="number"
143- [id]="'param-' + option.id"
144- [value]="getOptionState(option.id).value || ''"
145- (input)="updateOptionValue(option.id, $event)"
146- [placeholder]="option.parameter.placeholder || ''"
147- class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
148- [attr.aria-label]="option.description + ' value'"
149- />
150- }
151-
152- @if (option.parameter.type === 'enum' && option.parameter.enumValues) {
153- <select
154- [id]="'param-' + option.id"
155- [value]="getOptionState(option.id).value || option.parameter.defaultValue || ''"
156- (change)="updateOptionValue(option.id, $event)"
157- class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
158- [attr.aria-label]="option.description + ' value'"
159- >
160- <option value="">Select...</option>
161- @for (enumVal of option.parameter.enumValues; track enumVal.value) {
162- <option [value]="enumVal.value" [title]="enumVal.description || ''">
163- {{ enumVal.label }}
164- </option>
165- }
166- </select>
167- }
168- </div>
169- }
170- </div>
68+ <app-option-item
69+ [option]="option"
70+ [selected]="getOptionState(option.id).selected"
71+ [value]="getOptionState(option.id).value"
72+ (toggle)="toggleOption(option.id)"
73+ (valueChange)="updateOptionValue(option.id, $event)"
74+ />
17175 }
17276 </div>
17377 </div>
@@ -181,19 +85,10 @@ interface OptionState {
18185 <h2 class="text-xl font-bold mb-4">Examples</h2>
18286 <div class="space-y-4">
18387 @for (example of cmd.examples; track example.command) {
184- <div class="border border-gray-200 rounded-md p-4 hover:border-blue-300 transition-colors">
185- <code class="text-sm font-mono text-blue-600 block mb-2 break-all">
186- {{ example.command }}
187- </code>
188- <p class="text-xs text-gray-600 mb-3">{{ example.description }}</p>
189- <button
190- (click)="applyExample(example)"
191- class="w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded-md transition-colors"
192- [attr.aria-label]="'Apply example: ' + example.description"
193- >
194- Apply Example
195- </button>
196- </div>
88+ <app-example-card
89+ [example]="example"
90+ (apply)="applyExample(example)"
91+ />
19792 }
19893 </div>
19994 </div>
@@ -218,7 +113,6 @@ interface OptionState {
218113 </div>
219114 </div>
220115
221- <!-- Command History -->
222116 <div class="mt-6">
223117 <app-command-history [commandId]="cmd.id" />
224118 </div>
@@ -231,8 +125,7 @@ interface OptionState {
231125 </div>
232126 </div>
233127 }
234- ` ,
235- styles : ``
128+ `
236129} )
237130export class CommandBuilderComponent implements OnInit {
238131 private route = inject ( ActivatedRoute ) ;
@@ -244,7 +137,6 @@ export class CommandBuilderComponent implements OnInit {
244137 isLoading = signal ( true ) ;
245138 private flagStates = signal < Map < string , FlagState > > ( new Map ( ) ) ;
246139 private optionStates = signal < Map < string , OptionState > > ( new Map ( ) ) ;
247- copyButtonText = signal ( 'Copy' ) ;
248140 saveButtonText = signal ( 'Save to History' ) ;
249141
250142 generatedCommand = computed ( ( ) => {
@@ -369,26 +261,22 @@ export class CommandBuilderComponent implements OnInit {
369261 }
370262 }
371263
372- applyExample ( example : any ) : void {
264+ applyExample ( example : CommandExample ) : void {
373265 const cmd = this . command ( ) ;
374266 if ( ! cmd ) return ;
375267
376- // Reset states
377268 this . initializeStates ( cmd ) ;
378269
379- // Apply presets
380270 const flagMap = new Map ( this . flagStates ( ) ) ;
381271 const optionMap = new Map ( this . optionStates ( ) ) ;
382272
383273 Object . keys ( example . presets ) . forEach ( key => {
384274 const preset = example . presets [ key ] ;
385275
386- // Check if it's a flag
387276 if ( flagMap . has ( key ) ) {
388277 flagMap . set ( key , { id : key , selected : preset . selected } ) ;
389278 }
390279
391- // Check if it's an option
392280 if ( optionMap . has ( key ) ) {
393281 optionMap . set ( key , {
394282 id : key ,
@@ -414,19 +302,9 @@ export class CommandBuilderComponent implements OnInit {
414302 const cmdString = this . generatedCommand ( ) ;
415303 if ( cmd && cmdString ) {
416304 this . commandService . saveToHistory ( cmd . id , cmdString ) ;
417- // Refresh history instantly
418305 this . historyComponent ( ) ?. loadHistory ( ) ;
419- // Show feedback
420306 this . saveButtonText . set ( '✓ Saved!' ) ;
421307 setTimeout ( ( ) => this . saveButtonText . set ( 'Save to History' ) , 2000 ) ;
422308 }
423309 }
424-
425- copyToClipboard ( ) : void {
426- const cmd = this . generatedCommand ( ) ;
427- navigator . clipboard . writeText ( cmd ) . then ( ( ) => {
428- this . copyButtonText . set ( 'Copied!' ) ;
429- setTimeout ( ( ) => this . copyButtonText . set ( 'Copy' ) , 2000 ) ;
430- } ) ;
431- }
432310}
0 commit comments