Creando mi propio IDE con Angular y Node – Parte 3

¿Quieres aprender a crear tu propio IDE con Angular y Node?, chécate esta serie de tutoriales para llegar a crear uno propio de forma fácil y rápida.

Por fin ha llegado la parte 3 de creando mi propio IDE donde empezaremos a conectar todo el Front a la parte Back que hicimos en nuestro tutorial anterior, pero si te lo perdiste, te dejo el enlace:

En esta parte trataremos de conectar algunos de nuestros ends, quizá haya cambios que en las publicaciones anteriores se me haya escapado así que … no me odies, hago esto “al vuelo” 😉

Lo primero es generar un servicio que podamos reusar para hacer llamadas a nuestro “API” que en el tutorial anterior creamos, lo nombraremos http.service.ts y estará en el mismo nivel que app.component.ts en realidad no importa ni su nombre ni ubicación tanto pero ahí lo pondremos:

import {Injectable} from '@angular/core';
import {HttpHeaders, HttpClient} from '@angular/common/http';

import {Observable} from 'rxjs';
import {Router} from '@angular/router';

const API_URL = "http://localhost:3000";

@Injectable({
  providedIn: 'root'
})
export class ApiHttpService {

  public headers: any;

  public apiUrl = API_URL;

  constructor(
    private http: HttpClient,
    public router: Router
  ) {
    this.headers = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        Accept: 'application/json'
      })
    };
  }

  public get(url: string): Observable<any> {
    return this.http.get(
      this.apiUrl + url,
      this.headers
    );
  }

  public post(url: string, body: any): Observable<any> {
    return this.http.post(
      this.apiUrl + url,
      JSON.stringify(body),
      this.headers
    );
  }

  public put(url: string, body: any): Observable<any> {
    return this.http.put(
      this.apiUrl + url,
      JSON.stringify(body),
      this.headers
    );
  }

  public delete(url: string): Observable<any> {
    return this.http.delete(
      this.apiUrl + url,
      this.headers
    );
  }

  public patch(url: string, body: any) {
    return this.http.patch(
      this.apiUrl + url,
      JSON.stringify(body),
      this.headers
    );
  }
}

Notemos que tenemos 1 función por cada método, y que en la parte superior agregamos http://localhost:3000 como la URL ya que nos estaríamos conectando al API en su base… cuando tenemos esto, podemos avanzar.

Ahora, para continuar lo primero que haremos será generar la conexión para obtener un directorio raíz apenas abramos nuestro IDE. Recordando, ya tenemos la base del usuario como directorio principal, así que haremos el pull a “/” y simplemente obtendremos lo que haya.

Hasta aquí antes de implementar esto haré un paréntesis, en el app.module deben cargar el HttpClientModule más o menos así:

// En la parte superior el import:

import {HttpClientModule} from "@angular/common/http";

// Y en los imports:
imports: [
   ...
   HttpClientModule
   ...
]

Ahora sí necesitamos cargar nuestro pedacito de código dentro del constructor de app.component y como import para nuestros “proveedores” de la siguiente forma:

// "Hasta arriba" (o cerquita de arriba en los imports :P):
import {ApiHttpService} from "./http.service";

// En el @Component:
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [ApiHttpService] //<--agregamos esta línea
})

...
// Y en el constructor
constructor(private apiService: ApiHttpService) {
...
}

Abramos otro paréntesis, si hasta aquí ejecutásemos la petición veríamos en consola que nos marca un problemirijillo de CORS, así que hay de 2, hacemos un PROXY para Angular o … abrimos CORS en NodeJS,en este caso haremos lo segundo PERO, te dejo un tutorial si quieres hacerlo desde Angular…

Así que vamos a nuestro proyecto de backend para el IDE e instalamos la siguiente dependencia https://www.npmjs.com/package/cors:

npm install cors

En nuestro archivo index.js lo implementamos:

// Hasta arriba debajo de los require anteriores hacemos el import:
const cors = require('cors');
// Y abajo del app.use(bodyParser...) hacemos el uso:
app.use(cors());

Y eso es todo. Ahora sí podemos iniciar nuestro proyecto y veremos que funciona si hacemos el GET, en nuestro constructor de app.component hacemos la prueba:

constructor(private apiService: ApiHttpService) {
    this.apiService.get('/').subscribe((response) => {
      console.log(response);
    });
    ...
}

Como notita, recuerda, los ... significa que hay algo por ahí, no hagas el copiar/pegar así de agresivo 😛 para que no te salgan errores.

Así que entonces veríamos algo así en la consola:

image

Ignora que tapé mis carpetas con cosas de “adultos” 😉😉(no es cierto son puras cosas de programación)… pero podemos ver que está el nombre y saber si es directorio… o no.

Para integrarlo lo haremos modificando casi en su totalidad el constructor de la siguiente manera:

