Compare commits
2 Commits
| 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 { 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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user