Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"lokalise.i18n-ally",
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"ionic.ionic",
"angular.ng-template",
"yoavbls.pretty-ts-errors"
],
Expand Down
4 changes: 2 additions & 2 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@
},
"schematics": {
"@ionic/angular-toolkit:component": {
"styleext": "scss"
"styleext": "css"
},
"@ionic/angular-toolkit:page": {
"styleext": "scss"
"styleext": "css"
},
Comment thread
jorgkv marked this conversation as resolved.
"@schematics/angular:component": {
"type": "component"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { inject } from '@angular/core';
import { ModalController } from '@ionic/angular/standalone';
import { AttachmentViewModel, RegistrationViewModel } from 'src/app/modules/common-regobs-api';
import { RegistrationViewModel } from 'src/app/modules/common-regobs-api';
import { ObservationImageCarouselComponent } from './observation-image-carousel.component';
import { CarouselItems } from './models';

export function injectImageCarousel() {
const modalController = inject(ModalController);

return {
open: async (index: number, attachments: AttachmentViewModel[], registration: RegistrationViewModel) => {
open: async (index: number, items: CarouselItems, registration: RegistrationViewModel) => {
const modal = await modalController.create({
component: ObservationImageCarouselComponent,
cssClass: 'fullscreen-modal',
componentProps: {
attachmentIndex: index,
attachments,
index,
items,
registration,
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AttachmentViewModel } from 'src/app/modules/common-regobs-api';

interface CarouselItem<T extends string> {
type: T;
}

interface CarouselImageItem extends CarouselItem<'Attachment'> {
data: AttachmentViewModel;
}

type CarouselSnowProfile = CarouselItem<'SnowProfile'>;

export type CarouselItems = (CarouselSnowProfile | CarouselImageItem)[];
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@
keyboard="false"
(swiperslidechange)="setCurrentSlideIndex($event)"
>
@for (attachment of attachments(); track attachment.AttachmentId) {
@if (attachment.IsSnowProfilePlot) {
<swiper-slide lazy="true">
<img
[alt]="attachment.Alt || attachment.Comment"
[src]="snowProfileUrl()"
(error)="useFallbackSnowProfileImage(attachment, $event)"
class="snow-profile"
loading="lazy"
/>
@for (item of items(); track $index) {
@if (item.type === "SnowProfile") {
@let reg = registration();
<swiper-slide>
<app-snow-profile [profile]="reg.SnowProfile2!" [tests]="reg.CompressionTest"></app-snow-profile>
</swiper-slide>
} @else {
@let attachment = item.data;
<swiper-slide>
<div class="swiper-zoom-container">
<img
Expand All @@ -33,17 +29,30 @@
</swiper-container>
<div class="image-description">
<div class="image-description__content">
@let item = currentItem();
@let attachment = item.type === "Attachment" ? item.data : null;

<!-- tittel -->
@if (currentAttachmentData().RegistrationName) {
<ion-chip color="primary"> {{ currentAttachmentData().RegistrationName }}</ion-chip>
@if (attachment?.RegistrationName; as regName) {
<ion-chip color="primary">{{ regName }}</ion-chip>
}

<!-- kommentar -->
@if (currentAttachmentData().Comment) {
<app-key-value
[key]="'REGISTRATION.DANGER_OBS.COMMENT' | translate"
[value]="currentAttachmentData().Comment"
></app-key-value>
@if (item.type === "SnowProfile") {
@if (!showsComments()) {
@for (layer of layerComments(); track layer.i) {
<app-key-value [key]="layer.key" [value]="layer.comment"></app-key-value>
}
}
@if (registration().SnowProfile2?.Comment; as comment) {
<app-key-value [key]="'REGISTRATION.DANGER_OBS.COMMENT' | translate" [value]="comment"></app-key-value>
}
}

@if (attachment?.Comment; as comment) {
<app-key-value [key]="'REGISTRATION.DANGER_OBS.COMMENT' | translate" [value]="comment"></app-key-value>
}

<div class="image-description__params">
<!-- region -->
@if (registration().ObsLocation.ForecastRegionName) {
Expand All @@ -55,7 +64,7 @@
}

<!-- himmelretning -->
@if (currentAttachmentData().Aspect) {
@if (attachment?.Aspect) {
<app-key-value
[key]="'REGISTRATION.IMAGE_ASPECT' | translate"
[value]="roundedDownOrientationValue() | translate"
Expand All @@ -70,15 +79,13 @@
}

<!-- opphavsrett -->
@if (currentAttachmentData().Copyright) {
<app-key-value [key]="'MY_PROFILE.COPYRIGHT' | translate" [value]="currentAttachmentData().Copyright">
</app-key-value>
@if (attachment?.Copyright; as copyright) {
<app-key-value [key]="'MY_PROFILE.COPYRIGHT' | translate" [value]="copyright"> </app-key-value>
}

<!-- fotograf -->
@if (currentAttachmentData().Photographer) {
<app-key-value [key]="'MY_PROFILE.PHOTOGRAPHER' | translate" [value]="currentAttachmentData().Photographer">
</app-key-value>
@if (attachment?.Photographer; as photographer) {
<app-key-value [key]="'MY_PROFILE.PHOTOGRAPHER' | translate" [value]="photographer"> </app-key-value>
}
<!-- observert dato-->
@if (registration().DtObsTime) {
Expand All @@ -90,20 +97,15 @@
}
</div>
<div class="image-description__links">
<a [href]="currentAttachmentData().Url" download>
<ion-icon name="download-outline"></ion-icon> {{ "REGISTRATION.CARD.DOWNLOAD" | translate }}
</a>

@if (currentAttachmentData().IsSnowProfilePlot) {
<a [href]="snowProfileUrl()" target="_blank">
<ion-icon name="open-outline"></ion-icon>
{{ "REGISTRATION.CARD.GO_TO_SNOW_PROFILE" | translate }}
@if (attachment?.Url; as url) {
<a [href]="url" download>
<ion-icon name="download-outline"></ion-icon> {{ "REGISTRATION.CARD.DOWNLOAD" | translate }}
</a>
} @else {
<a [href]="currentAttachmentData().Url" target="_blank">
<a [href]="url" target="_blank">
<ion-icon name="open-outline"></ion-icon> {{ "REGISTRATION.CARD.GO_TO_ORIGINAL_IMAGE" | translate }}
</a>
}

@if (isImageListView()) {
<a [routerLink]="'registration/' + registration().RegId" (click)="closeModal()">
<ion-icon name="eye-outline"></ion-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ ion-fab-button::part(native):hover {
height: 100%;
background-color: white;
}

app-snow-profile {
// For å unngå at swiper-"prikkene" legger seg oppå hardhets-axis i bunnen
padding-bottom: 20px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
ElementRef,
inject,
input,
linkedSignal,
model,
viewChild,
ChangeDetectionStrategy,
Expand All @@ -21,10 +20,11 @@ import { DatePipe } from '@angular/common';
import { KeyValueComponent } from '../key-value/key-value.component';
import { settings } from 'src/settings';
import { getRoundedDownOrientationValue } from 'src/app/utils/getRoundedDownOrientationValue';
import { PlotService } from 'src/app/core/services/plot.service';
import { Router, RouterLink } from '@angular/router';
import { LoggingService } from 'src/app/modules/shared/services/logging/logging.service';
import { LogLevel } from 'src/app/modules/shared/services/logging/log-level.model';
import { CarouselItems } from './models';
import { SnowProfileComponent } from '../../snow-profile/snow-profile.component';

const DEBUG_TAG = 'ImageCarousel';

Expand All @@ -34,64 +34,63 @@ const DEBUG_TAG = 'ImageCarousel';
templateUrl: './observation-image-carousel.component.html',
styleUrls: ['./observation-image-carousel.component.scss'],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [IonIcon, IonFabButton, TranslatePipe, DatePipe, KeyValueComponent, RouterLink, IonChip],
imports: [
IonIcon,
IonFabButton,
TranslatePipe,
DatePipe,
KeyValueComponent,
RouterLink,
IonChip,
SnowProfileComponent,
],
})
export class ObservationImageCarouselComponent {
readonly swiper = viewChild<ElementRef<SwiperContainer>>('swiper');
private modalController = inject(ModalController);
private router = inject(Router);
private plotService = inject(PlotService);
private logger = inject(LoggingService);

isImageListView = computed(() => this.router.url.includes('search/pictures'));
attachments = input<AttachmentViewModel[]>([]);
translateService = inject(TranslateService);

registration = input.required<RegistrationViewModel>();
attachmentIndex = model<number>(0);
items = input<CarouselItems>([]);
index = model<number>(0);

private snowProfile = viewChild(SnowProfileComponent);
layerComments = computed(() => this.snowProfile()?.comments());
showsComments = computed(() => !!this.snowProfile()?.showComments());

isImageListView = computed(() => this.router.url.includes('search/pictures'));
roundedDownOrientationValue = computed(() => {
const aspectValue = this.currentAttachmentData().Aspect; //256
const item = this.currentItem();
if (item.type === 'SnowProfile') {
return '';
}
const aspectValue = item.data.Aspect; //256
if (!aspectValue) return '';
const roundedDownOrientation = getRoundedDownOrientationValue(aspectValue);
if (!roundedDownOrientation) return '';
return settings.orientation[roundedDownOrientation];
});

currentAttachmentData = computed(() => this.attachments()?.[this.attachmentIndex()]);

snowProfileUrl = linkedSignal(() => {
const reg = this.registration();
if (!reg) return undefined;
return this.plotService.getSnowProfileSvgUrl(reg);
});
currentItem = computed(() => this.items()[this.index()]);

constructor() {
addIcons({ close, downloadOutline, openOutline, eyeOutline });
}

useFallbackSnowProfileImage(attachment: AttachmentViewModel, event: Event) {
const target = event.target as HTMLImageElement;
if (target.src === attachment.Url) {
attachment.Url = 'assets/images/broken-image-w-bg.svg';
attachment.Alt = this.translateService.instant('REGISTRATION.COULD_NOT_DOWNLOAD_IMAGE');
}
this.snowProfileUrl.set(attachment.Url as string);
}

closeModal() {
this.modalController.dismiss();
}

setCurrentSlideIndex(e: Event) {
const customEvent = e as CustomEvent;
const activeIndex = customEvent.detail[0].activeIndex;
this.attachmentIndex.set(activeIndex);
this;
this.index.set(activeIndex);
}

ngAfterViewInit() {
this.swiper()?.nativeElement.swiper.slideTo(this.attachmentIndex());
this.swiper()?.nativeElement.swiper.slideTo(this.index());
}

setFallbackImage(attachment: AttachmentViewModel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@
<app-observation-location-map [registration]="reg"></app-observation-location-map>
</swiper-slide>

@if (hasSnowProfile()) {
<swiper-slide>
<app-snow-profile
[profile]="reg.SnowProfile2!"
[tests]="reg.CompressionTest"
[style.width.px]="250"
(click)="openImageCarousel(0)"
></app-snow-profile>
</swiper-slide>
}

@let carouselIndexOffset = hasSnowProfile() ? 1 : 0;
<!-- lazy-preload-prev-next="2" -->
@for (attachment of attachments(); track attachment.AttachmentId; let index = $index) {
<swiper-slide (click)="openImageCarousel(index)" lazy="true">
<img
[src]="attachment.UrlFormats?.Large"
[alt]="attachment.Comment"
loading="lazy"
decoding="async"
(error)="setFallbackImage(attachment)"
/>
@for (attachment of attachments(); track attachment.AttachmentId) {
<swiper-slide (click)="openImageCarousel($index + carouselIndexOffset)">
<img [src]="attachment.UrlFormats?.Large" [alt]="attachment.Comment" (error)="setFallbackImage(attachment)" />
Comment thread
jorgkv marked this conversation as resolved.
</swiper-slide>
}
</swiper-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { AvalancheActivitesViewComponent } from '../registrations/avalanche-acti
import { ObservationActionsComponent } from '../observation-actions/observation-actions.component';
import { ObservationLocationMapComponent } from '../observation-location-map/observation-location-map.component';
import { GeohazardChipComponent } from '../geohazard-chip/geohazard-chip.component';
import { SnowProfileComponent } from '../../snow-profile/snow-profile.component';
import { CarouselItems } from '../observation-image-carousel/models';

const DEBUG_TAG = 'ObservationComponent';

Expand All @@ -65,6 +67,7 @@ const DEBUG_TAG = 'ObservationComponent';
AvalancheProblemsViewComponent,
AvalancheEvaluationViewComponent,
ObservationActionsComponent,
SnowProfileComponent,
],
templateUrl: './observation.component.html',
styleUrl: './observation.component.css',
Expand All @@ -81,6 +84,13 @@ export class ObservationComponent implements AfterViewInit, OnDestroy {
modalController = inject(ModalController);

readonly registration = input.required<RegistrationViewModel>();
hasSnowProfile = computed(() => {
const snowProfileForm = this.registration().SnowProfile2;
if (snowProfileForm == null) {
return false;
}
return snowProfileForm.StratProfile?.Layers?.at(0) != null;
});
showChangedTime = computed(() => {
const { DtChangeTime, DtRegTime } = this.registration();
if (DtChangeTime == null) {
Expand All @@ -91,7 +101,9 @@ export class ObservationComponent implements AfterViewInit, OnDestroy {
}
return true;
});
attachments = computed(() => getAllAttachmentsFromViewModel(this.registration()));
attachments = computed(() =>
getAllAttachmentsFromViewModel(this.registration()).filter((a) => a.IsSnowProfilePlot != true)
);

constructor() {
addIcons({
Expand Down Expand Up @@ -156,7 +168,14 @@ export class ObservationComponent implements AfterViewInit, OnDestroy {
}

async openImageCarousel(index: number) {
await this.imageCarousel.open(index, this.attachments(), this.registration());
let items: CarouselItems = this.attachments()
.filter((x) => !x.IsSnowProfilePlot) // Bruk ny snøprofil-komponent, ikke vis genererte bilder
.map((data) => ({ type: 'Attachment', data }));
if (this.hasSnowProfile()) {
items = [{ type: 'SnowProfile' }, ...items];
}

await this.imageCarousel.open(index, items, this.registration());
}

hasData(data: unknown) {
Expand Down
Loading
Loading