constructor(private apiService: ApiHttpService) {
    this.items = [];
    this.apiService.get('/').subscribe((response) => {
      const internalItems: any[] = [];
      for(const item of response.datos) {
        internalItems.push({
          text: item.nombre,
          value: item.nombre,
          collapsed: true,
          disabled: false,
        })
      }
      this.items = [new TreeviewItem({
        text: 'ide', value: 1, collapsed: true, disabled: false, children: internalItems
      })];
    });
  }

Si hacemos click donde dice “IDE”, veremos que se despliega nuestro listado:

image 1

Ahora, debido a que Ngx-TreeView tiene una forma peculiar de hacer un select de objetos, lamentable selección a veces de elementos, pero así es esto. Por concluyente, vamos a copiar algo de su código pero a nuestra manera y debido a esto concluiremos este tutorial y nos vamos hasta unos “infinitos” tutoriales (lo siento joven lector, la idea es hacerlos no tan larguísimos y enfadosos):

En nuestro app.component vamos a agregar la siguiente lógica:

// Una variable llamada "value"
value = '';
// Vamos a borrar la variable config

// La siguiente función:
onValueChange(value: number): void {
  console.log(value);
}

Y eso es todo :D, ha no te creas, bueno sí pero solo en ese archivo, lo siguiente es crear un montón de ficheros, te paso los nombres:

dropdown-treeview-select.component.html
dropdown-treeview-select.component.scss
dropdown-treeview-select.component.ts
dropdown-treeview-select-i18n.ts

Y ahora los contenidos, una vez que terminemos esta parte, te mostraré cómo luce y vamos a continuar en la siguiente parte del tutorial porque ya es demasiado por este y no quiero abrumarte…

Dentro del HTML agregamos:

<ng-template #itemTemplate let-item="item" let-onCollapseExpand="onCollapseExpand"
             let-onCheckedChange="onCheckedChange">
  <div class="text-nowrap row-item">
    <i *ngIf="item.children" (click)="onCollapseExpand()" aria-hidden="true" [ngSwitch]="item.collapsed">
      <svg *ngSwitchCase="true" width="0.8rem" height="0.8rem" viewBox="0 0 16 16" class="bi bi-caret-right-fill"
           fill="currentColor" xmlns="http://www.w3.org/2000/svg">
        <path
          d="M12.14 8.753l-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z" />
      </svg>
      <svg *ngSwitchCase="false" width="0.8rem" height="0.8rem" viewBox="0 0 16 16" class="bi bi-caret-down-fill"
           fill="currentColor" xmlns="http://www.w3.org/2000/svg">
        <path
          d="M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z" />
      </svg>
    </i>
    <label class="form-check-label" (click)="select(item)">{{ item.text }}</label>
  </div>
</ng-template>
<ng-template #headerTemplate let-config="config" let-item="item" let-onCollapseExpand="onCollapseExpand"
             let-onCheckedChange="onCheckedChange" let-onFilterTextChange="onFilterTextChange">
  <div *ngIf="config.hasFilter" class="row row-filter">
    <div class="col-12">
      <input class="form-control" type="text" [placeholder]="i18n.getFilterPlaceholder()" [(ngModel)]="filterText"
             (ngModelChange)="onFilterTextChange($event)" />
    </div>
  </div>
  <div *ngIf="config.hasAllCheckBox || config.hasCollapseExpand" class="row">
    <div class="col-12">
      <label *ngIf="config.hasAllCheckBox" (click)="select(item)">
        <strong>{{ i18n.getAllCheckboxText() }}</strong>
      </label>
      <label *ngIf="config.hasCollapseExpand" class="float-right" (click)="onCollapseExpand()">
        <i [title]="i18n.getTooltipCollapseExpandText(item.collapsed)" aria-hidden="true" [ngSwitch]="item.collapsed">
          <svg *ngSwitchCase="true" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrows-angle-expand"
               fill="currentColor" xmlns="http://www.w3.org/2000/svg">
            <path fill-rule="evenodd"
                  d="M1.5 10.036a.5.5 0 0 1 .5.5v3.5h3.5a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5z" />
            <path fill-rule="evenodd"
                  d="M6.354 9.646a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0zm8.5-8.5a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0z" />
            <path fill-rule="evenodd"
                  d="M10.036 1.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 1 1-1 0V2h-3.5a.5.5 0 0 1-.5-.5z" />
          </svg>
          <svg *ngSwitchCase="false" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrows-angle-contract"
               fill="currentColor" xmlns="http://www.w3.org/2000/svg">
            <path fill-rule="evenodd"
                  d="M9.5 2.036a.5.5 0 0 1 .5.5v3.5h3.5a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5z" />
            <path fill-rule="evenodd"
                  d="M14.354 1.646a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 1 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0zm-7.5 7.5a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0z" />
            <path fill-rule="evenodd"
                  d="M2.036 9.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V10h-3.5a.5.5 0 0 1-.5-.5z" />
          </svg>
        </i>
      </label>
    </div>
  </div>
  <div *ngIf="config.hasDivider" class="dropdown-divider"></div>
