diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index a9eafa7..4493b16 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -249,7 +249,7 @@ jobs: uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.ver.outputs.tag }} - files: core/target/dist-out/*.AppImage + files: core/target/dist-out/DM_Loremind*.AppImage fail_on_unmatched_files: true # BETA uniquement : artefact PRIVE (pas de release publique). @@ -258,5 +258,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: loremind-beta-${{ steps.ver.outputs.version }}-appimage - path: core/target/dist-out/*.AppImage + path: core/target/dist-out/DM_Loremind*.AppImage retention-days: 90 diff --git a/installers/desktop/build-linux.sh b/installers/desktop/build-linux.sh index 8803a01..fa813ab 100644 --- a/installers/desktop/build-linux.sh +++ b/installers/desktop/build-linux.sh @@ -235,7 +235,9 @@ chmod +x "$APPDIR/AppRun" # e) appimagetool (telecharge si absent). --appimage-extract-and-run : evite # d'exiger FUSE (absent des runners CI). -TOOL="$OUT_DIR/appimagetool-x86_64.AppImage" +# /!\ HORS de $OUT_DIR : sinon ce .AppImage (l'outil) serait pris pour un livrable +# et attache a la release a cote du vrai DM_Loremind-*.AppImage. +TOOL="$CORE_DIR/target/appimagetool-x86_64.AppImage" if [[ ! -x "$TOOL" ]]; then curl -fsSL -o "$TOOL" \ https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage diff --git a/web/src/app/app.component.html b/web/src/app/app.component.html index 2e0305d..29a255c 100644 --- a/web/src/app/app.component.html +++ b/web/src/app/app.component.html @@ -20,3 +20,6 @@ + + + diff --git a/web/src/app/app.component.ts b/web/src/app/app.component.ts index d49295f..66d9756 100644 --- a/web/src/app/app.component.ts +++ b/web/src/app/app.component.ts @@ -6,6 +6,7 @@ import { SecondarySidebarComponent } from './shared/secondary-sidebar/secondary- import { GlobalSearchComponent } from './shared/global-search/global-search.component'; import { UpdateBannerComponent } from './shared/update-banner/update-banner.component'; import { ConfirmDialogHostComponent } from './shared/confirm-dialog/confirm-dialog-host.component'; +import { FirstRunLanguageComponent } from './shared/first-run-language/first-run-language.component'; import { LayoutService } from './services/layout.service'; import { GlobalSearchService } from './services/global-search.service'; import { VersionCheckerService } from './services/version-checker.service'; @@ -19,6 +20,7 @@ import { VersionCheckerService } from './services/version-checker.service'; GlobalSearchComponent, UpdateBannerComponent, ConfirmDialogHostComponent, + FirstRunLanguageComponent, AsyncPipe ], templateUrl: './app.component.html', diff --git a/web/src/app/services/language.service.ts b/web/src/app/services/language.service.ts index 8d73612..091734b 100644 --- a/web/src/app/services/language.service.ts +++ b/web/src/app/services/language.service.ts @@ -88,6 +88,14 @@ export class LanguageService { return this.defaultLang; } + /** + * Vrai si une langue a déjà été choisie explicitement (mémorisée en localStorage). + * Faux au tout premier lancement → on propose alors l'écran de choix de langue. + */ + hasExplicitChoice(): boolean { + return this.read() !== null; + } + private read(): string | null { try { return localStorage.getItem(STORAGE_KEY); diff --git a/web/src/app/shared/first-run-language/first-run-language.component.html b/web/src/app/shared/first-run-language/first-run-language.component.html new file mode 100644 index 0000000..58ce620 --- /dev/null +++ b/web/src/app/shared/first-run-language/first-run-language.component.html @@ -0,0 +1,17 @@ +@if (visible) { + +} diff --git a/web/src/app/shared/first-run-language/first-run-language.component.scss b/web/src/app/shared/first-run-language/first-run-language.component.scss new file mode 100644 index 0000000..5372ccc --- /dev/null +++ b/web/src/app/shared/first-run-language/first-run-language.component.scss @@ -0,0 +1,76 @@ +.flo-overlay { + position: fixed; + inset: 0; + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + padding: 1.5rem; + background: rgba(10, 10, 20, 0.82); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.flo-card { + width: 100%; + max-width: 420px; + padding: 2.5rem 2rem; + text-align: center; + background: #12121f; + border: 1px solid rgba(108, 99, 255, 0.25); + border-radius: 18px; + box-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.8); +} + +.flo-logo { + width: 64px; + height: 64px; + border-radius: 14px; + object-fit: contain; +} + +.flo-title { + margin: 1rem 0 0.3rem; + font-size: 1.5rem; + font-weight: 700; + color: #fff; +} + +.flo-sub { + margin: 0 0 1.8rem; + font-size: 0.95rem; + color: #9ca3af; +} + +.flo-buttons { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.flo-btn { + display: flex; + align-items: center; + gap: 0.9rem; + width: 100%; + padding: 0.9rem 1.1rem; + font-size: 1.05rem; + font-weight: 600; + color: #e0e0e0; + background: rgba(108, 99, 255, 0.08); + border: 1px solid rgba(108, 99, 255, 0.25); + border-radius: 12px; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, transform 0.12s; +} + +.flo-btn:hover { + background: rgba(108, 99, 255, 0.18); + border-color: rgba(108, 99, 255, 0.5); + transform: translateY(-1px); +} + +.flo-flag { + font-size: 1.5rem; + line-height: 1; +} diff --git a/web/src/app/shared/first-run-language/first-run-language.component.ts b/web/src/app/shared/first-run-language/first-run-language.component.ts new file mode 100644 index 0000000..661d267 --- /dev/null +++ b/web/src/app/shared/first-run-language/first-run-language.component.ts @@ -0,0 +1,35 @@ +import { Component, inject } from '@angular/core'; +import { LanguageService } from '../../services/language.service'; + +/** + * Écran de choix de langue au TOUT PREMIER lancement (overlay one-shot). + * + * Ne s'affiche que si aucune langue n'a encore été choisie explicitement + * (cf. LanguageService.hasExplicitChoice). Le choix est mémorisé en localStorage + * via LanguageService.use() → aux lancements suivants l'overlay ne réapparaît pas, + * et le sélecteur de langue habituel prend le relais. + * + * Textes volontairement BILINGUES / neutres (l'utilisateur n'a pas encore choisi). + */ +@Component({ + selector: 'app-first-run-language', + standalone: true, + templateUrl: './first-run-language.component.html', + styleUrls: ['./first-run-language.component.scss'], +}) +export class FirstRunLanguageComponent { + private readonly language = inject(LanguageService); + + visible = !this.language.hasExplicitChoice(); + + /** Libellés NATIFs (indépendants de la langue courante). */ + readonly options: ReadonlyArray<{ code: string; native: string; flag: string }> = [ + { code: 'fr', native: 'Français', flag: '🇫🇷' }, + { code: 'en', native: 'English', flag: '🇬🇧' }, + ]; + + choose(code: string): void { + this.language.use(code); // applique + mémorise + this.visible = false; + } +}