Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fee084d77 | ||
|
|
2965706b7e | ||
|
|
243aa3bbce | ||
|
|
7983a6875f | ||
|
|
6507da1610 | ||
|
|
a47b5d3d92 | ||
|
|
bd90b4a21f | ||
|
|
d0344d5d17 | ||
|
|
deb840e9bd | ||
|
|
d2b0cc5008 | ||
|
|
822c69944c | ||
|
|
f2608f1172 |
@@ -1,4 +1,6 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
# Source: @avalon-vanguard/config. EditorConfig cannot extend a file, so each repo commits a copy:
|
||||
# cp node_modules/@avalon-vanguard/config/.editorconfig .editorconfig
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: CI
|
||||
|
||||
# Gitea port of .github/workflows/ci.yml, through the organisation's shared workflow
|
||||
# (avalon-vanguard/ci, node.yml): lint, typecheck, unit tests, production build, then the
|
||||
# Playwright suite. Same triggers: pull requests and the branches they merge into, plus manual
|
||||
# dispatch.
|
||||
#
|
||||
# A .gitea/workflows directory makes Gitea ignore .github/workflows, so pages.yml,
|
||||
# data-refresh.yml and junie-review.yml now run on GitHub only, until they are ported.
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
# A second push to the same branch makes the first run's answer irrelevant.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
uses: avalon-vanguard/ci/.gitea/workflows/node.yml@v1
|
||||
# Pas d'e2e pour l'instant : la carte 3D rendue en logiciel par Chromium, sur ng serve,
|
||||
# dépasse le runner du VPS (17 tests, 7 en échec par délai dépassé en 16,8 min). À
|
||||
# remettre (e2e: true) une fois le runner capable de les passer.
|
||||
secrets: inherit
|
||||
@@ -0,0 +1 @@
|
||||
@avalon-vanguard:registry=https://git.avalonvanguard.com/api/packages/avalon-vanguard/npm/
|
||||
+1
-12
@@ -1,12 +1 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.html",
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"@avalon-vanguard/config/prettier/angular"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// @ts-check
|
||||
const { defineConfig } = require('eslint/config');
|
||||
const avalonBase = require('@avalon-vanguard/config/eslint');
|
||||
const avalonAngular = require('@avalon-vanguard/config/eslint/angular');
|
||||
|
||||
module.exports = defineConfig([
|
||||
{
|
||||
// Build, test and Playwright output (see .gitignore): ESLint does not read .gitignore.
|
||||
ignores: [
|
||||
'dist/**',
|
||||
'coverage/**',
|
||||
'.angular/**',
|
||||
'node_modules/**',
|
||||
'out-tsc/**',
|
||||
'tools/etl/.cache/**',
|
||||
'e2e/test-output/**',
|
||||
'test-results/**',
|
||||
'playwright-report/**',
|
||||
'blob-report/**',
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
extends: [avalonBase, avalonAngular.typescript],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{ type: 'attribute', prefix: 'app', style: 'camelCase' },
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{ type: 'element', prefix: 'app', style: 'kebab-case' },
|
||||
],
|
||||
},
|
||||
},
|
||||
avalonAngular.templates,
|
||||
]);
|
||||
Generated
+1433
-841
File diff suppressed because it is too large
Load Diff
+8
-1
@@ -7,11 +7,13 @@
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"lint": "eslint .",
|
||||
"etl": "tsx tools/etl/build.ts",
|
||||
"etl:typecheck": "tsc -p tools/etl/tsconfig.json --noEmit",
|
||||
"e2e": "playwright test",
|
||||
"e2e:typecheck": "tsc -p e2e/tsconfig.json --noEmit",
|
||||
"worker:typecheck": "tsc -p tsconfig.worker.json --noEmit"
|
||||
"worker:typecheck": "tsc -p tsconfig.worker.json --noEmit",
|
||||
"typecheck": "npm run etl:typecheck && npm run e2e:typecheck && npm run worker:typecheck"
|
||||
},
|
||||
"private": true,
|
||||
"packageManager": "npm@11.12.1",
|
||||
@@ -33,16 +35,21 @@
|
||||
"@angular/build": "^22.0.6",
|
||||
"@angular/cli": "^22.0.6",
|
||||
"@angular/compiler-cli": "^22.0.0",
|
||||
"@avalon-vanguard/config": "^1.0.0",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.61.1",
|
||||
"@tailwindcss/postcss": "^4.3.2",
|
||||
"@types/node": "^26.1.1",
|
||||
"@types/three": "^0.185.1",
|
||||
"angular-eslint": "^22.5.0",
|
||||
"eslint": "^10.10.0",
|
||||
"jsdom": "^28.0.0",
|
||||
"postcss": "^8.5.19",
|
||||
"prettier": "^3.8.1",
|
||||
"tailwindcss": "^4.3.2",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.70.0",
|
||||
"vitest": "^4.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"local>avalon-vanguard/renovate-config",
|
||||
"local>avalon-vanguard/renovate-config:angular"
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NgZone } from '@angular/core';
|
||||
import { Injector, NgZone, runInInjectionContext } from '@angular/core';
|
||||
import * as THREE from 'three/webgpu';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
@@ -10,7 +10,8 @@ import { EngineService } from './engine.service';
|
||||
* arithmetic that keeps them showing the same thing.
|
||||
*/
|
||||
function engineWithCameras(): { engine: EngineService; perspective: THREE.PerspectiveCamera; orthographic: THREE.OrthographicCamera } {
|
||||
const engine = new EngineService({ runOutsideAngular: (fn: () => unknown) => fn() } as unknown as NgZone);
|
||||
const zone = { runOutsideAngular: (fn: () => unknown) => fn() } as unknown as NgZone;
|
||||
const engine = runInInjectionContext(Injector.create({ providers: [{ provide: NgZone, useValue: zone }] }), () => new EngineService());
|
||||
const perspective = new THREE.PerspectiveCamera(50, 16 / 9, 0.1, 1000);
|
||||
const orthographic = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 1000);
|
||||
// The two cameras are private, because nothing outside should choose between them by hand.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable, NgZone } from '@angular/core';
|
||||
import { inject, Injectable, NgZone } from '@angular/core';
|
||||
import * as THREE from 'three/webgpu';
|
||||
|
||||
export type EngineTickCallback = (deltaSeconds: number, elapsedSeconds: number) => void;
|
||||
@@ -20,6 +20,7 @@ export type SceneCamera = THREE.PerspectiveCamera | THREE.OrthographicCamera;
|
||||
*/
|
||||
@Injectable()
|
||||
export class EngineService {
|
||||
private readonly ngZone = inject(NgZone);
|
||||
private readonly clock = new THREE.Clock(false);
|
||||
private readonly tickCallbacks = new Set<EngineTickCallback>();
|
||||
|
||||
@@ -36,8 +37,6 @@ export class EngineService {
|
||||
private projection: Projection = 'perspective';
|
||||
private running = false;
|
||||
|
||||
constructor(private readonly ngZone: NgZone) {}
|
||||
|
||||
get isInitialized(): boolean {
|
||||
return !!this.renderer;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, OnDestroy, signal, viewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, ElementRef, inject, OnDestroy, signal, viewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import * as THREE from 'three/webgpu';
|
||||
@@ -65,6 +65,12 @@ const GLOW_SCALE = 2.6;
|
||||
`
|
||||
})
|
||||
export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
|
||||
private readonly engine = inject(EngineService);
|
||||
private readonly dataLoader = inject(DataLoaderService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
private readonly navigationStore = inject(NavigationStore);
|
||||
|
||||
/** A kept place, revisited from this page: a star means leaving it for the map. */
|
||||
goToBookmark(bookmark: Bookmark): void {
|
||||
if (bookmark.kind === 'star') {
|
||||
@@ -95,14 +101,6 @@ export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
|
||||
readonly viewModel = signal<BodyDetailViewModel | undefined>(undefined);
|
||||
readonly notFound = signal(false);
|
||||
|
||||
constructor(
|
||||
private readonly engine: EngineService,
|
||||
private readonly dataLoader: DataLoaderService,
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly router: Router,
|
||||
private readonly navigationStore: NavigationStore
|
||||
) {}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
void this.bootstrap();
|
||||
}
|
||||
@@ -273,7 +271,6 @@ export class BodyDetailSceneComponent implements AfterViewInit, OnDestroy {
|
||||
scene.add(sunLight);
|
||||
|
||||
const geometry = new THREE.SphereGeometry(1, 64, 48);
|
||||
const viewModel = this.viewModel();
|
||||
this.planetMaterial = new THREE.MeshStandardMaterial({
|
||||
// White, always: the map that arrives a moment later carries the colour, whether it is a
|
||||
// photograph or a surface derived from the body's own measurements.
|
||||
|
||||
@@ -98,6 +98,8 @@ import { ReadoutSectionsComponent } from './readout-sections.component';
|
||||
export class InfoPanelComponent {
|
||||
readonly body = input.required<BodyDetailViewModel>();
|
||||
|
||||
private readonly router = inject(Router);
|
||||
|
||||
readonly bookmarks = inject(BookmarksStore);
|
||||
|
||||
private readonly articles = inject(ArticleService);
|
||||
@@ -105,7 +107,7 @@ export class InfoPanelComponent {
|
||||
readonly article = signal<Article | null>(null);
|
||||
readonly aboutState = signal<'idle' | 'loading' | 'none' | 'unavailable'>('idle');
|
||||
|
||||
constructor(private readonly router: Router) {
|
||||
constructor() {
|
||||
// The panel is reused as the route's parameter changes, so what was asked about one body
|
||||
// must not still be showing under the next one's name.
|
||||
effect(() => {
|
||||
|
||||
@@ -604,7 +604,7 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => {
|
||||
}
|
||||
|
||||
it('links the stars being drawn, and asks again once a new set of them holds still', async () => {
|
||||
const links = vi.fn((_rangePc: number, _drawn: Uint32Array) => Promise.resolve(new Float32Array(0)));
|
||||
const links = vi.fn<LinkScene['routing']['links']>(() => Promise.resolve(new Float32Array(0)));
|
||||
const component = linkScene(links);
|
||||
await settle();
|
||||
expect(links).toHaveBeenCalledTimes(1);
|
||||
@@ -625,7 +625,7 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => {
|
||||
});
|
||||
|
||||
it('asks for as much of the graph as a million pixels of line make, around where the view is centred', async () => {
|
||||
const links = vi.fn((_rangePc: number, _drawn: Uint32Array, _budget?: LinkBudget) => Promise.resolve(new Float32Array(0)));
|
||||
const links = vi.fn<LinkScene['routing']['links']>(() => Promise.resolve(new Float32Array(0)));
|
||||
Object.defineProperty((fixture.nativeElement as HTMLElement).querySelector('canvas')!, 'clientHeight', { value: 1080 });
|
||||
// A screen scaled to 200%: 1080 CSS pixels are 2160 drawn ones, and the lines are drawn in those.
|
||||
engine.pixelRatio = 2;
|
||||
@@ -641,7 +641,7 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => {
|
||||
|
||||
it('asks again once the view has zoomed past the budget it asked with, though the drawn stars are the same', async () => {
|
||||
// All three stars fit the star budget, so the drawn set never changes: only the budget can.
|
||||
const links = vi.fn((_rangePc: number, _drawn: Uint32Array, _budget?: LinkBudget) => Promise.resolve(new Float32Array(0)));
|
||||
const links = vi.fn<LinkScene['routing']['links']>(() => Promise.resolve(new Float32Array(0)));
|
||||
Object.defineProperty((fixture.nativeElement as HTMLElement).querySelector('canvas')!, 'clientHeight', { value: 1080 });
|
||||
const component = linkScene(links);
|
||||
await advanceFrames(engine, 0.3);
|
||||
@@ -658,7 +658,7 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => {
|
||||
});
|
||||
|
||||
it('asks for no graph from inside a system, where distances are in astronomical units', async () => {
|
||||
const links = vi.fn((_rangePc: number, _drawn: Uint32Array, _budget?: LinkBudget) => Promise.resolve(new Float32Array(0)));
|
||||
const links = vi.fn<LinkScene['routing']['links']>(() => Promise.resolve(new Float32Array(0)));
|
||||
navigationStore.selectStar(SUN.id);
|
||||
await flushAsync();
|
||||
await advanceFrames(engine, 2.5);
|
||||
@@ -695,7 +695,7 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => {
|
||||
});
|
||||
|
||||
it('gives a view on the move a new graph at least every quarter second, rather than waiting for it to stop', async () => {
|
||||
const links = vi.fn((_rangePc: number, _drawn: Uint32Array) => Promise.resolve(new Float32Array(0)));
|
||||
const links = vi.fn<LinkScene['routing']['links']>(() => Promise.resolve(new Float32Array(0)));
|
||||
const component = linkScene(links);
|
||||
await settle();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, computed, effect, ElementRef, OnDestroy, signal, viewChild } from '@angular/core';
|
||||
import { AfterViewInit, Component, computed, effect, ElementRef, inject, OnDestroy, signal, viewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import * as THREE from 'three/webgpu';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
@@ -323,6 +323,11 @@ function galacticOverviewPose(): { position: THREE.Vector3; target: THREE.Vector
|
||||
`
|
||||
})
|
||||
export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
private readonly engine = inject(EngineService);
|
||||
private readonly dataLoader = inject(DataLoaderService);
|
||||
private readonly router = inject(Router);
|
||||
readonly navigationStore = inject(NavigationStore);
|
||||
|
||||
private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
|
||||
private readonly labelHostRef = viewChild.required<ElementRef<HTMLDivElement>>('labelHost');
|
||||
private readonly leaderRef = viewChild.required<ElementRef<SVGLineElement>>('leader');
|
||||
@@ -467,12 +472,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
private starMarker?: THREE.Mesh;
|
||||
private starGlow?: THREE.Sprite;
|
||||
|
||||
constructor(
|
||||
private readonly engine: EngineService,
|
||||
private readonly dataLoader: DataLoaderService,
|
||||
private readonly router: Router,
|
||||
readonly navigationStore: NavigationStore
|
||||
) {
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const selectedStarId = this.navigationStore.selectedStarId();
|
||||
if (this.ready) {
|
||||
@@ -484,7 +484,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
// Reads both signals, so flipping the layer on and dragging the range each land here.
|
||||
effect(() => {
|
||||
this.jumpRangePc();
|
||||
this.display().jumpLinks;
|
||||
void this.display().jumpLinks;
|
||||
this.scheduleJumpLinks();
|
||||
});
|
||||
}
|
||||
@@ -1804,8 +1804,6 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
private swapToSystemSpace(star: StarRecord, direction: THREE.Vector3, onComplete: () => void): void {
|
||||
const camera = this.engine.getCamera();
|
||||
|
||||
this.systemRenderer?.dispose();
|
||||
if (this.starMarker) {
|
||||
this.systemGroup.remove(this.starMarker);
|
||||
@@ -1912,7 +1910,6 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
private swapToGalaxySpace(exitingStarId: number, direction: THREE.Vector3, isSwitchingSystems: boolean, onComplete: () => void): void {
|
||||
const camera = this.engine.getCamera();
|
||||
const star = this.starsById.get(exitingStarId);
|
||||
const starPc = star ? new THREE.Vector3(star.x, star.y, star.z) : GALAXY_OVERVIEW_TARGET.clone();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as THREE from 'three/webgpu';
|
||||
|
||||
import { GALACTIC_BASIS_EQUATORIAL, SUN_HEIGHT_ABOVE_MIDPLANE_PC } from '../../shared/astro/galaxy';
|
||||
import { GALACTIC_BASIS_EQUATORIAL } from '../../shared/astro/galaxy';
|
||||
|
||||
const SEGMENTS_PER_RING = 180;
|
||||
|
||||
|
||||
@@ -87,9 +87,9 @@ export function createRandom(seed: number): () => number {
|
||||
|
||||
/** Standard normal sample, by the polar form of Box-Muller. */
|
||||
function gaussian(random: () => number): number {
|
||||
let u = 0;
|
||||
let v = 0;
|
||||
let s = 0;
|
||||
let u: number;
|
||||
let v: number;
|
||||
let s: number;
|
||||
do {
|
||||
u = random() * 2 - 1;
|
||||
v = random() * 2 - 1;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { BrightnessIndex, brightnessIndex, Positioned } from '../../shared/astro
|
||||
import { spectralTypeToColorIndex } from '../../shared/astro/spectral';
|
||||
import { SceneCamera } from '../../core/engine/engine.service';
|
||||
import { StarRecord } from '../../shared/models/star.model';
|
||||
import { PIXELS_TO_ANGULAR_SIZE, REFERENCE_FOV_DEGREES, REFERENCE_VIEWPORT_HEIGHT_PX } from './angular-size';
|
||||
import { PIXELS_TO_ANGULAR_SIZE, REFERENCE_FOV_DEGREES } from './angular-size';
|
||||
|
||||
/** Apparent star diameters, in pixels at {@link REFERENCE_VIEWPORT_HEIGHT_PX}. */
|
||||
const MIN_POINT_SIZE = 1.5;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, computed, ElementRef, output, signal, viewChild } from '@angular/core';
|
||||
import { Component, computed, ElementRef, inject, output, signal, viewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { DataLoaderService } from '../../core/data/data-loader.service';
|
||||
@@ -80,6 +80,10 @@ const KIND_LABELS: Record<SearchResultKind, string> = {
|
||||
`
|
||||
})
|
||||
export class SearchComponent {
|
||||
private readonly dataLoader = inject(DataLoaderService);
|
||||
private readonly navigationStore = inject(NavigationStore);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
readonly query = signal('');
|
||||
/** Fires once a result has been chosen and navigation kicked off — the dock uses it to hand
|
||||
* the view back to the readout, since the thing to look at is now the scene, not the box. */
|
||||
@@ -109,11 +113,7 @@ export class SearchComponent {
|
||||
readonly matchTotal = computed(() => this.matches().length);
|
||||
readonly results = computed(() => this.matches().slice(0, MAX_RESULTS));
|
||||
|
||||
constructor(
|
||||
private readonly dataLoader: DataLoaderService,
|
||||
private readonly navigationStore: NavigationStore,
|
||||
private readonly router: Router
|
||||
) {
|
||||
constructor() {
|
||||
void this.buildIndex();
|
||||
}
|
||||
|
||||
|
||||
@@ -115,11 +115,12 @@ describe('renderPlanetTexture', () => {
|
||||
it('produces no NaN or out-of-range bytes for any class', () => {
|
||||
for (const planetClass of ALL_CLASSES) {
|
||||
const pixels = renderPlanetTexture(appearanceOf(planetClass, { polarCapExtentDeg: 30 }), SIZE);
|
||||
for (const value of pixels) {
|
||||
expect(Number.isInteger(value)).toBe(true);
|
||||
expect(value).toBeGreaterThanOrEqual(0);
|
||||
expect(value).toBeLessThanOrEqual(255);
|
||||
}
|
||||
// One assertion per class, not three per byte: 245 760 expect() calls ran past the
|
||||
// default 5 s timeout on a slower CI machine.
|
||||
const firstBad = pixels.findIndex(
|
||||
(value) => !Number.isInteger(value) || value < 0 || value > 255,
|
||||
);
|
||||
expect(firstBad, planetClass).toBe(-1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ export class BookmarksStore {
|
||||
}
|
||||
|
||||
private read(): Bookmark[] {
|
||||
let raw: string | null = null;
|
||||
let raw: string | null;
|
||||
try {
|
||||
raw = localStorage.getItem(STORAGE_KEY);
|
||||
} catch {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-17
@@ -1,24 +1,9 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
/* Compiler and Angular options come from @avalon-vanguard/config (tsconfig/angular.json). */
|
||||
{
|
||||
"extends": "@avalon-vanguard/config/tsconfig/angular.json",
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true
|
||||
},
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user