</ng-template>
<ngx-dropdown-treeview [config]="config" [headerTemplate]="headerTemplate" [items]="items"
                       [itemTemplate]="itemTemplate">
</ngx-dropdown-treeview>

Dentro del archivo TS (que no es el que termina en i18n):

import { Component, Input, Output, EventEmitter, ViewChild, OnChanges } from '@angular/core';
import { TreeviewI18n, TreeviewItem, TreeviewConfig, DropdownTreeviewComponent, TreeviewHelper } from 'ngx-treeview';
import { DropdownTreeviewSelectI18n } from './dropdown-treeview-select-i18n';

@Component({
  selector: 'ngx-dropdown-treeview-select',
  templateUrl: './dropdown-treeview-select.component.html',
  styleUrls: [
    './dropdown-treeview-select.component.scss'
  ],
  providers: [
    { provide: TreeviewI18n, useClass: DropdownTreeviewSelectI18n }
  ]
})
export class DropdownTreeviewSelectComponent implements OnChanges {
  @Input() config: TreeviewConfig;
  @Input() items: TreeviewItem[] = [];
  @Input() value: any;
  @Output() valueChange = new EventEmitter<any>();
  @ViewChild(DropdownTreeviewComponent, { static: false }) dropdownTreeviewComponent: DropdownTreeviewComponent | undefined;
  filterText = '';
  private dropdownTreeviewSelectI18n: DropdownTreeviewSelectI18n;

  constructor(
    public i18n: TreeviewI18n
  ) {
    this.config = TreeviewConfig.create({
      hasAllCheckBox: true,
      hasFilter: true,
      hasCollapseExpand: true,
      decoupleChildFromParent: false,
      maxHeight: 500,
    });
    this.dropdownTreeviewSelectI18n = i18n as DropdownTreeviewSelectI18n;
  }

  ngOnChanges(): void {
    this.updateSelectedItem();
  }

  select(item: TreeviewItem): void {
    if (!item.children) {
      this.selectItem(item);
    }
  }

  private updateSelectedItem(): void {
    if (this.items) {
      const selectedItem = TreeviewHelper.findItemInList(this.items, this.value);
      this.selectItem(selectedItem);
    }
  }

  private selectItem(item: TreeviewItem): void {
    if (this.dropdownTreeviewSelectI18n.selectedItem !== item) {
      this.dropdownTreeviewSelectI18n.selectedItem = item;
      if (this.dropdownTreeviewComponent) {
        this.dropdownTreeviewComponent.onSelectedChange([item]);
      }

      if (item) {
        if (this.value !== item.value) {
          this.value = item.value;
          this.valueChange.emit(item.value);
        }
      }
    }
  }
}

Ahora sí en el archivo TS que es para i18n:

import { Injectable } from '@angular/core';
import { TreeviewItem, TreeviewSelection, DefaultTreeviewI18n } from 'ngx-treeview';

@Injectable()
export class DropdownTreeviewSelectI18n extends DefaultTreeviewI18n {
  private internalSelectedItem: TreeviewItem | undefined;

  set selectedItem(value: TreeviewItem) {
    this.internalSelectedItem = value;
  }

  get selectedItem(): TreeviewItem {
    return <TreeviewItem>this.internalSelectedItem;
  }

  override getText(selection: TreeviewSelection): string {
    return this.internalSelectedItem ? this.internalSelectedItem.text : 'Please select';
  }
}

Por último en el SCSS:

label {
  margin-bottom: 0;
  cursor: pointer;
}

.bi {
  cursor: pointer;
  margin-right: 0.3rem;
}

Y ya para finalizar, importamos el Componente en el app.module :

// Como de costumbre el import arriba:
import {DropdownTreeviewSelectComponent} from "./dropdown-treeview-select.component";

// Y en las declarations:
  declarations: [
    AppComponent,
    DropdownTreeviewSelectComponent
  ],

¡Listo!, si hicimos todo bien, deberíamos ver lo siguiente:

image 2

Y dirás, ¿aquí qué?, lo veo casi igual pues no, si cliqueamos el “ide” entonces veríamos la lista y podemos seleccionar cosas, por ejemplo:

image 3

Con esto, ya podemos ir empezando a abrir archivos, pero como dije, no quiero abrumarte, así que nos vemos en la cuarta entrega…

¿Cuál es tu reacción?
+1
0
+1
0
+1
0
+1
0
+1
1
Total
0
Shares
Publicación anterior
security protection anti virus software 60504

Encontrar código malintencionado en PHP con grep en Linux

Siguiente Publicación
javascript logo

Capitalizar todas las palabras con JS

Publicaciones Relacionadas