From 6a538982a2a4f2a2e9d55bd871c54585dec4680d Mon Sep 17 00:00:00 2001 From: itskovacs Date: Tue, 22 Jul 2025 21:24:11 +0200 Subject: [PATCH] :sparkles: Optional Category image, :zap: Low Network mode, :sparkles: Trip map: fullscreen and context menu, :zap: backend prefix assets, :bug: fix category image fallback, :bug: fix category delete, :lipstick: Trip places: Fix usage badge, :lipstick: Remove external default TRIP image, :fire: Remove optional image files --- backend/storage/assets/accommodation.png | Bin 8649 -> 0 bytes backend/storage/assets/adventure.png | Bin 8954 -> 0 bytes backend/storage/assets/culture.png | Bin 8343 -> 0 bytes backend/storage/assets/entertainment.png | Bin 7840 -> 0 bytes backend/storage/assets/event.png | Bin 7119 -> 0 bytes backend/storage/assets/food.png | Bin 7219 -> 0 bytes backend/storage/assets/nature.png | Bin 8089 -> 0 bytes backend/storage/assets/wellness.png | Bin 9690 -> 0 bytes backend/storage/config.yml | 0 backend/trip/__init__.py | 2 +- backend/trip/config.py | 10 +- backend/trip/db/core.py | 48 +---- backend/trip/models/models.py | 26 ++- backend/trip/routers/categories.py | 34 +++- .../dashboard/dashboard.component.html | 107 +++++----- .../dashboard/dashboard.component.ts | 22 +- .../app/components/trip/trip.component.html | 30 ++- .../app/components/trip/trip.component.scss | 10 + src/src/app/components/trip/trip.component.ts | 59 ++++-- .../app/components/trips/trips.component.html | 2 +- .../category-create-modal.component.ts | 19 +- .../place-create-modal.component.ts | 1 - .../trip-create-day-item-modal.component.html | 2 +- .../trip-place-select-modal.component.html | 2 +- src/src/app/services/api.service.ts | 191 ++++-------------- src/src/app/services/utils.service.ts | 27 +-- src/src/app/shared/map.ts | 12 +- .../shared/place-box/place-box.component.html | 7 +- src/src/app/types/poi.ts | 1 - 29 files changed, 303 insertions(+), 309 deletions(-) delete mode 100644 backend/storage/assets/accommodation.png delete mode 100644 backend/storage/assets/adventure.png delete mode 100644 backend/storage/assets/culture.png delete mode 100644 backend/storage/assets/entertainment.png delete mode 100644 backend/storage/assets/event.png delete mode 100644 backend/storage/assets/food.png delete mode 100644 backend/storage/assets/nature.png delete mode 100644 backend/storage/assets/wellness.png delete mode 100644 backend/storage/config.yml diff --git a/backend/storage/assets/accommodation.png b/backend/storage/assets/accommodation.png deleted file mode 100644 index 1e10866aa16a8bb09e1655679c3a5a2289be6588..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8649 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 UMQzHM1ONa407*qoM6N<$g2@S{{Qv*} diff --git a/backend/storage/assets/culture.png b/backend/storage/assets/culture.png deleted file mode 100644 index 93d2b80db1f09d1b69c686b3446204475fda24b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8343 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 hnEvIL^Q6r*v5~BA`lpsj7 zNP;WBl6B5m=iamK*=y~+zrFYKJ@5Ox?VlZ^tF1yt%t#CX0Lau-mGp1!^1n|){F`eT z(tY{nM&zby<_Q3hQ2#!$057uW0RY?sq@k&osg@?p2IV3Mw?$ba1ko;TH)sGrS`O_7 zw{b#vfvgesNLLw-!{&Al5YkqL!$eFAtmSqO;eb^2^FSE*X&c)3IoUvMIpkzP(rDNX zfeXS54nn&)yL!UVG8}*L!fw{TUkh=7{(^Wp$#5wAHV87+(goc^c_2XIf?xp~FjyQU z0Tr}`Tf=Rvt?z<_!NL$Bu&5A3Q~(Tyi9lhZqM&~cjvI3xwstUmrThOFyLpn~aPabS zg9!=w`1lC=hzO!Q?1dmuC{ze6EF>%}aDx!=^mX-uqXk?&Isau)LU`JEAlC|A&L zMz}S~+e?Py#?`+yxVZhHb@luwPB#$~Lc`sJAcElEn*IXX+Wf(}d3!kjW!%i1w`fm8 zUpIu1KEe~_?O}sZethF5=WjJ`uzMZ|xEIR95QTF7H&eR*x(pH)78C{DF+sZ8qI^7g z|HB4^65Ie{U=S!jSQrKd{|(hb*&^+H{|i*o5F!kNh{M2; z{{+3M8C$p){C@=7+Q95k9xm`3!AKXlJwnLM)t&?NCycOrC})(%jo};bME>$ZOADsv z>gfe{wLz#U$#C3g7DOU#VG@#1J8P(|hyWNNE+HU+5VaGK6ow-NY{aZ3AhuA1H3Ir) zzY@yE`*&vl-EaHHj0$f5SXcc3wVk4}^mKO|<^UauT|6Ug&op z{;fmkze@b;+kfcUf8jTS;P>*+2)cRrGsF4R#t#7i45n&I3Wn&c)|WnS zxmCPZgNw_WQ31#;Eq(g<`%oZm5-nF8rByOlnB3#e=w5uGM;%tQHqYZbo4IT#UFPs@ zNy4!@u{COyzr7!*0I+k(6LQJNi7F&-Z=SpE9+cGud!4ri9yW6lZ7dzP1+nmDd+zp> zz4KU>*z(l{s#fN2V=Japy-1lfZ841ll=c&rgiwO{&^GKeSrW;P=(pXE09&L&wGZQ* z^$-P54RLj+COfdpJi}P^iqFlp4#7LwvhM!(z82yZ64OS}EBEG+8?uDT?+UPT*OPDH zCA<>EKUDbiwh)g(`|=)+LSg_I)%~Rv^zK1LU>V5}rewgYQ&n#i%ThrhZWV6Nz^@X0 zM|G#IT=tSq>y#v^aOKviS@*`oN$lF?2aOQxb(*-&$WD>*>L1o*VcY1Q3=cZ)E=qNw zZcL_gq5K6%C$QTYFN(MIe^>BIZ~{>qU?}Y{{oo!yIm<8gO)-fR)%INvdY* z--0n-S;O%sdO=w!uLC#B&owGNbR+UCh{&}^Yc&2k-LVt++p=uwg zez2+El7vd@0YSETR^s)!cn3PFPNBg9)uPe;o!r7m4*(@odGE9h3N2_ zp(^CHW?S+wt1aJ_{jH>fmku%c#;kc(Pg0guzM23DOzVu8PAPiyHk-zGuO!C5_nU6g zL4fT*oH)-yIJ(A3JK(*=93^@OE%|9pE`#%dbMSl4Az4=BnR^@a=+lZVVqL7ta97aR zC!-@CPlg5`&+m_IfpOH8XsrxP9lFd}v`qnfolG5wdYt)-g?RKIOec!(v1v-3CC)pxKZvu$z-(4S`$oQ=uBJ#j zUL~R?W$m6~;gQ48ITsKN<`IE;?*(XFK>lLN8#+|s%p1eJ|ZO6ikK!Gr0z)bMY zvEPK#@^Er+eL|~+2=AR;+9xK=-_17>?w;%T34@CsO%ssxue%IB&{OoGZu)qo*h
    5bZ6t?2BdL` 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 FV1j>Q35Eaw diff --git a/backend/storage/assets/food.png b/backend/storage/assets/food.png deleted file mode 100644 index e5d164219b21667e46ea7970e3d9d2be16b7aeae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7219 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 Y_jO6uiUc6;_isHl>``o~yk+qJ0Z_#&t^fc4 diff --git a/backend/storage/assets/nature.png b/backend/storage/assets/nature.png deleted file mode 100644 index acc8bbfa9a877364382895b921e0ca364a9e59ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8089 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

    rAEOUh-XT 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 str: + base = settings.ASSETS_URL + if not base.endswith("/"): + base += "/" + return base + filename + + class TripItemStatusEnum(str, Enum): PENDING = "pending" CONFIRMED = "booked" @@ -99,7 +108,7 @@ class Category(CategoryBase, table=True): class CategoryCreate(CategoryBase): name: str - image: str + image: str | None = None class CategoryUpdate(CategoryBase): @@ -109,13 +118,16 @@ class CategoryUpdate(CategoryBase): class CategoryRead(CategoryBase): id: int - image: str - image_id: int + image: str | None + image_id: int | None @classmethod def serialize(cls, obj: Category) -> "CategoryRead": return cls( - id=obj.id, name=obj.name, image_id=obj.image_id, image=obj.image.filename if obj.image else None + id=obj.id, + name=obj.name, + image_id=obj.image_id, + image=_prefix_assets_url(obj.image.filename) if obj.image else "/favicon.png", ) @@ -194,7 +206,7 @@ class PlaceRead(PlaceBase): price=obj.price, duration=obj.duration, visited=obj.visited, - image=obj.image.filename if obj.image else None, + image=_prefix_assets_url(obj.image.filename) if obj.image else None, image_id=obj.image_id, favorite=obj.favorite, gpx=("1" if obj.gpx else None) @@ -241,7 +253,7 @@ class TripReadBase(TripBase): id=obj.id, name=obj.name, archived=obj.archived, - image=obj.image.filename if obj.image else None, + image=_prefix_assets_url(obj.image.filename) if obj.image else None, image_id=obj.image_id, days=len(obj.days), ) @@ -260,7 +272,7 @@ class TripRead(TripBase): id=obj.id, name=obj.name, archived=obj.archived, - image=obj.image.filename if obj.image else None, + image=_prefix_assets_url(obj.image.filename) if obj.image else None, image_id=obj.image_id, days=[TripDayRead.serialize(day) for day in obj.days], places=[PlaceRead.serialize(place) for place in obj.places], diff --git a/backend/trip/routers/categories.py b/backend/trip/routers/categories.py index c1db7b8..578cdf8 100644 --- a/backend/trip/routers/categories.py +++ b/backend/trip/routers/categories.py @@ -29,16 +29,17 @@ def post_category( ) -> CategoryRead: new_category = Category(name=category.name, user=current_user) - image_bytes = b64img_decode(category.image) - filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE) - if not filename: - raise HTTPException(status_code=400, detail="Bad request") + if category.image: + image_bytes = b64img_decode(category.image) + filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE) + if not filename: + raise HTTPException(status_code=400, detail="Bad request") - image = Image(filename=filename, user=current_user) - session.add(image) - session.commit() - session.refresh(image) - new_category.image_id = image.id + image = Image(filename=filename, user=current_user) + session.add(image) + session.commit() + session.refresh(image) + new_category.image_id = image.id session.add(new_category) session.commit() @@ -57,9 +58,10 @@ def put_category( verify_exists_and_owns(current_user, db_category) category_data = category.model_dump(exclude_unset=True) - if category_data.get("image"): + category_image = category_data.pop("image", None) + if category_image: try: - image_bytes = b64img_decode(category_data.pop("image")) + image_bytes = b64img_decode(category_image) except Exception: raise HTTPException(status_code=400, detail="Bad request") @@ -105,6 +107,16 @@ def delete_category( if get_category_placess_cnt(session, category_id, current_user) > 0: raise HTTPException(status_code=409, detail="The resource is not orphan") + if db_category.image: + try: + remove_image(db_category.image.filename) + session.delete(db_category.image) + except Exception: + raise HTTPException( + status_code=500, + detail="Roses are red, violets are blue, if you're reading this, I'm sorry for you", + ) + session.delete(db_category) session.commit() return {} diff --git a/src/src/app/components/dashboard/dashboard.component.html b/src/src/app/components/dashboard/dashboard.component.html index 9b95ada..5df3ec9 100644 --- a/src/src/app/components/dashboard/dashboard.component.html +++ b/src/src/app/components/dashboard/dashboard.component.html @@ -7,7 +7,7 @@ }

    - +
    @@ -65,7 +65,7 @@ @for (p of visiblePlaces; track p.id) {
    - +

    {{ p.name }}

    @@ -166,59 +166,68 @@ - -
    -
    -

    Map parameters

    - You can customize the default view on map loading + +
    +

    Low Network Mode

    + You can disable Low Network Mode. Default is true. Display Category + image instead of Place image. +
    +
    +
    Low Network Mode
    + +
    + +
    +
    +
    +

    Map parameters

    + You can customize the default view on map loading +
    + +
    - -
    +
    + + + + -
    - - - - + + + + +
    - - - - -
    +
    +

    Currency

    +
    +
    + + + + +
    -
    -

    Currency

    -
    -
    - - - - -
    - -
    -

    Filters

    - You can customize the categories and attributes to hide by - default -
    -
    - - - - -
    - - -
    - -
    +
    +

    Filters

    + You can customize the categories to hide by default +
    +
    + + + + +
    +
    + +
    + diff --git a/src/src/app/components/dashboard/dashboard.component.ts b/src/src/app/components/dashboard/dashboard.component.ts index 1e485bb..d969226 100644 --- a/src/src/app/components/dashboard/dashboard.component.ts +++ b/src/src/app/components/dashboard/dashboard.component.ts @@ -78,6 +78,7 @@ export interface MarkerOptions extends L.MarkerOptions { export class DashboardComponent implements AfterViewInit { searchInput = new FormControl(""); info: Info | undefined; + isLowNet: boolean = false; viewSettings = false; viewFilters = false; @@ -111,6 +112,7 @@ export class DashboardComponent implements AfterViewInit { private fb: FormBuilder, ) { this.currencySigns = this.utilsService.currencySigns(); + this.isLowNet = this.utilsService.isLowNet; this.settingsForm = this.fb.group({ mapLat: [ @@ -244,6 +246,13 @@ export class DashboardComponent implements AfterViewInit { if (this.viewMarkersList) this.setVisibleMarkers(); } + toggleLowNet() { + this.utilsService.toggleLowNet(); + setTimeout(() => { + this.updateMarkersAndClusters(); + }, 200); + } + get filteredPlaces(): Place[] { return this.places.filter((p) => { if (!this.filter_display_visited && p.visited) return false; @@ -264,7 +273,7 @@ export class DashboardComponent implements AfterViewInit { } placeToMarker(place: Place): L.Marker { - let marker = placeToMarker(place); + let marker = placeToMarker(place, this.isLowNet); marker .on("click", (e) => { this.selectedPlace = place; @@ -456,6 +465,7 @@ export class DashboardComponent implements AfterViewInit { if (index > -1) this.places.splice(index, 1); this.closePlaceBox(); this.updateMarkersAndClusters(); + if (this.viewMarkersList) this.setVisibleMarkers(); }, }); }, @@ -499,6 +509,7 @@ export class DashboardComponent implements AfterViewInit { setTimeout(() => { this.updateMarkersAndClusters(); }, 10); + if (this.viewMarkersList) this.setVisibleMarkers(); }, }); }, @@ -561,6 +572,7 @@ export class DashboardComponent implements AfterViewInit { toggleMarkersList() { this.viewMarkersList = !this.viewMarkersList; + if (this.viewMarkersList) this.setVisibleMarkers(); } toggleMarkersListSearch() { @@ -649,7 +661,13 @@ export class DashboardComponent implements AfterViewInit { this.activeCategories = new Set( this.categories.map((c) => c.name), ); - this.updateMarkersAndClusters(); + this.places = this.places.map((p) => { + if (p.category.id == category.id) return { ...p, category }; + return p; + }); + setTimeout(() => { + this.updateMarkersAndClusters(); + }, 100); } }, }); diff --git a/src/src/app/components/trip/trip.component.html b/src/src/app/components/trip/trip.component.html index cbe4d8c..748f987 100644 --- a/src/src/app/components/trip/trip.component.html +++ b/src/src/app/components/trip/trip.component.html @@ -93,7 +93,7 @@ @if (tripitem.place) {
    - {{ tripitem.place.name }}
    @@ -146,7 +146,8 @@
    @@ -224,10 +225,14 @@ {{ trip?.name }} places
    - +
    + + +
    -
    +
    +
    @if (!selectedItem) { @@ -259,7 +264,7 @@ @for (p of places; track p.id) {
    - +

    {{ p.name }}

    @@ -270,7 +275,7 @@ class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded flex gap-2 items-center truncate">{{ p.category.name }} - @if (p.placeUsage) { + @if (isPlaceUsed(p.id)) { } @else { @@ -395,4 +400,15 @@ }
    - \ No newline at end of file + + +@if (isMapFullscreen) { +
    + +
    + +
    + +
    +} \ No newline at end of file diff --git a/src/src/app/components/trip/trip.component.scss b/src/src/app/components/trip/trip.component.scss index 7c6b484..f22b193 100644 --- a/src/src/app/components/trip/trip.component.scss +++ b/src/src/app/components/trip/trip.component.scss @@ -8,3 +8,13 @@ background-color: white !important; } } + +.fullscreen-map { + position: fixed !important; + top: 0 !important; + left: 0 !important; + width: 100vw !important; + height: 100vh !important; + border-radius: 0 !important; + box-shadow: none !important; +} diff --git a/src/src/app/components/trip/trip.component.ts b/src/src/app/components/trip/trip.component.ts index 38cc16a..b2555f1 100644 --- a/src/src/app/components/trip/trip.component.ts +++ b/src/src/app/components/trip/trip.component.ts @@ -31,10 +31,6 @@ import { AsyncPipe } from "@angular/common"; import { MenuItem } from "primeng/api"; import { MenuModule } from "primeng/menu"; -interface PlaceWithUsage extends Place { - placeUsage?: boolean; -} - @Component({ selector: "app-trip", standalone: true, @@ -59,15 +55,17 @@ export class TripComponent implements AfterViewInit { statuses: TripStatus[] = []; hoveredElement: HTMLElement | undefined; currency$: Observable; + placesUsedInTable = new Set(); trip: Trip | undefined; tripMapAntLayer: undefined; tripMapAntLayerDayID: number | undefined; + isMapFullscreen: boolean = false; totalPrice: number = 0; dayStatsCache = new Map(); - places: PlaceWithUsage[] = []; + places: Place[] = []; flattenedTripItems: FlattenedTripItem[] = []; menuTripActionsItems: MenuItem[] = []; @@ -176,7 +174,18 @@ export class TripComponent implements AfterViewInit { this.updateTotalPrice(); - this.map = createMap(); + let contentMenuItems = [ + { + text: "Copy coordinates", + callback: (e: any) => { + const latlng = e.latlng; + navigator.clipboard.writeText( + `${parseFloat(latlng.lat).toFixed(5)}, ${parseFloat(latlng.lng).toFixed(5)}`, + ); + }, + }, + ]; + this.map = createMap(contentMenuItems); this.markerClusterGroup = createClusterGroup().addTo(this.map); this.setPlacesAndMarkers(); @@ -224,6 +233,10 @@ export class TripComponent implements AfterViewInit { })) as (TripItem & { status: TripStatus })[]; } + isPlaceUsed(id: number): boolean { + return this.placesUsedInTable.has(id); + } + statusToTripStatus(status?: string): TripStatus | undefined { if (!status) return undefined; return this.statuses.find((s) => s.label == status) as TripStatus; @@ -250,18 +263,21 @@ export class TripComponent implements AfterViewInit { ); } - setPlacesAndMarkers() { - let usedPlaces = this.flattenedTripItems.map((i) => i.place?.id); - this.places = (this.trip?.places || []).map((p) => { - let ret: PlaceWithUsage = { ...p }; - if (usedPlaces.includes(p.id)) ret.placeUsage = true; - return ret; + makePlacesUsedInTable() { + this.placesUsedInTable.clear(); + this.flattenedTripItems.forEach((i) => { + if (i.place?.id) this.placesUsedInTable.add(i.place.id); }); + } + + setPlacesAndMarkers() { + this.makePlacesUsedInTable(); + this.places = this.trip?.places || []; this.places.sort((a, b) => a.name.localeCompare(b.name)); this.markerClusterGroup?.clearLayers(); this.places.forEach((p) => { - const marker = placeToMarker(p); + const marker = placeToMarker(p, false); this.markerClusterGroup?.addLayer(marker); }); } @@ -274,6 +290,15 @@ export class TripComponent implements AfterViewInit { ); } + toggleMapFullscreen() { + this.isMapFullscreen = !this.isMapFullscreen; + + setTimeout(() => { + this.map.invalidateSize(); + this.resetMapBounds(); + }, 50); + } + updateTotalPrice(n?: number) { if (n) this.totalPrice += n; else @@ -352,7 +377,7 @@ export class TripComponent implements AfterViewInit { this.map.fitBounds(coords, { padding: [30, 30] }); const path = antPath(coords, { - delay: 400, + delay: 600, dashArray: [10, 20], weight: 5, color: "#0000FF", @@ -607,6 +632,7 @@ export class TripComponent implements AfterViewInit { this.trip?.days!, ); this.dayStatsCache.delete(day.id); + this.makePlacesUsedInTable(); } }, }); @@ -685,6 +711,7 @@ export class TripComponent implements AfterViewInit { ); } if (item.price) this.updateTotalPrice(item.price); + if (item.place?.id) this.placesUsedInTable.add(item.place.id); }, }); }, @@ -718,6 +745,7 @@ export class TripComponent implements AfterViewInit { modal.onClose.subscribe({ next: (it: TripItem | null) => { if (!it) return; + if (item.place?.id) this.placesUsedInTable.delete(item.place.id); this.apiService .putTripDayItem(it, this.trip?.id!, item.day_id, item.id) @@ -744,6 +772,7 @@ export class TripComponent implements AfterViewInit { this.dayStatsCache.delete(item.day_id); } + if (item.place?.id) this.placesUsedInTable.add(item.place.id); const updatedPrice = -(item.price || 0) + (it.price || 0); this.updateTotalPrice(updatedPrice); @@ -786,6 +815,8 @@ export class TripComponent implements AfterViewInit { this.flattenedTripItems = this.flattenTripDayItems( this.trip?.days!, ); + if (item.place?.id) + this.placesUsedInTable.delete(item.place.id); this.dayStatsCache.delete(item.day_id); this.selectedItem = undefined; this.resetPlaceHighlightMarker(); diff --git a/src/src/app/components/trips/trips.component.html b/src/src/app/components/trips/trips.component.html index 77cbacb..625f058 100644 --- a/src/src/app/components/trips/trips.component.html +++ b/src/src/app/components/trips/trips.component.html @@ -15,7 +15,7 @@
    + [src]="trip.image || 'cover.webp'" />

    {{ trip.name }}

    diff --git a/src/src/app/modals/category-create-modal/category-create-modal.component.ts b/src/src/app/modals/category-create-modal/category-create-modal.component.ts index 013746f..3bbd5ae 100644 --- a/src/src/app/modals/category-create-modal/category-create-modal.component.ts +++ b/src/src/app/modals/category-create-modal/category-create-modal.component.ts @@ -1,5 +1,10 @@ import { Component } from "@angular/core"; -import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { + FormBuilder, + FormGroup, + ReactiveFormsModule, + Validators, +} from "@angular/forms"; import { ButtonModule } from "primeng/button"; import { DynamicDialogConfig, DynamicDialogRef } from "primeng/dynamicdialog"; import { FloatLabelModule } from "primeng/floatlabel"; @@ -8,7 +13,13 @@ import { FocusTrapModule } from "primeng/focustrap"; @Component({ selector: "app-category-create-modal", - imports: [FloatLabelModule, InputTextModule, ButtonModule, ReactiveFormsModule, FocusTrapModule], + imports: [ + FloatLabelModule, + InputTextModule, + ButtonModule, + ReactiveFormsModule, + FocusTrapModule, + ], standalone: true, templateUrl: "./category-create-modal.component.html", styleUrl: "./category-create-modal.component.scss", @@ -21,12 +32,12 @@ export class CategoryCreateModalComponent { constructor( private ref: DynamicDialogRef, private fb: FormBuilder, - private config: DynamicDialogConfig + private config: DynamicDialogConfig, ) { this.categoryForm = this.fb.group({ id: -1, name: ["", Validators.required], - image: ["", Validators.required], + image: null, }); if (this.config.data) { diff --git a/src/src/app/modals/place-create-modal/place-create-modal.component.ts b/src/src/app/modals/place-create-modal/place-create-modal.component.ts index 11d6270..f755338 100644 --- a/src/src/app/modals/place-create-modal/place-create-modal.component.ts +++ b/src/src/app/modals/place-create-modal/place-create-modal.component.ts @@ -99,7 +99,6 @@ export class PlaceCreateModalComponent { if (this.config.data) { let patchValue: Place = this.config.data.place; - if (patchValue.imageDefault) delete patchValue["image"]; this.placeForm.patchValue(patchValue); } diff --git a/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.html b/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.html index b9f640b..b72f826 100644 --- a/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.html +++ b/src/src/app/modals/trip-create-day-item-modal/trip-create-day-item-modal.component.html @@ -31,7 +31,7 @@
    - +
    {{ place.name }}
    diff --git a/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.html b/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.html index 1df0cba..e4f6163 100644 --- a/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.html +++ b/src/src/app/modals/trip-place-select-modal/trip-place-select-modal.component.html @@ -24,7 +24,7 @@ @for (p of selectedPlaces; track p.id) {
    - +

    {{ p.name }}

    diff --git a/src/src/app/services/api.service.ts b/src/src/app/services/api.service.ts index 6bf145f..1643a0c 100644 --- a/src/src/app/services/api.service.ts +++ b/src/src/app/services/api.service.ts @@ -4,7 +4,6 @@ import { Category, Place } from "../types/poi"; import { BehaviorSubject, distinctUntilChanged, - map, Observable, shareReplay, tap, @@ -18,7 +17,6 @@ import { Trip, TripBase, TripDay, TripItem } from "../types/trip"; }) export class ApiService { public apiBaseUrl: string = "/api"; - public assetsBaseUrl: string = "/api/assets"; private categoriesSubject = new BehaviorSubject(null); public categories$: Observable = @@ -33,23 +31,6 @@ export class ApiService { return this.httpClient.get(this.apiBaseUrl + "/info"); } - _normalizeTripImage(trip: Trip | TripBase): Trip | TripBase { - if (trip.image) trip.image = `${this.assetsBaseUrl}/${trip.image}`; - else trip.image = "cover.webp"; - return trip; - } - - _normalizePlaceImage(place: Place): Place { - if (place.image) { - place.image = `${this.assetsBaseUrl}/${place.image}`; - place.imageDefault = false; - } else { - place.image = `${this.assetsBaseUrl}/${(place.category as Category).image}`; - place.imageDefault = true; - } - return place; - } - _categoriesSubjectNext(categories: Category[]) { this.categoriesSubject.next( categories.sort((categoryA: Category, categoryB: Category) => @@ -63,11 +44,6 @@ export class ApiService { return this.httpClient .get(`${this.apiBaseUrl}/categories`) .pipe( - map((resp) => { - return resp.map((c) => { - return { ...c, image: `${this.assetsBaseUrl}/${c.image}` }; - }); - }), tap((categories) => this._categoriesSubjectNext(categories)), distinctUntilChanged(), shareReplay(), @@ -80,12 +56,6 @@ export class ApiService { return this.httpClient .post(this.apiBaseUrl + "/categories", c) .pipe( - map((category) => { - return { - ...category, - image: `${this.assetsBaseUrl}/${category.image}`, - }; - }), tap((category) => this._categoriesSubjectNext([ ...(this.categoriesSubject.value || []), @@ -99,12 +69,6 @@ export class ApiService { return this.httpClient .put(this.apiBaseUrl + `/categories/${c_id}`, c) .pipe( - map((category) => { - return { - ...category, - image: `${this.assetsBaseUrl}/${category.image}`, - }; - }), tap((category) => { let categories = this.categoriesSubject.value || []; let categoryIndex = categories?.findIndex((c) => c.id == c_id) || -1; @@ -133,29 +97,27 @@ export class ApiService { } getPlaces(): Observable { - return this.httpClient.get(`${this.apiBaseUrl}/places`).pipe( - map((resp) => resp.map((p) => this._normalizePlaceImage(p))), - distinctUntilChanged(), - shareReplay(), - ); + return this.httpClient + .get(`${this.apiBaseUrl}/places`) + .pipe(distinctUntilChanged(), shareReplay()); } postPlace(place: Place): Observable { - return this.httpClient - .post(`${this.apiBaseUrl}/places`, place) - .pipe(map((p) => this._normalizePlaceImage(p))); + return this.httpClient.post(`${this.apiBaseUrl}/places`, place); } postPlaces(places: Partial): Observable { - return this.httpClient - .post(`${this.apiBaseUrl}/places/batch`, places) - .pipe(map((resp) => resp.map((p) => this._normalizePlaceImage(p)))); + return this.httpClient.post( + `${this.apiBaseUrl}/places/batch`, + places, + ); } putPlace(place_id: number, place: Partial): Observable { - return this.httpClient - .put(`${this.apiBaseUrl}/places/${place_id}`, place) - .pipe(map((p) => this._normalizePlaceImage(p))); + return this.httpClient.put( + `${this.apiBaseUrl}/places/${place_id}`, + place, + ); } deletePlace(place_id: number): Observable { @@ -165,50 +127,23 @@ export class ApiService { } getPlaceGPX(place_id: number): Observable { - return this.httpClient - .get(`${this.apiBaseUrl}/places/${place_id}`) - .pipe(map((p) => this._normalizePlaceImage(p))); + return this.httpClient.get(`${this.apiBaseUrl}/places/${place_id}`); } getTrips(): Observable { - return this.httpClient.get(`${this.apiBaseUrl}/trips`).pipe( - map((resp) => { - return resp.map((trip: TripBase) => { - trip = this._normalizeTripImage(trip) as TripBase; - return trip; - }); - }), - distinctUntilChanged(), - shareReplay(), - ); + return this.httpClient + .get(`${this.apiBaseUrl}/trips`) + .pipe(distinctUntilChanged(), shareReplay()); } getTrip(id: number): Observable { - return this.httpClient.get(`${this.apiBaseUrl}/trips/${id}`).pipe( - map((trip) => { - trip = this._normalizeTripImage(trip) as Trip; - trip.places = trip.places.map((p) => this._normalizePlaceImage(p)); - trip.days.map((day) => { - day.items.forEach((item) => { - if (item.place) this._normalizePlaceImage(item.place); - }); - }); - return trip; - }), - distinctUntilChanged(), - shareReplay(), - ); + return this.httpClient + .get(`${this.apiBaseUrl}/trips/${id}`) + .pipe(distinctUntilChanged(), shareReplay()); } postTrip(trip: TripBase): Observable { - return this.httpClient - .post(`${this.apiBaseUrl}/trips`, trip) - .pipe( - map((trip) => { - trip = this._normalizeTripImage(trip) as TripBase; - return trip; - }), - ); + return this.httpClient.post(`${this.apiBaseUrl}/trips`, trip); } deleteTrip(trip_id: number): Observable { @@ -216,20 +151,10 @@ export class ApiService { } putTrip(trip: Partial, trip_id: number): Observable { - return this.httpClient - .put(`${this.apiBaseUrl}/trips/${trip_id}`, trip) - .pipe( - map((trip) => { - trip = this._normalizeTripImage(trip) as Trip; - trip.places = trip.places.map((p) => this._normalizePlaceImage(p)); - trip.days.map((day) => { - day.items.forEach((item) => { - if (item.place) this._normalizePlaceImage(item.place); - }); - }); - return trip; - }), - ); + return this.httpClient.put( + `${this.apiBaseUrl}/trips/${trip_id}`, + trip, + ); } postTripDay(tripDay: TripDay, trip_id: number): Observable { @@ -240,19 +165,10 @@ export class ApiService { } putTripDay(tripDay: Partial, trip_id: number): Observable { - return this.httpClient - .put( - `${this.apiBaseUrl}/trips/${trip_id}/days/${tripDay.id}`, - tripDay, - ) - .pipe( - map((td) => { - td.items.forEach((item) => { - if (item.place) this._normalizePlaceImage(item.place); - }); - return td; - }), - ); + return this.httpClient.put( + `${this.apiBaseUrl}/trips/${trip_id}/days/${tripDay.id}`, + tripDay, + ); } deleteTripDay(trip_id: number, day_id: number): Observable { @@ -266,17 +182,10 @@ export class ApiService { trip_id: number, day_id: number, ): Observable { - return this.httpClient - .post( - `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items`, - item, - ) - .pipe( - map((item) => { - if (item.place) item.place = this._normalizePlaceImage(item.place); - return item; - }), - ); + return this.httpClient.post( + `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items`, + item, + ); } putTripDayItem( @@ -285,17 +194,10 @@ export class ApiService { day_id: number, item_id: number, ): Observable { - return this.httpClient - .put( - `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items/${item_id}`, - item, - ) - .pipe( - map((item) => { - if (item.place) item.place = this._normalizePlaceImage(item.place); - return item; - }), - ); + return this.httpClient.put( + `${this.apiBaseUrl}/trips/${trip_id}/days/${day_id}/items/${item_id}`, + item, + ); } deleteTripDayItem( @@ -336,21 +238,10 @@ export class ApiService { settingsUserImport(formdata: FormData): Observable { const headers = { enctype: "multipart/form-data" }; - return this.httpClient - .post< - Place[] - >(`${this.apiBaseUrl}/settings/import`, formdata, { headers: headers }) - .pipe( - map((resp) => { - return resp.map((c) => { - if (c.image) c.image = `${this.assetsBaseUrl}/${c.image}`; - else { - c.image = `${this.assetsBaseUrl}/${(c.category as Category).image}`; - c.imageDefault = true; - } - return c; - }); - }), - ); + return this.httpClient.post( + `${this.apiBaseUrl}/settings/import`, + formdata, + { headers: headers }, + ); } } diff --git a/src/src/app/services/utils.service.ts b/src/src/app/services/utils.service.ts index 2bfade8..8afa3bd 100644 --- a/src/src/app/services/utils.service.ts +++ b/src/src/app/services/utils.service.ts @@ -4,19 +4,33 @@ import { TripStatus } from "../types/trip"; import { ApiService } from "./api.service"; import { map } from "rxjs"; +const DISABLE_LOWNET = "TRIP_DISABLE_LOWNET"; + @Injectable({ providedIn: "root", }) export class UtilsService { private apiService = inject(ApiService); currency$ = this.apiService.settings$.pipe(map((s) => s?.currency ?? "€")); + public isLowNet: boolean = true; - constructor(private ngMessageService: MessageService) {} + constructor(private ngMessageService: MessageService) { + this.isLowNet = !localStorage.getItem(DISABLE_LOWNET); + } toGithubTRIP() { window.open("https://github.com/itskovacs/trip", "_blank"); } + toggleLowNet() { + if (this.isLowNet) { + localStorage.setItem(DISABLE_LOWNET, "1"); + } else { + localStorage.removeItem(DISABLE_LOWNET); + } + this.isLowNet = !this.isLowNet; + } + get statuses(): TripStatus[] { return [ { label: "pending", color: "#3258A8" }, @@ -35,17 +49,6 @@ export class UtilsService { }); } - getObjectDiffFields(a: T, b: T): Partial { - const diff: Partial = {}; - - for (const key in b) { - if (!Object.is(a[key], b[key]) && JSON.stringify(a[key]) !== JSON.stringify(b[key])) { - diff[key] = b[key]; - } - } - return diff; - } - parseGoogleMapsUrl(url: string): [string, string] { const match = url.match(/place\/(.*)\/@([\d\-.]+,[\d\-.]+)/); diff --git a/src/src/app/shared/map.ts b/src/src/app/shared/map.ts index 946de93..8a71251 100644 --- a/src/src/app/shared/map.ts +++ b/src/src/app/shared/map.ts @@ -69,7 +69,10 @@ export function createClusterGroup(): L.MarkerClusterGroup { }); } -export function placeToMarker(place: Place): L.Marker { +export function placeToMarker( + place: Place, + isLowNet: boolean = true, +): L.Marker { let marker: L.Marker; let options: any = { riseOnHover: true, @@ -79,8 +82,13 @@ export function placeToMarker(place: Place): L.Marker { }; marker = new L.Marker([+place.lat, +place.lng], options); + + const markerImage = isLowNet + ? place.category.image + : (place.image ?? place.category.image); + marker.options.icon = L.icon({ - iconUrl: place.image!, + iconUrl: markerImage, iconSize: [56, 56], iconAnchor: [28, 28], shadowSize: [0, 0], diff --git a/src/src/app/shared/place-box/place-box.component.html b/src/src/app/shared/place-box/place-box.component.html index 771cfcd..b56ce2b 100644 --- a/src/src/app/shared/place-box/place-box.component.html +++ b/src/src/app/shared/place-box/place-box.component.html @@ -2,9 +2,10 @@
    -
    - -
    +
    + +

    {{ selectedPlace.name }}

    diff --git a/src/src/app/types/poi.ts b/src/src/app/types/poi.ts index 135033b..0b7b776 100644 --- a/src/src/app/types/poi.ts +++ b/src/src/app/types/poi.ts @@ -22,5 +22,4 @@ export interface Place { allowdog?: boolean; visited?: boolean; favorite?: boolean; - imageDefault?: boolean; // Injected in service }