Compare commits
2 commits
2d1d0ba1d0
...
43482d9ba9
| Author | SHA1 | Date | |
|---|---|---|---|
| 43482d9ba9 | |||
| 4981c7eaed |
3 changed files with 221 additions and 82 deletions
62
CHANGELOG.md
62
CHANGELOG.md
|
|
@ -9167,3 +9167,65 @@ Neste steg:
|
||||||
egen, enklere side-veiviser uten dette detalj-steget), ekte
|
egen, enklere side-veiviser uten dette detalj-steget), ekte
|
||||||
Mapbox-tokens, og migrering av `060_round_shot.sql` mot ekte
|
Mapbox-tokens, og migrering av `060_round_shot.sql` mot ekte
|
||||||
`teecup_db` (krever egen, eksplisitt brukerbekreftelse).
|
`teecup_db` (krever egen, eksplisitt brukerbekreftelse).
|
||||||
|
|
||||||
|
46. **Slag-for-slag GPS-avstandsmåling — inngangspunkt 2 av 2, alltid-
|
||||||
|
synlig merkelapp på hull-kortet — 2026-08-08 (ADR-048).** Fullfører
|
||||||
|
v1-kravet om å dekke BEGGE hull-eiertyper fra start (punkt 45 dekket
|
||||||
|
kun deltaker-eide hull via `ScoringWizard`).
|
||||||
|
|
||||||
|
Refaktorerte punkt 45s inline logikk (state + fetch + submit, som satt
|
||||||
|
direkte i `ScoringWizard`) ut til en ny, delt lokal funksjon
|
||||||
|
`ShotMeasurementEntry` i `round-detail.tsx` -- samme
|
||||||
|
"lokal gjenbruk innad i filen, ikke en egen delt-fil"-mønster som
|
||||||
|
resten av filens komponenter (`NumberPicker`/`Stepper`/`ChoiceRow`
|
||||||
|
m.fl., se `DESIGN_SYSTEM.md`s "Komponentmønstre"-seksjon), siden alle
|
||||||
|
tre bruksstedene nå ligger i samme fil. Komponenten bygger riktig
|
||||||
|
URL-base (`.../participants/{id}/holes/{n}/shots` vs.
|
||||||
|
`.../sides/{id}/holes/{n}/shots`) fra en enkel `owner: {kind, id}`-
|
||||||
|
prop -- ingen egen gren utover selve URL-en trengs, ADR-048s speilede
|
||||||
|
endepunkt-par dekker begge hull-eiertyper identisk.
|
||||||
|
|
||||||
|
Koblet inn tre steder:
|
||||||
|
- `ScoringWizard`s detalj-steg (uendret fra punkt 45, nå bare kalt
|
||||||
|
via den delte komponenten i stedet for egen kopi).
|
||||||
|
- `PlayerHoleCards` (deltaker-eide hull, hoved-"Score"-fanens
|
||||||
|
spillerkort): en liten merkelapp-rad lagt til som SØSKEN av kortets
|
||||||
|
store klikkbare knapp (ikke nøstet inni -- ugyldig HTML/ARIA å neste
|
||||||
|
`<button>` i `<button>`), synlig uavhengig av om veiviseren er åpen,
|
||||||
|
for retroaktiv måling.
|
||||||
|
- `SideScorecardGrid` (side-eide hull, delt-ball-formater): siden selve
|
||||||
|
scorekort-gridet er en tett 52px-per-hull-tabell uten plass til en
|
||||||
|
egen knapp per rute, ble to merkelapper (én per side, med lag-
|
||||||
|
etikett, f.eks. "Rødt lag: 2 slag målt") lagt til som en egen rad
|
||||||
|
rett under gridet, ved siden av "Forrige/Neste hull"-knappene.
|
||||||
|
|
||||||
|
**Scratch-verifisert i ekte nettleser** (sekstende scratch-miljø
|
||||||
|
denne økten, samme migrasjon-002-forsiktighet, `teecup_db` bekreftet
|
||||||
|
uendret) -- **med et ekte Mapbox-token satt inn i `.env` av brukeren
|
||||||
|
midt i denne runden** (`NEXT_PUBLIC_MAPBOX_TOKEN`, offentlig/URL-
|
||||||
|
restriktert; `TEECUP_MAPBOX_SECRET_TOKEN` for delings-satellittbilder
|
||||||
|
fortsatt ikke mottatt): opprettet én vanlig slagspill-runde (deltaker-
|
||||||
|
eid) og én foursome-runde med to sider (side-eid, via egen-opprettede
|
||||||
|
sider + en gjest lagt til side B) direkte mot API-et. Bekreftet
|
||||||
|
merkelappen vises korrekt og uavhengig av kortknappen i
|
||||||
|
`PlayerHoleCards`, åpner arket direkte (ikke via veiviseren). Testet
|
||||||
|
"Velg punkt på kart"-veien med det ekte tokenet -- Mapbox avviste med
|
||||||
|
403 (URL-restriksjonen tillater kun det ekte domenet, ikke dette
|
||||||
|
scratch-miljøets `localhost`-opprinnelse), som bekreftet at
|
||||||
|
feilhåndteringen i `MapPointPicker` fungerer rent (ren "Kunne ikke
|
||||||
|
laste kartet"-melding, ingen krasj) -- selve kart-SUKSESS-stien kunne
|
||||||
|
ikke click-through-testes i dette miljøet av samme grunn. For
|
||||||
|
side-eide hull: bekreftet begge merkelapper ("Blått lag"/"Rødt lag")
|
||||||
|
henter riktig antall fra sine respektive `.../sides/{id}/holes/{n}/
|
||||||
|
shots`-endepunkt (bekreftet i API-loggen), og at et slag opprettet på
|
||||||
|
én side kun oppdaterer DEN sidens merkelapp, ikke den andres. Samme
|
||||||
|
GPS-tillatelse-miljøbegrensning som punkt 45 gjaldt fortsatt for
|
||||||
|
GPS-suksess-stien; de eksakte `submitShot`-kallene ble derfor igjen
|
||||||
|
verifisert direkte mot API-et for begge eiertyper. Scratch-miljøet
|
||||||
|
ryddet opp fullstendig.
|
||||||
|
|
||||||
|
**Ikke rullet ut** -- samme gjenstående blokkere som punkt 45:
|
||||||
|
`round_shot`-tabellen finnes ikke i ekte `teecup_db` ennå
|
||||||
|
(migrasjonen aldri kjørt der, krever egen brukerbekreftelse), og
|
||||||
|
`TEECUP_MAPBOX_SECRET_TOKEN` (for delings-satellittbilder) mangler
|
||||||
|
fortsatt.
|
||||||
|
|
|
||||||
|
|
@ -1522,6 +1522,22 @@ export function RoundDetail({ roundId }: { roundId: string }) {
|
||||||
setWizardSideId(sideId)
|
setWizardSideId(sideId)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{!readOnly && round.sides.length === 2 && (
|
||||||
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
|
{round.sides.map((s, i) => (
|
||||||
|
<ShotMeasurementEntry
|
||||||
|
key={s.id}
|
||||||
|
roundId={roundId}
|
||||||
|
holeNumber={activeHole}
|
||||||
|
owner={{ kind: "side", id: s.id }}
|
||||||
|
ownBagClubs={ownBagClubs}
|
||||||
|
readOnly={readOnly}
|
||||||
|
variant="badge"
|
||||||
|
label={s.label?.trim() || (i === 0 ? "Side A" : "Side B")}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Button type="button" variant="outline" onClick={goPrev} className="h-14 rounded-2xl text-base font-bold">
|
<Button type="button" variant="outline" onClick={goPrev} className="h-14 rounded-2xl text-base font-bold">
|
||||||
<ChevronLeft aria-hidden="true" className="size-5" />
|
<ChevronLeft aria-hidden="true" className="size-5" />
|
||||||
|
|
@ -1550,6 +1566,7 @@ export function RoundDetail({ roundId }: { roundId: string }) {
|
||||||
) : (
|
) : (
|
||||||
<section ref={holePanelRef} className="mt-5 flex scroll-mt-28 flex-col gap-4">
|
<section ref={holePanelRef} className="mt-5 flex scroll-mt-28 flex-col gap-4">
|
||||||
<PlayerHoleCards
|
<PlayerHoleCards
|
||||||
|
roundId={roundId}
|
||||||
players={players}
|
players={players}
|
||||||
holesByParticipant={holesByParticipant}
|
holesByParticipant={holesByParticipant}
|
||||||
holeOrder={holeOrder}
|
holeOrder={holeOrder}
|
||||||
|
|
@ -1559,6 +1576,7 @@ export function RoundDetail({ roundId }: { roundId: string }) {
|
||||||
sides={round.sides}
|
sides={round.sides}
|
||||||
isTwoSided={TWO_SIDED_FORMATS.has(round.play_format)}
|
isTwoSided={TWO_SIDED_FORMATS.has(round.play_format)}
|
||||||
formatResult={formatResult}
|
formatResult={formatResult}
|
||||||
|
ownBagClubs={ownBagClubs}
|
||||||
onSelectHole={(n) => {
|
onSelectHole={(n) => {
|
||||||
setCurrentHole(n)
|
setCurrentHole(n)
|
||||||
scrollToHolePanel()
|
scrollToHolePanel()
|
||||||
|
|
@ -1738,71 +1756,6 @@ function ScoringWizard({
|
||||||
const isLastPlayer = playerIndex === -1 || playerIndex === players.length - 1
|
const isLastPlayer = playerIndex === -1 || playerIndex === players.length - 1
|
||||||
const nextPlayer = !isLastPlayer ? players[playerIndex + 1] : null
|
const nextPlayer = !isLastPlayer ? players[playerIndex + 1] : null
|
||||||
|
|
||||||
// Slag-for-slag GPS-avstandsmåling (ADR-048, 2026-08-08) -- kun
|
|
||||||
// deltaker-eide hull (denne veiviseren brukes utelukkende for
|
|
||||||
// per-spiller-formater, se ScoringWizard sin egen kommentar over --
|
|
||||||
// delt-ball-formater bruker en egen, enklere side-veiviser uten dette
|
|
||||||
// steget). `shotCount` lastes på nytt hver gang hull/spiller endres.
|
|
||||||
const [shotSheetOpen, setShotSheetOpen] = useState(false)
|
|
||||||
const [shotCount, setShotCount] = useState(0)
|
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false
|
|
||||||
fetch(`/rounds/${roundId}/participants/${player.id}/holes/${hole.holeNumber}/shots`, { credentials: "include" })
|
|
||||||
.then((res) => (res.ok ? res.json() : []))
|
|
||||||
.then((shots: unknown[]) => {
|
|
||||||
if (!cancelled) setShotCount(shots.length)
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [roundId, player.id, hole.holeNumber])
|
|
||||||
|
|
||||||
async function submitShot(result: {
|
|
||||||
club: string
|
|
||||||
distanceMeters: number
|
|
||||||
startMethod: "gps" | "map_tap"
|
|
||||||
startLat: number
|
|
||||||
startLng: number
|
|
||||||
endLat: number
|
|
||||||
endLng: number
|
|
||||||
share: boolean
|
|
||||||
shareText: string
|
|
||||||
}) {
|
|
||||||
const res = await fetch(
|
|
||||||
`/rounds/${roundId}/participants/${player.id}/holes/${hole.holeNumber}/shots`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify({
|
|
||||||
club: result.club,
|
|
||||||
distance_meters: result.distanceMeters,
|
|
||||||
start_method: result.startMethod,
|
|
||||||
start_lat: result.startLat,
|
|
||||||
start_lng: result.startLng,
|
|
||||||
end_lat: result.endLat,
|
|
||||||
end_lng: result.endLng,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (!res.ok) {
|
|
||||||
setShotSheetOpen(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const shot = await res.json()
|
|
||||||
if (result.share) {
|
|
||||||
await fetch(`/rounds/${roundId}/shots/${shot.id}/share`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify({ body: result.shareText }),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setShotCount((n) => n + 1)
|
|
||||||
setShotSheetOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const steps = wizardStepsFor(player.statLevel)
|
const steps = wizardStepsFor(player.statLevel)
|
||||||
const [stepIndex, setStepIndex] = useState(0)
|
const [stepIndex, setStepIndex] = useState(0)
|
||||||
// Nullstill til steg 1 hver gang veiviseren åpnes for en NY spiller --
|
// Nullstill til steg 1 hver gang veiviseren åpnes for en NY spiller --
|
||||||
|
|
@ -2007,16 +1960,14 @@ function ScoringWizard({
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!readOnly && (
|
<ShotMeasurementEntry
|
||||||
<button
|
roundId={roundId}
|
||||||
type="button"
|
holeNumber={hole.holeNumber}
|
||||||
onClick={() => setShotSheetOpen(true)}
|
owner={{ kind: "participant", id: player.id }}
|
||||||
className="flex min-h-11 items-center justify-center gap-2 rounded-xl border border-dashed border-border bg-card px-4 text-base font-semibold text-foreground transition-colors hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
ownBagClubs={player.isSelf ? ownBagClubs : []}
|
||||||
>
|
readOnly={readOnly}
|
||||||
<Ruler className="size-5 shrink-0" aria-hidden="true" />
|
variant="inline"
|
||||||
{shotCount > 0 ? `Mål et slag (${shotCount} målt)` : "Mål et slag"}
|
/>
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hole.par !== 3 && (
|
{hole.par !== 3 && (
|
||||||
<DirectionCross
|
<DirectionCross
|
||||||
|
|
@ -2080,17 +2031,127 @@ function ScoringWizard({
|
||||||
{isLastStep ? (isLastPlayer ? "Ferdig" : `Neste: ${nextPlayer?.name}`) : "Neste"}
|
{isLastStep ? (isLastPlayer ? "Ferdig" : `Neste: ${nextPlayer?.name}`) : "Neste"}
|
||||||
</Button>
|
</Button>
|
||||||
</footer>
|
</footer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
{shotSheetOpen && (
|
// Slag-for-slag GPS-avstandsmåling (ADR-048, 2026-08-08) -- delt mellom
|
||||||
|
// tre bruksteder: ScoringWizard sitt detalj-steg (inline-variant,
|
||||||
|
// deltaker-eid hull), PlayerHoleCards sin alltid-synlige merkelapp
|
||||||
|
// (badge-variant, deltaker-eid, retroaktiv måling), og SideScorecardGrid
|
||||||
|
// sin tilsvarende merkelapp per side (badge-variant, side-eid hull --
|
||||||
|
// dekker delt-ball-formatene som ScoringWizard aldri når siden de bruker
|
||||||
|
// en egen, enklere side-veiviser uten dette steget). Samme
|
||||||
|
// urlBase-konstruksjon dekker begge hull-eiertyper (ADR-048 sine speilede
|
||||||
|
// deltaker-/side-endepunkt), ingen egen gren trengs utover selve URL-en.
|
||||||
|
function ShotMeasurementEntry({
|
||||||
|
roundId,
|
||||||
|
holeNumber,
|
||||||
|
owner,
|
||||||
|
ownBagClubs,
|
||||||
|
readOnly,
|
||||||
|
variant,
|
||||||
|
label,
|
||||||
|
}: {
|
||||||
|
roundId: string
|
||||||
|
holeNumber: number
|
||||||
|
owner: { kind: "participant" | "side"; id: string }
|
||||||
|
ownBagClubs: string[]
|
||||||
|
readOnly: boolean
|
||||||
|
variant: "inline" | "badge"
|
||||||
|
label?: string
|
||||||
|
}) {
|
||||||
|
const urlBase = `/rounds/${roundId}/${owner.kind === "participant" ? "participants" : "sides"}/${owner.id}/holes/${holeNumber}/shots`
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [count, setCount] = useState(0)
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
fetch(urlBase, { credentials: "include" })
|
||||||
|
.then((res) => (res.ok ? res.json() : []))
|
||||||
|
.then((shots: unknown[]) => {
|
||||||
|
if (!cancelled) setCount(shots.length)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [urlBase])
|
||||||
|
|
||||||
|
async function submitShot(result: {
|
||||||
|
club: string
|
||||||
|
distanceMeters: number
|
||||||
|
startMethod: "gps" | "map_tap"
|
||||||
|
startLat: number
|
||||||
|
startLng: number
|
||||||
|
endLat: number
|
||||||
|
endLng: number
|
||||||
|
share: boolean
|
||||||
|
shareText: string
|
||||||
|
}) {
|
||||||
|
const res = await fetch(urlBase, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify({
|
||||||
|
club: result.club,
|
||||||
|
distance_meters: result.distanceMeters,
|
||||||
|
start_method: result.startMethod,
|
||||||
|
start_lat: result.startLat,
|
||||||
|
start_lng: result.startLng,
|
||||||
|
end_lat: result.endLat,
|
||||||
|
end_lng: result.endLng,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if (!res.ok) {
|
||||||
|
setOpen(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const shot = await res.json()
|
||||||
|
if (result.share) {
|
||||||
|
await fetch(`/rounds/${roundId}/shots/${shot.id}/share`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify({ body: result.shareText }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setCount((n) => n + 1)
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readOnly) return null
|
||||||
|
|
||||||
|
const text = count > 0 ? `${label ? `${label}: ` : ""}${count} slag målt` : `${label ? `${label}: ` : ""}Mål et slag`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
setOpen(true)
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center gap-1.5 rounded-full border border-dashed transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
|
variant === "inline"
|
||||||
|
? "min-h-11 justify-center border-border bg-card px-4 text-base font-semibold text-foreground hover:bg-accent/50"
|
||||||
|
: "min-h-11 border-border bg-background px-3 text-xs font-semibold text-muted-foreground hover:bg-accent/50",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Ruler className={variant === "inline" ? "size-5 shrink-0" : "size-3.5 shrink-0"} aria-hidden="true" />
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
<ShotMeasurementSheet
|
<ShotMeasurementSheet
|
||||||
holeNumber={hole.holeNumber}
|
holeNumber={holeNumber}
|
||||||
existingShotCount={shotCount}
|
existingShotCount={count}
|
||||||
ownBagClubs={player.isSelf ? ownBagClubs : []}
|
ownBagClubs={ownBagClubs}
|
||||||
onSubmit={submitShot}
|
onSubmit={submitShot}
|
||||||
onCancel={() => setShotSheetOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4992,6 +5053,7 @@ function sumForPlayer(list: ApiHole[] | undefined, numbers: number[]): { strokes
|
||||||
// -- selve registreringsvinduet er bevisst IKKE rørt av denne rundens
|
// -- selve registreringsvinduet er bevisst IKKE rørt av denne rundens
|
||||||
// V0-prompt.
|
// V0-prompt.
|
||||||
function PlayerHoleCards({
|
function PlayerHoleCards({
|
||||||
|
roundId,
|
||||||
players,
|
players,
|
||||||
holesByParticipant,
|
holesByParticipant,
|
||||||
holeOrder,
|
holeOrder,
|
||||||
|
|
@ -5001,11 +5063,13 @@ function PlayerHoleCards({
|
||||||
sides,
|
sides,
|
||||||
isTwoSided,
|
isTwoSided,
|
||||||
formatResult,
|
formatResult,
|
||||||
|
ownBagClubs,
|
||||||
onSelectHole,
|
onSelectHole,
|
||||||
onPrevHole,
|
onPrevHole,
|
||||||
onNextHole,
|
onNextHole,
|
||||||
onOpenEntry,
|
onOpenEntry,
|
||||||
}: {
|
}: {
|
||||||
|
roundId: string
|
||||||
players: Player[]
|
players: Player[]
|
||||||
holesByParticipant: Record<string, ApiHole[]>
|
holesByParticipant: Record<string, ApiHole[]>
|
||||||
holeOrder: number[]
|
holeOrder: number[]
|
||||||
|
|
@ -5015,6 +5079,7 @@ function PlayerHoleCards({
|
||||||
sides: ApiSide[]
|
sides: ApiSide[]
|
||||||
isTwoSided: boolean
|
isTwoSided: boolean
|
||||||
formatResult: ApiFormatResult | null
|
formatResult: ApiFormatResult | null
|
||||||
|
ownBagClubs: string[]
|
||||||
onSelectHole: (holeNumber: number) => void
|
onSelectHole: (holeNumber: number) => void
|
||||||
onPrevHole: () => void
|
onPrevHole: () => void
|
||||||
onNextHole: () => void
|
onNextHole: () => void
|
||||||
|
|
@ -5250,6 +5315,18 @@ function PlayerHoleCards({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
{!readOnly && (
|
||||||
|
<div className="flex justify-end border-t border-border px-4 py-2">
|
||||||
|
<ShotMeasurementEntry
|
||||||
|
roundId={roundId}
|
||||||
|
holeNumber={currentHole}
|
||||||
|
owner={{ kind: "participant", id: player.id }}
|
||||||
|
ownBagClubs={player.isSelf ? ownBagClubs : []}
|
||||||
|
readOnly={readOnly}
|
||||||
|
variant="badge"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue