diff --git a/.eslintrc.json b/.eslintrc.json index 2d75e61a..cfcd7b02 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,6 +24,13 @@ "style": "camelCase" } ], + "@angular-eslint/component-max-inline-declarations": [ + "warn", + { + "template": 6, + "styles": 6 + } + ], "@angular-eslint/component-selector": [ "error", { diff --git a/public/icons/github-icon.svg b/public/icons/github-icon.svg new file mode 100644 index 00000000..f376c3ef --- /dev/null +++ b/public/icons/github-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/logo-header.png b/public/logo-header.png new file mode 100644 index 00000000..e37500b6 Binary files /dev/null and b/public/logo-header.png differ diff --git a/scripts/set-env.js b/scripts/set-env.js index 549af173..0fed2340 100644 --- a/scripts/set-env.js +++ b/scripts/set-env.js @@ -6,7 +6,8 @@ dotenv.config({ path: path.resolve(__dirname, '../.env') }); const envFilePath = path.resolve(__dirname, '../src/environment.ts'); const output = `export const env = { - api_url: "https://api-opis.unictdev.org/api/v2" + api_url: "https://api-opis.unictdev.org/api/v2", + github_api_url: "https://api.github.com/repos/UNICT-DMI" };`; fs.writeFileSync(envFilePath, output); diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 5afc5503..bc79f260 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,12 +1,18 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { provideRouter, withInMemoryScrolling } from '@angular/router'; import { routes } from './app.routes'; import { provideCharts, withDefaultRegisterables } from 'ng2-charts'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), - provideRouter(routes), + provideRouter( + routes, + withInMemoryScrolling({ + scrollPositionRestoration: 'top', + anchorScrolling: 'enabled', + }), + ), provideCharts(withDefaultRegisterables()), ], }; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 8b42205b..aa95a84b 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -2,13 +2,14 @@ import { Routes } from '@angular/router'; import { DepartmentPageComponent } from './pages/department/department'; import { InfoPageComponent } from './pages/info/info'; import { HomePageComponent } from './pages/home/home'; +import { FormulaComponent } from './pages/formula/formula'; export const routes: Routes = [ - { path: 'home', component: HomePageComponent }, - { path: ':depsName', component: DepartmentPageComponent }, + { path: '', component: HomePageComponent }, + { path: 'formula', component: FormulaComponent }, { path: 'info', component: InfoPageComponent }, - { path: '**', redirectTo: '/home' }, - // { path: 'formula', }, - // { path: 'signup', }, // { path: 'login', } + // { path: 'signup', }, + { path: 'department/:depsName', component: DepartmentPageComponent }, + { path: '**', redirectTo: '', pathMatch: 'full' }, ]; diff --git a/src/app/app.scss b/src/app/app.scss index e69de29b..58376808 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -0,0 +1,9 @@ +:host { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.opis-app { + flex: 1 0 auto; +} diff --git a/src/app/app.spec.ts b/src/app/app.spec.ts index 2f16bf8d..23f0493f 100644 --- a/src/app/app.spec.ts +++ b/src/app/app.spec.ts @@ -1,11 +1,13 @@ import { TestBed } from '@angular/core/testing'; import { describe, it, expect } from 'vitest'; import { App } from './app'; +import { provideRouter } from '@angular/router'; describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], + providers: [provideRouter([])], }).compileComponents(); }); diff --git a/src/app/app.ts b/src/app/app.ts index 2441a2a3..6124f885 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,10 +1,16 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import { Footer } from '@sections/footer/footer'; +import { HeaderNav } from '@sections/header-nav/header-nav'; @Component({ selector: 'opis-root', - imports: [RouterOutlet], - template: '', + imports: [HeaderNav, RouterOutlet, Footer], + template: ` +
+ +
+ `, styleUrl: './app.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/src/app/custom_types/icon-dimension.type.ts b/src/app/custom_types/icon-dimension.type.ts new file mode 100644 index 00000000..81932eb0 --- /dev/null +++ b/src/app/custom_types/icon-dimension.type.ts @@ -0,0 +1,9 @@ +export type IconDimension = + | '1-5rem' + | '2rem' + | '2-5rem' + | '3rem' + | '3-5rem' + | '4rem' + | '4-5rem' + | '5rem'; diff --git a/src/app/interfaces/github.interface.ts b/src/app/interfaces/github.interface.ts new file mode 100644 index 00000000..a127f88e --- /dev/null +++ b/src/app/interfaces/github.interface.ts @@ -0,0 +1,37 @@ +export interface CacheEntry { + timestamp: number; + data: T; +} + +export interface GithubUser { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + user_view_type: string; + site_admin: boolean; + contributions: number; +} + +export interface GitUserView { + nick: string; + contributions: number; + email?: string; + linkedin?: string; + telegram?: string; + name?: string; + github_profile?: string; +} diff --git a/src/app/interfaces/header-nav-interface.ts b/src/app/interfaces/header-nav-interface.ts new file mode 100644 index 00000000..229e49ab --- /dev/null +++ b/src/app/interfaces/header-nav-interface.ts @@ -0,0 +1,10 @@ +import { IconDimension } from '@c_types/icon-dimension.type'; + +export interface NavItem { + label: string; + route: string; + icon?: { + name: string; + dimension: IconDimension; + }; +} diff --git a/src/app/pages/department/department.ts b/src/app/pages/department/department.ts index e7f31b64..304c2743 100644 --- a/src/app/pages/department/department.ts +++ b/src/app/pages/department/department.ts @@ -15,15 +15,15 @@ import { RouterLink } from '@angular/router'; import { CdsService } from '@services/cds/cds.service'; import { CDS } from '@interfaces/cds.interface'; import { NO_CHOICE_CDS } from '@values/no-choice-cds'; -import { Loader } from '@components/loader/loader'; -import { Icon } from '@components/icon/icon'; import { CdsSelectedSection } from '@sections/cds-selected-section/cds-selected-section'; import { QuestionService } from '@services/questions/questions.service'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { IconComponent } from '@shared-ui/icon/icon'; +import { Loader } from '@shared-ui/loader/loader'; @Component({ selector: 'opis-department', - imports: [RouterLink, Loader, Icon, CdsSelectedSection], + imports: [RouterLink, Loader, IconComponent, CdsSelectedSection], templateUrl: './department.html', styleUrl: './department.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/pages/formula/formula.html b/src/app/pages/formula/formula.html new file mode 100644 index 00000000..28200486 --- /dev/null +++ b/src/app/pages/formula/formula.html @@ -0,0 +1 @@ +

formula works!

diff --git a/src/app/pages/formula/formula.scss b/src/app/pages/formula/formula.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/pages/formula/formula.spec.ts b/src/app/pages/formula/formula.spec.ts new file mode 100644 index 00000000..3847c362 --- /dev/null +++ b/src/app/pages/formula/formula.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormulaComponent } from './formula'; + +describe('Formula', () => { + let component: FormulaComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FormulaComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(FormulaComponent); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/formula/formula.ts b/src/app/pages/formula/formula.ts new file mode 100644 index 00000000..c237f80e --- /dev/null +++ b/src/app/pages/formula/formula.ts @@ -0,0 +1,10 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'opis-formula', + imports: [], + templateUrl: './formula.html', + styleUrl: './formula.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FormulaComponent {} diff --git a/src/app/pages/home/home.ts b/src/app/pages/home/home.ts index e07625b2..b3d6b62e 100644 --- a/src/app/pages/home/home.ts +++ b/src/app/pages/home/home.ts @@ -1,9 +1,9 @@ import { ChangeDetectionStrategy, Component, computed, inject, OnDestroy } from '@angular/core'; import { DepCard } from '@cards/dep-card/dep-card'; -import { LogoAnimated } from '@components/logo-animated/logo-animated'; import { DepartmentsService } from '@services/departments/departments.service'; -import { Loader } from '@components/loader/loader'; import { YearSection } from '@sections/year-section/year-section'; +import { Loader } from '@shared-ui/loader/loader'; +import { LogoAnimated } from '@shared-ui/logo-animated/logo-animated'; @Component({ selector: 'opis-home', diff --git a/src/app/services/github/github.service.ts b/src/app/services/github/github.service.ts new file mode 100644 index 00000000..07a7381e --- /dev/null +++ b/src/app/services/github/github.service.ts @@ -0,0 +1,53 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable, signal } from '@angular/core'; +import { env } from '@env'; +import { CacheEntry, GithubUser, GitUserView } from '@interfaces/github.interface'; +import { REAL_NAME } from '@values/real-name-contributors.value'; +import { lastValueFrom } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class GitHubService { + private readonly REPO_NAME = 'OPIS-Manager'; + private readonly BASE_URL = env.github_api_url + '/' + this.REPO_NAME; + private readonly _http = inject(HttpClient); + + private cacheTTL = 0.5 * 60 * 60 * 1000; // 30 min + + readonly contributors = signal([]); + + async getRepoContributors(): Promise { + const cacheKey = `contributors_${this.REPO_NAME}`; + const cachedRaw = localStorage.getItem(cacheKey); + const now = Date.now(); + + if (cachedRaw) { + try { + const cached = JSON.parse(cachedRaw) as CacheEntry; + if (now - cached.timestamp < this.cacheTTL) { + return cached.data; + } + } catch { + console.warn('No cache founded or expired, calling api'); + } + } + + const url = `${this.BASE_URL}/contributors`; + try { + const contributors = await lastValueFrom(this._http.get(url)); + const noBots = contributors.filter((user) => !user.login.includes('bot')); + const mapped = noBots.map((user) => ({ + nick: user.login ?? 'Unknown', + name: REAL_NAME.get(user.login.toLowerCase()) ?? '', + contributions: user.contributions ?? 0, + github_profile: user.html_url, + })); + + const sorted = mapped.sort((userA, userB) => userB.contributions - userA.contributions); + localStorage.setItem(cacheKey, JSON.stringify({ timestamp: now, data: sorted })); + return sorted; + } catch (err) { + console.error(`Errore nel recuperare i contributors di ${this.REPO_NAME}`, err); + return []; + } + } +} diff --git a/src/app/services/icon-registry/icon-registry.service.ts b/src/app/services/icon-registry/icon-registry.service.ts new file mode 100644 index 00000000..3f4b30b2 --- /dev/null +++ b/src/app/services/icon-registry/icon-registry.service.ts @@ -0,0 +1,22 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, shareReplay, map } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class IconRegistryService { + private readonly _http = inject(HttpClient); + private readonly cache = new Map<'github' | 'linkedin', Observable>(); + + load(name: 'github' | 'linkedin'): Observable { + if (!this.cache.has(name)) { + const request$ = this._http.get(`/icons/${name}-icon.svg`, { responseType: 'text' }).pipe( + map((svg) => svg.trim()), + shareReplay(1), + ); + + this.cache.set(name, request$); + } + + return this.cache.get(name)!; + } +} diff --git a/src/app/ui/cards/dep-card/dep-card.html b/src/app/ui/cards/dep-card/dep-card.html index bf80e919..771a9ae3 100644 --- a/src/app/ui/cards/dep-card/dep-card.html +++ b/src/app/ui/cards/dep-card/dep-card.html @@ -1,5 +1,5 @@
- +
diff --git a/src/app/ui/cards/dep-card/dep-card.ts b/src/app/ui/cards/dep-card/dep-card.ts index 152a3a1e..952b8137 100644 --- a/src/app/ui/cards/dep-card/dep-card.ts +++ b/src/app/ui/cards/dep-card/dep-card.ts @@ -1,11 +1,11 @@ import { ChangeDetectionStrategy, Component, input, OnInit } from '@angular/core'; import { RouterLink } from '@angular/router'; -import { Icon } from '@components/icon/icon'; import { Department } from '@interfaces/department.interface'; +import { IconComponent } from '@shared-ui/icon/icon'; @Component({ selector: 'opis-dep-card', - imports: [RouterLink, Icon], + imports: [RouterLink, IconComponent], templateUrl: './dep-card.html', styleUrl: './dep-card.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/ui/components/icon/icon.ts b/src/app/ui/components/icon/icon.ts deleted file mode 100644 index 4f27742b..00000000 --- a/src/app/ui/components/icon/icon.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; - -@Component({ - selector: 'opis-icon', - template: `{{ - iconName() - }}`, - // eslint-disable-next-line @angular-eslint/component-max-inline-declarations - styles: ` - span { - display: block; - - &.bigger { - font-size: 5rem; - } - } - `, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class Icon { - readonly iconName = input.required(); - readonly isBig = input(false); -} diff --git a/src/app/ui/sections/cds-selected-section/cds-selected-section.html b/src/app/ui/sections/cds-selected-section/cds-selected-section.html index f0ca06c3..14284ce4 100644 --- a/src/app/ui/sections/cds-selected-section/cds-selected-section.html +++ b/src/app/ui/sections/cds-selected-section/cds-selected-section.html @@ -8,7 +8,7 @@ @if (infoCds.status() === ERR_STATUS) {
- +

Qualcosa è andato storto :/

} diff --git a/src/app/ui/sections/cds-selected-section/cds-selected-section.ts b/src/app/ui/sections/cds-selected-section/cds-selected-section.ts index 1cef420f..dc59354e 100644 --- a/src/app/ui/sections/cds-selected-section/cds-selected-section.ts +++ b/src/app/ui/sections/cds-selected-section/cds-selected-section.ts @@ -5,14 +5,14 @@ import { inject, ResourceStatus, } from '@angular/core'; -import { Graph } from '@components/graph/graph'; -import { Icon } from '@components/icon/icon'; -import { Loader } from '@components/loader/loader'; import { CdsService } from '@services/cds/cds.service'; +import { Graph } from '@shared-ui/graph/graph'; +import { IconComponent } from '@shared-ui/icon/icon'; +import { Loader } from '@shared-ui/loader/loader'; @Component({ selector: 'opis-cds-selected-section', - imports: [Icon, Loader, Graph], + imports: [IconComponent, Loader, Graph], templateUrl: './cds-selected-section.html', styleUrl: './cds-selected-section.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/ui/sections/footer/footer.html b/src/app/ui/sections/footer/footer.html new file mode 100644 index 00000000..a8fb93d6 --- /dev/null +++ b/src/app/ui/sections/footer/footer.html @@ -0,0 +1,28 @@ +
diff --git a/src/app/ui/sections/footer/footer.scss b/src/app/ui/sections/footer/footer.scss new file mode 100644 index 00000000..e2403ddc --- /dev/null +++ b/src/app/ui/sections/footer/footer.scss @@ -0,0 +1,35 @@ +.opis-footer { + width: 100%; + margin: 20px 0px; + background-color: var(--dark-bg); + color: var(--gray-400); + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: center; + + .opis-license { + display: inline-flex; + align-items: center; + justify-content: start; + gap: 0.2rem; + + small { + text-decoration: underline; + margin-bottom: 0.4rem; + display: block; + } + } + + .opis-quote { + display: flex; + justify-content: center; + gap: 1.2rem; + text-align: center; + } + + @media (min-width: 800px) { + justify-content: space-between; + padding: 0.5rem 2rem; + } +} diff --git a/src/app/ui/sections/footer/footer.ts b/src/app/ui/sections/footer/footer.ts new file mode 100644 index 00000000..8d87404a --- /dev/null +++ b/src/app/ui/sections/footer/footer.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core'; +import { IconRegistryService } from '@services/icon-registry/icon-registry.service'; +import { IconComponent } from '@shared-ui/icon/icon'; + +@Component({ + selector: 'opis-footer', + imports: [IconComponent], + templateUrl: './footer.html', + styleUrl: './footer.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Footer implements OnInit { + private readonly _iconRegistryService = inject(IconRegistryService); + + protected readonly githubIcon = signal(''); + + ngOnInit(): void { + this._iconRegistryService.load('github').subscribe((icon) => { + this.githubIcon.set(icon); + }); + } +} diff --git a/src/app/ui/sections/header-nav/header-nav.html b/src/app/ui/sections/header-nav/header-nav.html new file mode 100644 index 00000000..cde8d807 --- /dev/null +++ b/src/app/ui/sections/header-nav/header-nav.html @@ -0,0 +1,20 @@ + diff --git a/src/app/ui/sections/header-nav/header-nav.scss b/src/app/ui/sections/header-nav/header-nav.scss new file mode 100644 index 00000000..2a19ed15 --- /dev/null +++ b/src/app/ui/sections/header-nav/header-nav.scss @@ -0,0 +1,47 @@ +.opis-header { + background-color: var(--dark-bg); + + .header-content { + margin: 0 auto; + width: 100%; + padding: 0.5rem 1rem; + display: inline-flex; + gap: 1rem; + align-items: end; + justify-content: space-between; + color: var(--color-surface); + font-size: 1.2rem; + + .opis-logo { + display: inline-flex; + gap: 1rem; + align-items: end; + width: 3rem; + height: 3rem; + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + + h1 { + white-space: nowrap; + font-weight: 400; + font-size: 2rem; + display: none; + + @media (min-width: 600px) { + display: block; + } + } + } + + .end-nav-items { + display: inline-flex; + align-items: center; + gap: 1rem; + padding-bottom: 0.3rem; + } + } +} diff --git a/src/app/ui/sections/header-nav/header-nav.ts b/src/app/ui/sections/header-nav/header-nav.ts new file mode 100644 index 00000000..48251baa --- /dev/null +++ b/src/app/ui/sections/header-nav/header-nav.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { NavItem } from '@interfaces/header-nav-interface'; +import { IconComponent } from '@shared-ui/icon/icon'; + +@Component({ + selector: 'opis-header-nav', + imports: [RouterLink, IconComponent], + templateUrl: './header-nav.html', + styleUrl: './header-nav.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class HeaderNav { + protected readonly NavItems: NavItem[] = [ + { label: 'Formula', route: '/formula' }, + { label: 'Info', route: '/info' }, + { + label: 'Login', + route: '/login', + icon: { + name: 'account_circle', + dimension: '2rem', + }, + }, + ]; +} diff --git a/src/app/ui/shared/graph/grap.html b/src/app/ui/shared/graph/grap.html new file mode 100644 index 00000000..c82ff63b --- /dev/null +++ b/src/app/ui/shared/graph/grap.html @@ -0,0 +1,10 @@ +@if (dataChart()) { +
+ +
+} diff --git a/src/app/ui/shared/graph/graph.scss b/src/app/ui/shared/graph/graph.scss new file mode 100644 index 00000000..3651c161 --- /dev/null +++ b/src/app/ui/shared/graph/graph.scss @@ -0,0 +1,11 @@ +#graph { + width: 93%; + margin: 1rem auto; + display: flex; + justify-content: center; + + @media (min-width: 1300px) { + width: 60%; + margin: 1.5rem auto; + } +} diff --git a/src/app/ui/shared/graph/graph.ts b/src/app/ui/shared/graph/graph.ts new file mode 100644 index 00000000..d2fb2c38 --- /dev/null +++ b/src/app/ui/shared/graph/graph.ts @@ -0,0 +1,88 @@ +import { ChangeDetectionStrategy, Component, input, OnInit } from '@angular/core'; +import { OpisGroup, OpisGroupType } from '@enums/opis-group.enum'; +import { GraphView } from '@interfaces/graph-config.interface'; +import { ChartConfiguration, ChartData } from 'chart.js'; +import { BaseChartDirective } from 'ng2-charts'; + +@Component({ + selector: 'opis-graph', + imports: [BaseChartDirective], + templateUrl: `./grap.html`, + styleUrl: './graph.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Graph implements OnInit { + protected readonly chartOptions = this.setInitOptions(); + + readonly dataChart = input.required(); + protected coloredDataChart: ChartData; + + ngOnInit(): void { + this.addBrandColorToDataset(); + } + + private isOpisGroup(value: unknown): value is OpisGroupType { + return Object.values(OpisGroup).includes(value as OpisGroupType); + } + + private cssVar(name: string): string { + return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); + } + + private hexToRgba(hex: string, alpha: number): string { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + } + + private getColor(labelGroup: OpisGroupType): string { + const blue = this.cssVar('--blue-900'); + const red = this.cssVar('--red-900'); + const sand = this.cssVar('--sand-500'); + const defaultGrey = this.cssVar('--gray-700'); + + const colors = new Map([ + [OpisGroup.Group1, blue], + [OpisGroup.Group2, red], + [OpisGroup.Group3, sand], + ]); + + return colors.get(labelGroup) ?? defaultGrey; + } + + private addBrandColorToDataset(): void { + const datasets = this.dataChart().data.datasets; + + this.dataChart().data.datasets = datasets.map((set) => { + const label = set.label; + + if (!this.isOpisGroup(label)) { + return set; + } + + const color = this.getColor(label); + + return { + ...set, + borderColor: color, + backgroundColor: this.hexToRgba(color, 0.6), + }; + }); + } + + private setInitOptions(): ChartConfiguration['options'] { + return { + // We use these empty structures as placeholders for dynamic theming. + scales: { + x: {}, + y: {}, + }, + plugins: { + legend: { + display: true, + }, + }, + }; + } +} diff --git a/src/app/ui/shared/icon/icon.component.html b/src/app/ui/shared/icon/icon.component.html new file mode 100644 index 00000000..c5422a35 --- /dev/null +++ b/src/app/ui/shared/icon/icon.component.html @@ -0,0 +1,7 @@ +@if (safeSvgIcon()) { + +} @else { + + {{ iconName() }} + +} diff --git a/src/app/ui/shared/icon/icon.component.scss b/src/app/ui/shared/icon/icon.component.scss new file mode 100644 index 00000000..ef374b7c --- /dev/null +++ b/src/app/ui/shared/icon/icon.component.scss @@ -0,0 +1,44 @@ +.opis-icon { + display: inline-flex; + line-height: 1; + + &.size-1-5rem { + font-size: 1.5rem; + } + + &.size-2rem { + font-size: 2rem; + } + + &.size-2-5rem { + font-size: 2.5rem; + } + + &.size-3rem { + font-size: 3rem; + } + + &.size-3-5rem { + font-size: 3.5rem; + } + + &.size-4rem { + font-size: 4rem; + } + + &.size-4-5rem { + font-size: 4.5rem; + } + + &.size-5rem { + font-size: 5rem; + } +} + +/* QUESTO È IL FIX */ +:host ::ng-deep svg { + width: 1em; + height: 1em; + display: block; + fill: currentColor; +} diff --git a/src/app/ui/shared/icon/icon.ts b/src/app/ui/shared/icon/icon.ts new file mode 100644 index 00000000..2ba90289 --- /dev/null +++ b/src/app/ui/shared/icon/icon.ts @@ -0,0 +1,25 @@ +import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { IconDimension } from '@c_types/icon-dimension.type'; + +@Component({ + selector: 'opis-icon', + templateUrl: './icon.component.html', + styleUrl: './icon.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IconComponent { + private readonly _sanitizer = inject(DomSanitizer); + + readonly iconName = input(); + readonly dimension = input('1-5rem'); + readonly svgIcon = input(); + + protected readonly safeSvgIcon = computed(() => { + const svgIcon = this.svgIcon(); + if (svgIcon) { + return this._sanitizer.bypassSecurityTrustHtml(svgIcon); + } + return null; + }); +} diff --git a/src/app/ui/components/loader/loader.ts b/src/app/ui/shared/loader/loader.ts similarity index 100% rename from src/app/ui/components/loader/loader.ts rename to src/app/ui/shared/loader/loader.ts diff --git a/src/app/ui/components/logo-animated/logo-animated.html b/src/app/ui/shared/logo-animated/logo-animated.html similarity index 100% rename from src/app/ui/components/logo-animated/logo-animated.html rename to src/app/ui/shared/logo-animated/logo-animated.html diff --git a/src/app/ui/components/logo-animated/logo-animated.scss b/src/app/ui/shared/logo-animated/logo-animated.scss similarity index 100% rename from src/app/ui/components/logo-animated/logo-animated.scss rename to src/app/ui/shared/logo-animated/logo-animated.scss diff --git a/src/app/ui/components/logo-animated/logo-animated.ts b/src/app/ui/shared/logo-animated/logo-animated.ts similarity index 100% rename from src/app/ui/components/logo-animated/logo-animated.ts rename to src/app/ui/shared/logo-animated/logo-animated.ts diff --git a/src/app/values/real-name-contributors.value.ts b/src/app/values/real-name-contributors.value.ts new file mode 100644 index 00000000..3674db03 --- /dev/null +++ b/src/app/values/real-name-contributors.value.ts @@ -0,0 +1,13 @@ +export const REAL_NAME = new Map([ + ['helias', 'Stefano Borzì'], + ['wornairz', 'Alessandro Catalano'], + ['pierpaolo791', 'Pierpaolo Pecoraio'], + ['', 'Simone Scionti'], + ['', 'Alessio Piazza'], + ['', 'Diego Sinitò'], + ['guberlo', 'Salvo Asero'], + ['mkokeshi', 'Giuseppe Ferro'], + ['herbrant', 'Davide Carnemolla'], + ['makapx', 'Martina Parlavecchio'], + ['boozec', 'Santo Cariotti'], +]); diff --git a/src/styles.scss b/src/styles.scss index e9c8ece2..e6e659d9 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,11 +1,16 @@ /* You can add global styles to this file, and also import other style files */ @use 'reset'; @use 'palette'; +@use 'scrollbar'; + +html, +body { + height: 100%; + margin: 0; +} body { font-family: var(--font-sans); - border-top: 2rem solid var(--dark-bg); - border-bottom: 2rem solid var(--dark-bg); color: var(--color-text); } diff --git a/src/styles/_palette.scss b/src/styles/_palette.scss index dde4dc92..fd3fc5d8 100644 --- a/src/styles/_palette.scss +++ b/src/styles/_palette.scss @@ -39,6 +39,7 @@ /* testo principale */ --gray-700: #333333; --gray-500: #5f5f5f; + --gray-400: #9c9c9c; /* testo secondario */ --gray-300: #cfcfcf; --gray-200: #e6e3e0; diff --git a/src/styles/_scrollbar.scss b/src/styles/_scrollbar.scss new file mode 100644 index 00000000..7ba08c09 --- /dev/null +++ b/src/styles/_scrollbar.scss @@ -0,0 +1,26 @@ +/* Target della scrollbar */ +::-webkit-scrollbar { + width: 12px; + /* larghezza verticale */ + height: 12px; + /* altezza orizzontale */ +} + +/* Track (sfondo della scrollbar) */ +::-webkit-scrollbar-track { + background: #ffffff; + border-radius: 6px; +} + +/* Thumb (la parte che si muove) */ +::-webkit-scrollbar-thumb { + background-color: var(--dark-bg); + // border-radius: 6px; + // border: 3px solid #f0f0f0; + /* crea spazio interno attorno al thumb */ +} + +/* Thumb hover */ +::-webkit-scrollbar-thumb:hover { + background-color: var(--gray-7100); +} diff --git a/tsconfig.json b/tsconfig.json index 452961b6..00ad673a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "baseUrl": "./", "paths": { - "@components/*": ["src/app/ui/components/*"], + "@shared-ui/*": ["src/app/ui/shared/*"], "@sections/*": ["src/app/ui/sections/*"], "@cards/*": ["src/app/ui/cards/*"], "@services/*": ["src/app/services/*"],