Skip to content

Commit 1c9f0ef

Browse files
committed
extract code (mainly html) from main component
1 parent 4c70af2 commit 1c9f0ef

File tree

7 files changed

+264
-159
lines changed

7 files changed

+264
-159
lines changed

src/app/components/command-builder/command-builder.component.ts

Lines changed: 37 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { Component, signal, computed, effect, inject, OnInit, viewChild } from '@angular/core';
22
import { ActivatedRoute } from '@angular/router';
33
import { Title } from '@angular/platform-browser';
4-
import { FormsModule } from '@angular/forms';
5-
import { CommonModule } from '@angular/common';
64
import { CommandService } from '../../services/command.service';
7-
import { Command, Flag, Option } from '../../models/command.model';
5+
import { Command } from '../../models/command.model';
86
import { 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

1014
interface 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
})
237130
export 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
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Component, input } from '@angular/core';
2+
import { Command } from '../../models/command.model';
3+
4+
@Component({
5+
selector: 'app-command-header',
6+
template: `
7+
@if (command(); as cmd) {
8+
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
9+
<h1 class="text-3xl font-bold mb-2 font-mono">{{ cmd.name }}</h1>
10+
<p class="text-gray-600">
11+
{{ cmd.description }}
12+
@if (cmd.link) {
13+
<a [href]="cmd.link" target="_blank" rel="noopener noreferrer" class="ml-2 text-blue-500 hover:text-blue-700 underline">
14+
View documentation
15+
</a>
16+
}
17+
</p>
18+
</div>
19+
}
20+
`
21+
})
22+
export class CommandHeaderComponent {
23+
command = input.required<Command>();
24+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Component, input, output } from '@angular/core';
2+
3+
export interface CommandExample {
4+
command: string;
5+
description: string;
6+
presets: Record<string, any>;
7+
}
8+
9+
@Component({
10+
selector: 'app-example-card',
11+
template: `
12+
@if (example(); as ex) {
13+
<div class="border border-gray-200 rounded-md p-4 hover:border-blue-300 transition-colors">
14+
<code class="text-sm font-mono text-blue-600 block mb-2 break-all">
15+
{{ ex.command }}
16+
</code>
17+
<p class="text-xs text-gray-600 mb-3">{{ ex.description }}</p>
18+
<button
19+
(click)="apply.emit()"
20+
class="w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded-md transition-colors"
21+
[attr.aria-label]="'Apply example: ' + ex.description"
22+
>
23+
Apply Example
24+
</button>
25+
</div>
26+
}
27+
`
28+
})
29+
export class ExampleCardComponent {
30+
example = input.required<CommandExample>();
31+
apply = output<void>();
32+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Component, input, output } from '@angular/core';
2+
import { Flag } from '../../models/command.model';
3+
4+
@Component({
5+
selector: 'app-flag-item',
6+
template: `
7+
@if (flag(); as f) {
8+
<div class="flex items-start space-x-3 p-3 hover:bg-gray-50 rounded-md">
9+
<input
10+
type="checkbox"
11+
[id]="'flag-' + f.id"
12+
[checked]="selected()"
13+
(change)="toggle.emit()"
14+
class="mt-1 w-4 h-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
15+
/>
16+
<label [attr.for]="'flag-' + f.id" class="flex-1 cursor-pointer">
17+
<div class="font-mono font-semibold text-blue-600">{{ f.flag }}</div>
18+
<div class="text-sm text-gray-600">
19+
{{ f.description }}
20+
@if (f.link) {
21+
<a [href]="f.link" target="_blank" rel="noopener noreferrer" class="ml-1 text-blue-500 hover:text-blue-700 underline" (click)="$event.stopPropagation()">
22+
Learn more
23+
</a>
24+
}
25+
</div>
26+
</label>
27+
</div>
28+
}
29+
`
30+
})
31+
export class FlagItemComponent {
32+
flag = input.required<Flag>();
33+
selected = input.required<boolean>();
34+
toggle = output<void>();
35+
}

0 commit comments

Comments
 (0)