Compare commits

..

2 Commits

Author SHA1 Message Date
debian 2cff986760 Mocniejsze rozmycie tla
Deploy PROD / deploy (push) Successful in 24s
Domyslne `blurRadius: 10` z biblioteki jest za slabe do tego zastosowania.
Sprawdzone na zywo przy prowadzeniu z domu: przez rozmycie widac uklad pokoju,
zdjecia na polkach i przechodzace osoby, czyli dokladnie to, co ma zaslaniac.

25 daje tlo nierozpoznawalne. Obrys postaci zostaje przy tym czysty, bo maska
pochodzi z modelu segmentacji i nie zalezy od sily rozmycia: podnoszenie jej
nie psuje krawedzi. Kosztuje nieco mocy procesora, bo rozmycie liczy sie dla
kazdej klatki.

Model zostaje ten sam. `selfie_segmenter.tflite` to wariant "general" 256x256,
czyli DOKLADNIEJSZY z dwoch dostepnych; szybszego wariantu "landscape" uzywa
Google Meet. Przy nieruchomym prelegencie roznica w plynnosci jest
niezauwazalna, a jakosc obrysu ma tu wieksze znaczenie.

Wartosc w stalej z wyjasnieniem, zeby nastepna zmiana nie byla zgadywaniem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:36:09 +02:00
debian e7288c9f7e Okno potwierdzenia zwarte, ostrzezenie tylko tam gdzie cos przepada
UKLAD
Okno korzystalo z komponentu `Modal`, ktory jest zbudowany pod FORMULARZE:
naglowek oddzielony kreska, przycisk zamkniecia w rogu, przewijana tresc
i stopka oddzielona druga kreska. Przy imporcie kontaktow to sluszne, ale przy
pytaniu na dwa zdania te same elementy rozdymaly okno: dwie linie podzialu
i krzyzyk zajmowaly wiecej miejsca niz sama tresc.

Okno potwierdzenia jest krotkie z natury, wiec dostaje uklad ciagly: ikona
ostrzezenia jako okragla plakietka OBOK tytulu zamiast osobnego paska, przyciski
tuz pod trescia. Krzyzyka nie ma, bo bylby trzecim sposobem powiedzenia "nie",
obok przycisku "Anuluj", klawisza Escape i klikniecia w tlo. Naklatka, Escape
i blokada przewijania tla zostaja takie same jak w `Modal`: powtarzamy
zachowanie, nie uklad.

OSTRZEZENIE PRZESTAJE BYC WSZEDZIE
Trzy operacje mialy `destructive`, choc niczego nie niszcza:

- zatrzymanie nagrywania: plik zostaje, nic nie przepada. Nieodwracalne jest
  wylacznie to, ze nagranej chwili nie da sie powtorzyc,
- anulowanie zamowienia: zamowienie jest dokumentem finansowym i ZOSTAJE
  w dzienniku, co mowila zreszta sama tresc komunikatu,
- przywrocenie grafiki domyslnej: plik da sie wgrac z powrotem, a "przywroc
  domyslna" to cofniecie, nie zniszczenie.

Przy zatrzymaniu nagrywania tresc powtarzala przy tym ostrzezenie wlasnymi
slowami, wiec to samo bylo napisane dwa razy.

Ostrzezenie uzyte wszedzie przestaje cokolwiek znaczyc tam, gdzie strata jest
prawdziwa.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:35:52 +02:00
5 changed files with 119 additions and 45 deletions
+82 -29
View File
@@ -1,6 +1,5 @@
import { createContext, useCallback, useContext, useMemo, useRef, useState, type ReactNode } from 'react' import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
import { Icon } from './Icon' import { Icon } from './Icon'
import { Modal } from './Modal'
import { Button } from './ui' import { Button } from './ui'
/** /**
@@ -76,41 +75,95 @@ export function ConfirmProvider({ children }: { children: ReactNode }) {
<ConfirmContext.Provider value={api}> <ConfirmContext.Provider value={api}>
{children} {children}
{pending && ( {pending && <ConfirmDialog options={pending} onSettle={settle} />}
<Modal </ConfirmContext.Provider>
title={pending.title} )
onClose={() => settle(false)} }
footer={
<div className="flex justify-end gap-2"> /**
<Button type="button" variant="secondary" onClick={() => settle(false)}> * Samo okno pytania.
{pending.cancelLabel ?? 'Anuluj'} *
* NIE UZYWA `Modal`, I TO JEST SWIADOME
* ------------------------------------------------------------------------
* `Modal` jest zbudowany pod FORMULARZE: ma naglowek oddzielony kreska,
* przycisk zamkniecia w rogu, przewijana tresc i stopke oddzielona druga
* kreska. Przy imporcie kontaktow albo edycji banera to sluszne, ale przy
* pytaniu na dwa zdania te same elementy rozdymaja okno: dwie linie
* podzialu i krzyzyk zajmuja wtedy wiecej miejsca niz sama tresc.
*
* Okno potwierdzenia jest krotkie z natury, wiec dostaje uklad ciagly, bez
* kresek, a wyjscie prowadzi przez przycisk "Anuluj". Krzyzyk bylby trzecim
* sposobem powiedzenia "nie", obok przycisku, Escape i klikniecia w tlo.
*
* Naklatka, Escape i blokada przewijania tla sa te same co w `Modal`:
* powtarzamy zachowanie, a nie uklad.
*/
function ConfirmDialog({
options,
onSettle,
}: {
options: ConfirmOptions
onSettle: (value: boolean) => void
}) {
useEffect(() => {
function onKey(event: KeyboardEvent) {
if (event.key === 'Escape') onSettle(false)
}
document.addEventListener('keydown', onKey)
const previous = document.body.style.overflow
document.body.style.overflow = 'hidden'
return () => {
document.removeEventListener('keydown', onKey)
document.body.style.overflow = previous
}
}, [onSettle])
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-navy-900/40 p-4 sm:p-6"
role="dialog"
aria-modal="true"
aria-labelledby="confirm-title"
onMouseDown={(event) => {
if (event.target === event.currentTarget) onSettle(false)
}}
>
<div className="w-full rounded-lg border border-slate-100 bg-white p-5 shadow-xl sm:max-w-md sm:p-6">
<div className="flex items-start gap-3">
{options.destructive && (
<span className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-alarm-50">
<Icon name="alert-triangle" size={18} className="text-alarm-600" />
</span>
)}
<div className="min-w-0">
<h2 id="confirm-title" className="text-base font-semibold text-slate-900">
{options.title}
</h2>
{options.message && (
<div className="mt-1.5 text-sm leading-relaxed text-slate-600">
{options.message}
</div>
)}
</div>
</div>
<div className="mt-5 flex flex-wrap justify-end gap-2">
<Button type="button" variant="secondary" onClick={() => onSettle(false)}>
{options.cancelLabel ?? 'Anuluj'}
</Button> </Button>
<Button <Button
type="button" type="button"
variant={pending.destructive ? 'danger' : 'primary'} variant={options.destructive ? 'danger' : 'primary'}
onClick={() => settle(true)} onClick={() => onSettle(true)}
autoFocus
> >
{pending.confirmLabel ?? 'Potwierdź'} {options.confirmLabel ?? 'Potwierdź'}
</Button> </Button>
</div> </div>
}
>
<div className="space-y-3">
{pending.destructive && (
<div className="flex items-start gap-3 rounded-md border border-alarm-500/30 bg-alarm-50 p-3">
<Icon name="alert-triangle" size={20} className="mt-0.5 shrink-0 text-alarm-600" />
<p className="text-sm leading-relaxed text-alarm-600">
Tej operacji nie da się cofnąć.
</p>
</div> </div>
)}
{pending.message && (
<div className="text-sm leading-relaxed text-slate-600">{pending.message}</div>
)}
</div> </div>
</Modal>
)}
</ConfirmContext.Provider>
) )
} }
+2 -1
View File
@@ -320,7 +320,8 @@ function OrderDetailModal({ orderId, onClose }: { orderId: number; onClose: () =
message: message:
'Kursant nie dostanie dostępu do kursu. Zamówienie zostaje w dzienniku jako anulowane.', 'Kursant nie dostanie dostępu do kursu. Zamówienie zostaje w dzienniku jako anulowane.',
confirmLabel: 'Anuluj zamówienie', confirmLabel: 'Anuluj zamówienie',
destructive: true, // bez `destructive`: zamowienie jest dokumentem finansowym
// i zostaje w dzienniku. To zmiana stanu, nie zniszczenie.
}) })
if (ok) cancel.mutate() if (ok) cancel.mutate()
}} }}
@@ -32,6 +32,25 @@ import { Icon } from '../../components/Icon'
* przepinana w locie. Tryb `disabled` przepuszcza obraz bez zmian i nie * przepinana w locie. Tryb `disabled` przepuszcza obraz bez zmian i nie
* przerywa publikacji. * przerywa publikacji.
*/ */
/**
* Sila rozmycia tla.
*
* Domyslne 10 z biblioteki jest ZA SLABE do tego zastosowania: przy prowadzeniu
* z domu widac przez nie uklad pokoju, zdjecia na polkach i osoby przechodzace
* w tle, czyli dokladnie to, co rozmycie ma zaslonic. Sprawdzone na zywo.
*
* 25 daje tlo nierozpoznawalne, a obrys postaci zostaje czysty: maska pochodzi
* z modelu segmentacji i nie zalezy od tej wartosci, wiec podnoszenie sily nie
* psuje krawedzi. Kosztuje za to nieco mocy procesora, bo rozmycie liczy sie
* dla kazdej klatki.
*
* Model `selfie_segmenter` to wariant "general" (256x256), czyli dokladniejszy
* z dwoch dostepnych. Wariant "landscape" jest szybszy i tego uzywa Google Meet,
* ale przy nieruchomym prelegencie roznica w plynnosci jest niezauwazalna,
* a jakosc obrysu ma tu wieksze znaczenie.
*/
const BLUR_RADIUS = 25
export function BackgroundBlurToggle({ className = '' }: { className?: string }) { export function BackgroundBlurToggle({ className = '' }: { className?: string }) {
const { cameraTrack } = useLocalParticipant() const { cameraTrack } = useLocalParticipant()
const [enabled, setEnabled] = useState(false) const [enabled, setEnabled] = useState(false)
@@ -79,7 +98,7 @@ export function BackgroundBlurToggle({ className = '' }: { className?: string })
const processor = BackgroundProcessor({ const processor = BackgroundProcessor({
mode: 'background-blur', mode: 'background-blur',
blurRadius: 10, blurRadius: BLUR_RADIUS,
assetPaths: { assetPaths: {
tasksVisionFileSet: '/mediapipe/wasm', tasksVisionFileSet: '/mediapipe/wasm',
modelAssetPath: '/mediapipe/selfie_segmenter.tflite', modelAssetPath: '/mediapipe/selfie_segmenter.tflite',
@@ -152,7 +152,8 @@ export function BrandingSection() {
title: 'Przywrócić grafikę domyślną?', title: 'Przywrócić grafikę domyślną?',
message: 'Wgrany plik zostanie usunięty, a instancja wróci do wyglądu sprzed jego dodania.', message: 'Wgrany plik zostanie usunięty, a instancja wróci do wyglądu sprzed jego dodania.',
confirmLabel: 'Przywróć domyślną', confirmLabel: 'Przywróć domyślną',
destructive: true, // bez `destructive`: grafike da sie wgrac z powrotem,
// a "przywroc domyslna" to cofniecie, nie zniszczenie
}) })
if (ok) remove.mutate(kind) if (ok) remove.mutate(kind)
}} }}
+8 -8
View File
@@ -102,17 +102,17 @@ export function SpeakQueue({
if (recordingBusy) return if (recordingBusy) return
if (egressId) { if (egressId) {
/*
* Bez `destructive`: plik zostaje i nic nie przepada. Nieodwracalne jest
* wylacznie to, ze nagranej chwili nie da sie powtorzyc, a to za malo na
* czerwony alarm. Ostrzezenie uzyte wszedzie traci znaczenie tam, gdzie
* strata jest prawdziwa.
*/
const ok = await confirm({ const ok = await confirm({
title: 'Zatrzymać nagrywanie?', title: 'Zatrzymać nagrywanie?',
message: ( message:
<> 'Plik trafi do zakładki Pliki. Ponowne włączenie zacznie nowe nagranie od bieżącej chwili.',
Nagrywanie tego spotkania zostanie zakończone, a plik trafi do zakładki Pliki.{' '}
<strong>Tego fragmentu nie nagrasz ponownie</strong>: ponowne włączenie zacznie nowe
nagranie od bieżącej chwili.
</>
),
confirmLabel: 'Zatrzymaj nagrywanie', confirmLabel: 'Zatrzymaj nagrywanie',
destructive: true,
}) })
if (!ok) return if (!ok) return
} }