Compare commits
2 Commits
1599c7b0c1
...
2cff986760
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cff986760 | |||
| e7288c9f7e |
@@ -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 { Modal } from './Modal'
|
||||
import { Button } from './ui'
|
||||
|
||||
/**
|
||||
@@ -76,41 +75,95 @@ export function ConfirmProvider({ children }: { children: ReactNode }) {
|
||||
<ConfirmContext.Provider value={api}>
|
||||
{children}
|
||||
|
||||
{pending && (
|
||||
<Modal
|
||||
title={pending.title}
|
||||
onClose={() => settle(false)}
|
||||
footer={
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="secondary" onClick={() => settle(false)}>
|
||||
{pending.cancelLabel ?? 'Anuluj'}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant={pending.destructive ? 'danger' : 'primary'}
|
||||
onClick={() => settle(true)}
|
||||
>
|
||||
{pending.confirmLabel ?? 'Potwierdź'}
|
||||
</Button>
|
||||
</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>
|
||||
{pending && <ConfirmDialog options={pending} onSettle={settle} />}
|
||||
</ConfirmContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Samo okno pytania.
|
||||
*
|
||||
* 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>
|
||||
)}
|
||||
{pending.message && (
|
||||
<div className="text-sm leading-relaxed text-slate-600">{pending.message}</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</ConfirmContext.Provider>
|
||||
</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
|
||||
type="button"
|
||||
variant={options.destructive ? 'danger' : 'primary'}
|
||||
onClick={() => onSettle(true)}
|
||||
autoFocus
|
||||
>
|
||||
{options.confirmLabel ?? 'Potwierdź'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -320,7 +320,8 @@ function OrderDetailModal({ orderId, onClose }: { orderId: number; onClose: () =
|
||||
message:
|
||||
'Kursant nie dostanie dostępu do kursu. Zamówienie zostaje w dzienniku jako anulowane.',
|
||||
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()
|
||||
}}
|
||||
|
||||
@@ -32,6 +32,25 @@ import { Icon } from '../../components/Icon'
|
||||
* przepinana w locie. Tryb `disabled` przepuszcza obraz bez zmian i nie
|
||||
* 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 }) {
|
||||
const { cameraTrack } = useLocalParticipant()
|
||||
const [enabled, setEnabled] = useState(false)
|
||||
@@ -79,7 +98,7 @@ export function BackgroundBlurToggle({ className = '' }: { className?: string })
|
||||
|
||||
const processor = BackgroundProcessor({
|
||||
mode: 'background-blur',
|
||||
blurRadius: 10,
|
||||
blurRadius: BLUR_RADIUS,
|
||||
assetPaths: {
|
||||
tasksVisionFileSet: '/mediapipe/wasm',
|
||||
modelAssetPath: '/mediapipe/selfie_segmenter.tflite',
|
||||
|
||||
@@ -152,7 +152,8 @@ export function BrandingSection() {
|
||||
title: 'Przywrócić grafikę domyślną?',
|
||||
message: 'Wgrany plik zostanie usunięty, a instancja wróci do wyglądu sprzed jego dodania.',
|
||||
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)
|
||||
}}
|
||||
|
||||
@@ -102,17 +102,17 @@ export function SpeakQueue({
|
||||
if (recordingBusy) return
|
||||
|
||||
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({
|
||||
title: 'Zatrzymać nagrywanie?',
|
||||
message: (
|
||||
<>
|
||||
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.
|
||||
</>
|
||||
),
|
||||
message:
|
||||
'Plik trafi do zakładki Pliki. Ponowne włączenie zacznie nowe nagranie od bieżącej chwili.',
|
||||
confirmLabel: 'Zatrzymaj nagrywanie',
|
||||
destructive: true,
|
||||
})
|
||||
if (!ok) return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user