up edit
This commit is contained in:
parent
f991aee8ed
commit
bdb3728494
13 changed files with 283 additions and 20 deletions
|
@ -1,3 +0,0 @@
|
||||||
html{
|
|
||||||
font-family: "Calibri", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
|
@ -94,10 +94,12 @@
|
||||||
@if (featureId()) {
|
@if (featureId()) {
|
||||||
<button class="btn btn-ghost" type="button" (click)="onDelete()">Supprimer</button>
|
<button class="btn btn-ghost" type="button" (click)="onDelete()">Supprimer</button>
|
||||||
}
|
}
|
||||||
|
<button class="btn btn-ghost" type="button" (click)="onCancelEdit()">Quitter l’édition</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (status().state !== 'idle') {
|
@if (status().state !== 'idle') {
|
||||||
<div class="status">
|
<div class="toast-container">
|
||||||
|
<div class="toast" [class.is-info]="status().state==='saving'" [class.is-success]="status().state==='saved'" [class.is-error]="status().state==='error'">
|
||||||
@if (status().state === 'saving') {
|
@if (status().state === 'saving') {
|
||||||
<div>{{status().message}}</div>
|
<div>{{status().message}}</div>
|
||||||
} @else if (status().state === 'saved') {
|
} @else if (status().state === 'saved') {
|
||||||
|
@ -109,5 +111,6 @@
|
||||||
<div>{{status().message}}</div>
|
<div>{{status().message}}</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -220,10 +220,12 @@ export class EditForm implements OnChanges {
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
this.status.set({ state: 'saved', what: val.what, message: 'Évènement mis à jour' });
|
this.status.set({ state: 'saved', what: val.what, message: 'Évènement mis à jour' });
|
||||||
this.saved.emit(res);
|
this.saved.emit(res);
|
||||||
|
setTimeout(() => this.status.set({ state: 'idle' }), 3000);
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.status.set({ state: 'error', what: val.what, message: 'Erreur lors de la mise à jour' });
|
this.status.set({ state: 'error', what: val.what, message: 'Erreur lors de la mise à jour' });
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
setTimeout(() => this.status.set({ state: 'idle' }), 3000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -231,10 +233,12 @@ export class EditForm implements OnChanges {
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
this.status.set({ state: 'saved', what: val.what, message: 'Évènement créé' });
|
this.status.set({ state: 'saved', what: val.what, message: 'Évènement créé' });
|
||||||
this.created.emit(res);
|
this.created.emit(res);
|
||||||
|
setTimeout(() => this.status.set({ state: 'idle' }), 3000);
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.status.set({ state: 'error', what: val.what, message: 'Erreur lors de la création' });
|
this.status.set({ state: 'error', what: val.what, message: 'Erreur lors de la création' });
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
setTimeout(() => this.status.set({ state: 'idle' }), 3000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -254,14 +258,36 @@ export class EditForm implements OnChanges {
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
this.status.set({ state: 'saved', what: this.form.value.what, message: 'Évènement supprimé' });
|
this.status.set({ state: 'saved', what: this.form.value.what, message: 'Évènement supprimé' });
|
||||||
this.deleted.emit(res);
|
this.deleted.emit(res);
|
||||||
|
setTimeout(() => this.status.set({ state: 'idle' }), 3000);
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.status.set({ state: 'error', what: this.form.value.what, message: 'Erreur lors de la suppression' });
|
this.status.set({ state: 'error', what: this.form.value.what, message: 'Erreur lors de la suppression' });
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
setTimeout(() => this.status.set({ state: 'idle' }), 3000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCancelEdit() {
|
||||||
|
this.selected = null;
|
||||||
|
this.featureId.set(null);
|
||||||
|
this.form.reset({
|
||||||
|
label: '',
|
||||||
|
description: '',
|
||||||
|
what: '',
|
||||||
|
where: '',
|
||||||
|
lat: '',
|
||||||
|
lon: '',
|
||||||
|
wikidata: '',
|
||||||
|
featureType: 'point',
|
||||||
|
type: 'unscheduled',
|
||||||
|
start: this.toLocalInputValue(new Date()),
|
||||||
|
stop: this.toLocalInputValue(new Date(new Date().getTime() + 24 * 3600 * 1000))
|
||||||
|
});
|
||||||
|
this.presetValues.set({});
|
||||||
|
this.status.set({ state: 'idle' });
|
||||||
|
}
|
||||||
|
|
||||||
private toLocalInputValue(d: string | Date): string {
|
private toLocalInputValue(d: string | Date): string {
|
||||||
const date = (typeof d === 'string') ? new Date(d) : d;
|
const date = (typeof d === 'string') ? new Date(d) : d;
|
||||||
if (Number.isNaN(date.getTime())) return '';
|
if (Number.isNaN(date.getTime())) return '';
|
||||||
|
|
|
@ -1 +1,17 @@
|
||||||
<p>osm works!</p>
|
<p>
|
||||||
|
osm works!
|
||||||
|
|
||||||
|
|
||||||
|
@if(isLogginIn){
|
||||||
|
<div class="pseudo">
|
||||||
|
{{osmPseudo}}
|
||||||
|
</div>
|
||||||
|
<button (click)="logout()">logout</button>
|
||||||
|
}
|
||||||
|
@else{
|
||||||
|
<div class="pseudo">
|
||||||
|
pas connecté
|
||||||
|
</div>
|
||||||
|
<button (click)="login()">osm login</button>
|
||||||
|
}
|
||||||
|
</p>
|
|
@ -7,5 +7,14 @@ import { Component } from '@angular/core';
|
||||||
styleUrl: './osm.scss'
|
styleUrl: './osm.scss'
|
||||||
})
|
})
|
||||||
export class Osm {
|
export class Osm {
|
||||||
|
osmPseudo: string='';
|
||||||
|
isLogginIn: any = false;
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,4 +4,9 @@
|
||||||
<input #searchBox type="text" placeholder="Chercher un lieu (Nominatim)" class="input" style="flex:1;">
|
<input #searchBox type="text" placeholder="Chercher un lieu (Nominatim)" class="input" style="flex:1;">
|
||||||
<button class="btn" type="button" (click)="searchPlace(searchBox.value)">Chercher</button>
|
<button class="btn" type="button" (click)="searchPlace(searchBox.value)">Chercher</button>
|
||||||
</div>
|
</div>
|
||||||
|
@if (canRestoreOriginal) {
|
||||||
|
<div style="position:absolute;bottom:10px;left:10px;display:flex;gap:8px;">
|
||||||
|
<button class="btn btn-ghost" type="button" (click)="restoreOriginalCoords()">Reprendre les coordonnées initiales</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
@keyframes pulseGreen {
|
||||||
|
0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4); }
|
||||||
|
70% { box-shadow: 0 0 0 12px rgba(76, 175, 80, 0); }
|
||||||
|
100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); }
|
||||||
|
}
|
||||||
|
@keyframes pulseRed {
|
||||||
|
0% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.4); }
|
||||||
|
70% { box-shadow: 0 0 0 12px rgba(244, 67, 54, 0); }
|
||||||
|
100% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-feature-id].pulse-green {
|
||||||
|
animation: pulseGreen 1.2s ease-out 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
[data-feature-id].pulse-red {
|
||||||
|
animation: pulseRed 1.2s ease-out 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import oedb_what_categories from '../../../oedb-types';
|
||||||
})
|
})
|
||||||
export class AllEvents {
|
export class AllEvents {
|
||||||
@Input() features: Array<any> = [];
|
@Input() features: Array<any> = [];
|
||||||
|
@Input() selected: any | null = null;
|
||||||
|
@Input() highlight: { id: string | number, type: 'saved' | 'deleted' } | null = null;
|
||||||
@Output() select = new EventEmitter<any>();
|
@Output() select = new EventEmitter<any>();
|
||||||
@Output() pickCoords = new EventEmitter<[number, number]>();
|
@Output() pickCoords = new EventEmitter<[number, number]>();
|
||||||
|
|
||||||
|
@ -17,6 +19,8 @@ export class AllEvents {
|
||||||
private map: any;
|
private map: any;
|
||||||
private markers: any[] = [];
|
private markers: any[] = [];
|
||||||
private pickedMarker: any | null = null;
|
private pickedMarker: any | null = null;
|
||||||
|
private originalCoords: [number, number] | null = null;
|
||||||
|
private currentPicked: [number, number] | null = null;
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.ensureMapLibre();
|
await this.ensureMapLibre();
|
||||||
|
@ -31,7 +35,28 @@ export class AllEvents {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
|
// track original coordinates of the selected feature
|
||||||
|
if (this.selected && Array.isArray(this.selected?.geometry?.coordinates)) {
|
||||||
|
const coords = this.selected.geometry.coordinates as [number, number];
|
||||||
|
this.originalCoords = coords;
|
||||||
|
// If no picked marker yet, align current picked to original
|
||||||
|
if (!this.currentPicked) this.currentPicked = coords;
|
||||||
|
}
|
||||||
this.renderFeatures();
|
this.renderFeatures();
|
||||||
|
|
||||||
|
// trigger animation highlight
|
||||||
|
if (this.highlight && this.highlight.id !== undefined && this.highlight.id !== null) {
|
||||||
|
const idStr = String(this.highlight.id);
|
||||||
|
const el = document.querySelector(`[data-feature-id="${CSS.escape(idStr)}"]`);
|
||||||
|
if (el) {
|
||||||
|
el.classList.remove('pulse-green', 'pulse-red');
|
||||||
|
if (this.highlight.type === 'saved') el.classList.add('pulse-green');
|
||||||
|
if (this.highlight.type === 'deleted') el.classList.add('pulse-red');
|
||||||
|
setTimeout(() => {
|
||||||
|
el.classList.remove('pulse-green', 'pulse-red');
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureMapLibre(): Promise<void> {
|
private ensureMapLibre(): Promise<void> {
|
||||||
|
@ -101,6 +126,7 @@ export class AllEvents {
|
||||||
this.pickedMarker.remove();
|
this.pickedMarker.remove();
|
||||||
}
|
}
|
||||||
this.pickedMarker = new maplibregl.Marker({ element: el }).setLngLat(coords).addTo(this.map);
|
this.pickedMarker = new maplibregl.Marker({ element: el }).setLngLat(coords).addTo(this.map);
|
||||||
|
this.currentPicked = coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchPlace(query: string) {
|
async searchPlace(query: string) {
|
||||||
|
@ -125,6 +151,18 @@ export class AllEvents {
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canRestoreOriginal(): boolean {
|
||||||
|
if (!this.originalCoords || !this.currentPicked) return false;
|
||||||
|
return this.originalCoords[0] !== this.currentPicked[0] || this.originalCoords[1] !== this.currentPicked[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreOriginalCoords() {
|
||||||
|
if (!this.originalCoords) return;
|
||||||
|
this.showPickedMarker(this.originalCoords);
|
||||||
|
this.pickCoords.emit(this.originalCoords);
|
||||||
|
if (this.map) this.map.flyTo({ center: this.originalCoords, zoom: Math.max(this.map.getZoom() || 12, 12) });
|
||||||
|
}
|
||||||
|
|
||||||
private renderFeatures() {
|
private renderFeatures() {
|
||||||
if (!this.map || !Array.isArray(this.features)) return;
|
if (!this.map || !Array.isArray(this.features)) return;
|
||||||
// clear existing markers
|
// clear existing markers
|
||||||
|
@ -138,11 +176,22 @@ export class AllEvents {
|
||||||
const coords = f?.geometry?.coordinates;
|
const coords = f?.geometry?.coordinates;
|
||||||
if (!coords || !Array.isArray(coords)) return;
|
if (!coords || !Array.isArray(coords)) return;
|
||||||
const p = f.properties || {};
|
const p = f.properties || {};
|
||||||
|
const fid = (p && (p.id ?? p.uuid)) ?? f?.id;
|
||||||
const el = this.buildMarkerElement(p);
|
const el = this.buildMarkerElement(p);
|
||||||
el.style.cursor = 'pointer';
|
el.style.cursor = 'pointer';
|
||||||
|
if (typeof fid !== 'undefined') {
|
||||||
|
el.setAttribute('data-feature-id', String(fid));
|
||||||
|
}
|
||||||
|
// selected styling
|
||||||
|
const selId = this.selected?.properties?.id ?? this.selected?.properties?.uuid ?? this.selected?.id;
|
||||||
|
if (selId !== undefined && selId !== null && String(selId) === String(fid)) {
|
||||||
|
el.style.transform = 'scale(1.2)';
|
||||||
|
el.style.boxShadow = '0 0 0 4px rgba(25,118,210,0.25)';
|
||||||
|
el.style.borderRadius = '50%';
|
||||||
|
}
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
this.select.emit({
|
this.select.emit({
|
||||||
id: (p && (p.id ?? p.uuid)) ?? f?.id,
|
id: fid,
|
||||||
properties: p,
|
properties: p,
|
||||||
geometry: { type: 'Point', coordinates: coords }
|
geometry: { type: 'Point', coordinates: coords }
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,8 +14,33 @@
|
||||||
<app-edit-form [selected]="selected" (saved)="onSaved($event)" (created)="onCreated($event)" (deleted)="onDeleted($event)"></app-edit-form>
|
<app-edit-form [selected]="selected" (saved)="onSaved($event)" (created)="onCreated($event)" (deleted)="onDeleted($event)"></app-edit-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
|
@if (!showTable) {
|
||||||
<div class="map">
|
<div class="map">
|
||||||
<app-all-events [features]="features" (select)="onSelect($event)" (pickCoords)="onPickCoords($event)"></app-all-events>
|
<app-all-events [features]="features" [selected]="selected" (select)="onSelect($event)" (pickCoords)="onPickCoords($event)"></app-all-events>
|
||||||
</div>
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="table-wrapper" style="overflow:auto;height:100%;">
|
||||||
|
<table style="width:100%;border-collapse:collapse;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Type</th>
|
||||||
|
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Label</th>
|
||||||
|
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Start</th>
|
||||||
|
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Stop</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (f of features; track f.id) {
|
||||||
|
<tr (click)="onSelect({ id: f?.properties?.id ?? f?.id, properties: f.properties, geometry: f.geometry })" style="cursor:pointer;">
|
||||||
|
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.what}}</td>
|
||||||
|
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.label || f?.properties?.name}}</td>
|
||||||
|
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.start || f?.properties?.when}}</td>
|
||||||
|
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.stop}}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,7 @@ export class Home {
|
||||||
OedbApi = inject(OedbApi);
|
OedbApi = inject(OedbApi);
|
||||||
features: Array<any> = [];
|
features: Array<any> = [];
|
||||||
selected: any | null = null;
|
selected: any | null = null;
|
||||||
|
showTable = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.OedbApi.getEvents({ when: 'now', limit: 500 }).subscribe((events: any) => {
|
this.OedbApi.getEvents({ when: 'now', limit: 500 }).subscribe((events: any) => {
|
||||||
|
@ -68,4 +69,49 @@ export class Home {
|
||||||
this.features = Array.isArray(events?.features) ? events.features : [];
|
this.features = Array.isArray(events?.features) ? events.features : [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Menu callbacks
|
||||||
|
ngAfterViewInit() {
|
||||||
|
// Wire menu callbacks if needed via querySelector; left simple for now
|
||||||
|
// We keep logic here: toggling and downloads
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleView() {
|
||||||
|
this.showTable = !this.showTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadGeoJSON() {
|
||||||
|
const blob = new Blob([JSON.stringify({ type: 'FeatureCollection', features: this.features }, null, 2)], { type: 'application/geo+json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'events.geojson';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadCSV() {
|
||||||
|
const header = ['id', 'what', 'label', 'start', 'stop', 'lon', 'lat'];
|
||||||
|
const rows = this.features.map((f: any) => [
|
||||||
|
JSON.stringify(f?.properties?.id ?? f?.id ?? ''),
|
||||||
|
JSON.stringify(f?.properties?.what ?? ''),
|
||||||
|
JSON.stringify(f?.properties?.label ?? f?.properties?.name ?? ''),
|
||||||
|
JSON.stringify(f?.properties?.start ?? f?.properties?.when ?? ''),
|
||||||
|
JSON.stringify(f?.properties?.stop ?? ''),
|
||||||
|
JSON.stringify(f?.geometry?.coordinates?.[0] ?? ''),
|
||||||
|
JSON.stringify(f?.geometry?.coordinates?.[1] ?? '')
|
||||||
|
].join(','));
|
||||||
|
const csv = [header.join(','), ...rows].join('\n');
|
||||||
|
const blob = new Blob([csv], { type: 'text/csv' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'events.csv';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,12 @@
|
||||||
<button class="button">oui, toujours là</button>
|
<button class="button">oui, toujours là</button>
|
||||||
<button class="button">non plus là</button>
|
<button class="button">non plus là</button>
|
||||||
<button class="button">pouet pouet!</button>
|
<button class="button">pouet pouet!</button>
|
||||||
|
<hr>
|
||||||
|
<button class="button" (click)="toggleView()">Basculer carte / tableau</button>
|
||||||
|
<div class="downloaders">
|
||||||
|
<button class="button" (click)="downloadGeoJSON()">Télécharger GeoJSON</button>
|
||||||
|
<button class="button" (click)="downloadCSV()">Télécharger CSV</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="user_infos">
|
<div id="user_infos">
|
||||||
login OSM:
|
login OSM:
|
||||||
|
|
|
@ -10,6 +10,9 @@ import oedb_what_categories from '../../../../oedb-types';
|
||||||
export class Menu {
|
export class Menu {
|
||||||
|
|
||||||
public oedb_what_categories: Array<any> = [];
|
public oedb_what_categories: Array<any> = [];
|
||||||
|
public onToggleView?: () => void;
|
||||||
|
public onDownloadGeoJSON?: () => void;
|
||||||
|
public onDownloadCSV?: () => void;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
let keys = Object.keys(oedb_what_categories.presets.what);
|
let keys = Object.keys(oedb_what_categories.presets.what);
|
||||||
|
@ -22,4 +25,8 @@ export class Menu {
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleView() { this.onToggleView && this.onToggleView(); }
|
||||||
|
downloadGeoJSON() { this.onDownloadGeoJSON && this.onDownloadGeoJSON(); }
|
||||||
|
downloadCSV() { this.onDownloadCSV && this.onDownloadCSV(); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ $color-bg: #f7fafb;
|
||||||
$color-surface: #ffffff;
|
$color-surface: #ffffff;
|
||||||
$color-text: #22303a;
|
$color-text: #22303a;
|
||||||
$color-muted: #6b7b86;
|
$color-muted: #6b7b86;
|
||||||
|
$color-success: #b4e5c6;
|
||||||
|
$color-error: #f6c9c9;
|
||||||
|
$color-info: #cfe8ff;
|
||||||
$border-radius: 10px;
|
$border-radius: 10px;
|
||||||
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06);
|
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
$shadow-md: 0 6px 18px rgba(0, 0, 0, 0.08);
|
$shadow-md: 0 6px 18px rgba(0, 0, 0, 0.08);
|
||||||
|
@ -16,8 +19,35 @@ html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: $color-bg;
|
background: $color-bg;
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, sans-serif;
|
// font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
html, body{
|
||||||
|
font-family: "Calibri", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, .button{
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #79a2d1;
|
||||||
|
padding: 1rem 0.5rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover{
|
||||||
|
background-color: #6992c1;
|
||||||
|
}
|
||||||
|
&:active{
|
||||||
|
background-color: #5982b1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input{
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
background: #ffffff;
|
||||||
|
color: #22303a;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
app-root, app-home {
|
app-root, app-home {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -56,3 +86,27 @@ app-root, app-home {
|
||||||
}
|
}
|
||||||
|
|
||||||
label { font-size: 0.85rem; color: $color-muted; }
|
label { font-size: 0.85rem; color: $color-muted; }
|
||||||
|
|
||||||
|
/* Toasts */
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
right: 16px;
|
||||||
|
bottom: 16px;
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
min-width: 240px;
|
||||||
|
max-width: 360px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
box-shadow: $shadow-md;
|
||||||
|
border: 1px solid rgba(0,0,0,0.06);
|
||||||
|
background: $color-surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.is-success { background: $color-success; }
|
||||||
|
.toast.is-error { background: $color-error; }
|
||||||
|
.toast.is-info { background: $color-info; }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue