@coventia/softphone 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{call-VKq1I8yP.d.cts → call-C6YpT6uR.d.cts} +107 -1
- package/dist/{call-VKq1I8yP.d.ts → call-C6YpT6uR.d.ts} +107 -1
- package/dist/chunk-5E5OEXS2.js +14 -0
- package/dist/chunk-5E5OEXS2.js.map +1 -0
- package/dist/{chunk-LMJXX4YM.js → chunk-UHNLEBBO.js} +4 -2
- package/dist/chunk-UHNLEBBO.js.map +1 -0
- package/dist/element/index.d.ts +124 -9
- package/dist/element/index.js +1837 -413
- package/dist/element/index.js.map +1 -1
- package/dist/{engine-BlQBo2Ys.d.cts → engine-CDdaSojx.d.cts} +1 -1
- package/dist/{engine-C0BOkIcW.d.ts → engine-DPft99Wj.d.ts} +1 -1
- package/dist/index.cjs +213 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -6
- package/dist/index.d.ts +16 -6
- package/dist/index.js +75 -10
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +2 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +1 -1
- package/dist/{softphone-DlGV12Xp.d.cts → softphone-CY5d2WWY.d.cts} +28 -10
- package/dist/{softphone-Cyq3hiIM.d.ts → softphone-DXgmTyrX.d.ts} +28 -10
- package/dist/softphone.css +364 -35
- package/dist/{sources-IRKNQPWW.js → sources-ONYQW733.js} +125 -6
- package/dist/sources-ONYQW733.js.map +1 -0
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/ui/index.cjs +1450 -33
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +25 -3
- package/dist/ui/index.d.ts +25 -3
- package/dist/ui/index.js +1445 -36
- package/dist/ui/index.js.map +1 -1
- package/dist/zadarma/index.d.cts +2 -2
- package/dist/zadarma/index.d.ts +2 -2
- package/package.json +1 -1
- package/dist/chunk-LMJXX4YM.js.map +0 -1
- package/dist/sources-IRKNQPWW.js.map +0 -1
package/dist/ui/index.cjs
CHANGED
|
@@ -13128,6 +13128,14 @@ var ArrowRight = createLucideIcon("ArrowRight", [
|
|
|
13128
13128
|
["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
|
|
13129
13129
|
]);
|
|
13130
13130
|
|
|
13131
|
+
// ../../node_modules/lucide-react/dist/esm/icons/bell-ring.js
|
|
13132
|
+
var BellRing = createLucideIcon("BellRing", [
|
|
13133
|
+
["path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9", key: "1qo2s2" }],
|
|
13134
|
+
["path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0", key: "qgo35s" }],
|
|
13135
|
+
["path", { d: "M4 2C2.8 3.7 2 5.7 2 8", key: "tap9e0" }],
|
|
13136
|
+
["path", { d: "M22 8c0-2.3-.8-4.3-2-6", key: "5bb3ad" }]
|
|
13137
|
+
]);
|
|
13138
|
+
|
|
13131
13139
|
// ../../node_modules/lucide-react/dist/esm/icons/calendar-clock.js
|
|
13132
13140
|
var CalendarClock = createLucideIcon("CalendarClock", [
|
|
13133
13141
|
["path", { d: "M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5", key: "1osxxc" }],
|
|
@@ -13242,6 +13250,13 @@ var MonitorSmartphone = createLucideIcon("MonitorSmartphone", [
|
|
|
13242
13250
|
["rect", { width: "6", height: "10", x: "16", y: "12", rx: "2", key: "1egngj" }]
|
|
13243
13251
|
]);
|
|
13244
13252
|
|
|
13253
|
+
// ../../node_modules/lucide-react/dist/esm/icons/music.js
|
|
13254
|
+
var Music = createLucideIcon("Music", [
|
|
13255
|
+
["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
|
|
13256
|
+
["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
|
|
13257
|
+
["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
|
|
13258
|
+
]);
|
|
13259
|
+
|
|
13245
13260
|
// ../../node_modules/lucide-react/dist/esm/icons/pause.js
|
|
13246
13261
|
var Pause = createLucideIcon("Pause", [
|
|
13247
13262
|
["rect", { x: "14", y: "4", width: "4", height: "16", rx: "1", key: "zuxfzm" }],
|
|
@@ -13367,6 +13382,19 @@ var Users = createLucideIcon("Users", [
|
|
|
13367
13382
|
["path", { d: "M16 3.13a4 4 0 0 1 0 7.75", key: "1da9ce" }]
|
|
13368
13383
|
]);
|
|
13369
13384
|
|
|
13385
|
+
// ../../node_modules/lucide-react/dist/esm/icons/volume-2.js
|
|
13386
|
+
var Volume2 = createLucideIcon("Volume2", [
|
|
13387
|
+
[
|
|
13388
|
+
"path",
|
|
13389
|
+
{
|
|
13390
|
+
d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z",
|
|
13391
|
+
key: "uqj9uw"
|
|
13392
|
+
}
|
|
13393
|
+
],
|
|
13394
|
+
["path", { d: "M16 9a5 5 0 0 1 0 6", key: "1q6k2b" }],
|
|
13395
|
+
["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728", key: "ijwkga" }]
|
|
13396
|
+
]);
|
|
13397
|
+
|
|
13370
13398
|
// ../../node_modules/lucide-react/dist/esm/icons/x.js
|
|
13371
13399
|
var X = createLucideIcon("X", [
|
|
13372
13400
|
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
@@ -13493,7 +13521,9 @@ function useDevices() {
|
|
|
13493
13521
|
outputs: audio.outputs,
|
|
13494
13522
|
currentInputId: audio.currentInputId,
|
|
13495
13523
|
currentOutputId: audio.currentOutputId,
|
|
13524
|
+
currentRingtoneOutputId: audio.currentRingtoneOutputId,
|
|
13496
13525
|
canSelectOutput: audio.canSelectOutput,
|
|
13526
|
+
canSelectRingtoneOutput: audio.canSelectRingtoneOutput,
|
|
13497
13527
|
setInput: (deviceId) => audio.setInput(deviceId),
|
|
13498
13528
|
setOutput: (deviceId) => audio.setOutput(deviceId),
|
|
13499
13529
|
setRingtoneOutput: (deviceId) => audio.setRingtoneOutput(deviceId),
|
|
@@ -13510,6 +13540,36 @@ var PACKED = "AC247 AD376 AE971 AF93 AG1 AI1 AL355 AM374 AO244 AR54 AS1 AT43 AU6
|
|
|
13510
13540
|
var DIAL_CODES = new Map(
|
|
13511
13541
|
PACKED.split(" ").map((token) => [token.slice(0, 2), Number(token.slice(2))])
|
|
13512
13542
|
);
|
|
13543
|
+
var REGIONS_BY_CODE = (() => {
|
|
13544
|
+
const map = /* @__PURE__ */ new Map();
|
|
13545
|
+
for (const [region, code] of DIAL_CODES) map.set(code, [...map.get(code) ?? [], region]);
|
|
13546
|
+
return map;
|
|
13547
|
+
})();
|
|
13548
|
+
var MAIN_REGION = {
|
|
13549
|
+
1: "US",
|
|
13550
|
+
7: "RU",
|
|
13551
|
+
39: "IT",
|
|
13552
|
+
44: "GB",
|
|
13553
|
+
47: "NO",
|
|
13554
|
+
61: "AU",
|
|
13555
|
+
212: "MA",
|
|
13556
|
+
262: "RE",
|
|
13557
|
+
290: "SH",
|
|
13558
|
+
358: "FI",
|
|
13559
|
+
590: "GP",
|
|
13560
|
+
599: "CW"
|
|
13561
|
+
};
|
|
13562
|
+
function regionOfInternational(typed) {
|
|
13563
|
+
const text = String(typed ?? "").trim();
|
|
13564
|
+
if (!text.startsWith("+")) return null;
|
|
13565
|
+
const digits = text.slice(1).replace(/\D/g, "");
|
|
13566
|
+
for (let length = 1; length <= Math.min(3, digits.length); length++) {
|
|
13567
|
+
const code = Number(digits.slice(0, length));
|
|
13568
|
+
const regions = REGIONS_BY_CODE.get(code);
|
|
13569
|
+
if (regions) return MAIN_REGION[code] ?? regions[0] ?? null;
|
|
13570
|
+
}
|
|
13571
|
+
return null;
|
|
13572
|
+
}
|
|
13513
13573
|
var DEFAULT_COUNTRY = "ES";
|
|
13514
13574
|
var REGION_NAMES = (() => {
|
|
13515
13575
|
try {
|
|
@@ -13561,12 +13621,16 @@ function CountryMark({ code }) {
|
|
|
13561
13621
|
}
|
|
13562
13622
|
function CountryPicker({
|
|
13563
13623
|
value,
|
|
13564
|
-
onChange
|
|
13624
|
+
onChange,
|
|
13625
|
+
fromNumber = false
|
|
13565
13626
|
}) {
|
|
13566
13627
|
const [open, setOpen] = React.useState(false);
|
|
13567
13628
|
const [query, setQuery] = React.useState("");
|
|
13568
13629
|
const rootRef = React.useRef(null);
|
|
13569
13630
|
const inputRef = React.useRef(null);
|
|
13631
|
+
React.useEffect(() => {
|
|
13632
|
+
if (fromNumber) setOpen(false);
|
|
13633
|
+
}, [fromNumber]);
|
|
13570
13634
|
const results = React.useMemo(() => {
|
|
13571
13635
|
const q = query.trim().toLowerCase().replace(/^\+/, "");
|
|
13572
13636
|
if (!q) return COUNTRIES;
|
|
@@ -13595,6 +13659,12 @@ function CountryPicker({
|
|
|
13595
13659
|
else setQuery("");
|
|
13596
13660
|
}, [open]);
|
|
13597
13661
|
const dial = DIAL_CODES.get(value);
|
|
13662
|
+
if (fromNumber) {
|
|
13663
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-cp", ref: rootRef, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-cp-chip", "data-from-number": true, title: "El prefijo viene del n\xFAmero marcado", part: "country-chip", children: [
|
|
13664
|
+
/* @__PURE__ */ jsxRuntime.jsx(CountryMark, { code: value || "+" }),
|
|
13665
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-sr-only", children: dial ? `Prefijo del n\xFAmero marcado: ${countryName(value)} (+${dial})` : "Prefijo del n\xFAmero marcado" })
|
|
13666
|
+
] }) });
|
|
13667
|
+
}
|
|
13598
13668
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-cp", ref: rootRef, children: [
|
|
13599
13669
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
13600
13670
|
"button",
|
|
@@ -13738,6 +13808,59 @@ var KEYPAD_KEYS = [
|
|
|
13738
13808
|
["#", ""]
|
|
13739
13809
|
];
|
|
13740
13810
|
|
|
13811
|
+
// src/ui/audio/classic-ring.ts
|
|
13812
|
+
var C6 = 1046.5;
|
|
13813
|
+
var E6 = 1318.51;
|
|
13814
|
+
var HARMONICITY = 4;
|
|
13815
|
+
var INDEX = 1.8;
|
|
13816
|
+
var CLASSIC_CYCLE_SECONDS = 3;
|
|
13817
|
+
var CLASSIC_GAIN = 0.3841;
|
|
13818
|
+
function classicRingGraph(ctx, dest) {
|
|
13819
|
+
const output = ctx.createGain();
|
|
13820
|
+
output.gain.value = CLASSIC_GAIN;
|
|
13821
|
+
const chain = ["highpass", "highpass", "lowpass", "lowpass"].map((type) => {
|
|
13822
|
+
const f = ctx.createBiquadFilter();
|
|
13823
|
+
f.type = type;
|
|
13824
|
+
f.frequency.value = type === "highpass" ? 250 : 7500;
|
|
13825
|
+
f.Q.value = 0.707;
|
|
13826
|
+
return f;
|
|
13827
|
+
});
|
|
13828
|
+
for (let i = 0; i < chain.length - 1; i++) chain[i].connect(chain[i + 1]);
|
|
13829
|
+
chain[chain.length - 1].connect(output);
|
|
13830
|
+
output.connect(dest);
|
|
13831
|
+
return { input: chain[0], output };
|
|
13832
|
+
}
|
|
13833
|
+
function strike(ctx, input, t, freq, vel, track) {
|
|
13834
|
+
const carrier = ctx.createOscillator();
|
|
13835
|
+
carrier.frequency.value = freq;
|
|
13836
|
+
const modulator = ctx.createOscillator();
|
|
13837
|
+
modulator.frequency.value = freq * HARMONICITY;
|
|
13838
|
+
const depth = ctx.createGain();
|
|
13839
|
+
depth.gain.setValueAtTime(0, t);
|
|
13840
|
+
depth.gain.linearRampToValueAtTime(INDEX * freq, t + 4e-3);
|
|
13841
|
+
depth.gain.setTargetAtTime(0, t + 4e-3, 0.02);
|
|
13842
|
+
modulator.connect(depth);
|
|
13843
|
+
depth.connect(carrier.frequency);
|
|
13844
|
+
const amp = ctx.createGain();
|
|
13845
|
+
amp.gain.setValueAtTime(0, t);
|
|
13846
|
+
amp.gain.linearRampToValueAtTime(vel, t + 5e-3);
|
|
13847
|
+
amp.gain.setTargetAtTime(0, t + 5e-3, 0.15);
|
|
13848
|
+
amp.gain.setTargetAtTime(0, t + 0.045, 0.08);
|
|
13849
|
+
carrier.connect(amp);
|
|
13850
|
+
amp.connect(input);
|
|
13851
|
+
for (const o of [carrier, modulator]) {
|
|
13852
|
+
o.start(t);
|
|
13853
|
+
o.stop(t + 0.7);
|
|
13854
|
+
track(o);
|
|
13855
|
+
}
|
|
13856
|
+
}
|
|
13857
|
+
function scheduleClassicCycle(ctx, input, t0, track) {
|
|
13858
|
+
for (const ring of [0, 0.6]) {
|
|
13859
|
+
for (let i = 0; i < 8; i++) strike(ctx, input, t0 + ring + i * 0.05, i % 2 ? E6 : C6, 0.55 + 0.1 * (i % 2), track);
|
|
13860
|
+
}
|
|
13861
|
+
return CLASSIC_CYCLE_SECONDS;
|
|
13862
|
+
}
|
|
13863
|
+
|
|
13741
13864
|
// src/ui/audio/audio.ts
|
|
13742
13865
|
var TONE_HZ = 425;
|
|
13743
13866
|
function createRemoteAudio() {
|
|
@@ -13780,30 +13903,159 @@ function primeAudio() {
|
|
|
13780
13903
|
if (ctx && ctx.state === "suspended") void ctx.resume?.().catch(() => {
|
|
13781
13904
|
});
|
|
13782
13905
|
}
|
|
13783
|
-
function playRingtone() {
|
|
13906
|
+
function playRingtone(options = {}) {
|
|
13784
13907
|
const ctx = sharedContext();
|
|
13785
13908
|
if (!ctx) return { stop() {
|
|
13786
13909
|
} };
|
|
13910
|
+
const bus = ctx.createGain();
|
|
13911
|
+
const route = routeToDevice(ctx, bus, options.sinkId ?? null);
|
|
13912
|
+
const voice = options.buffer ? fileVoice(ctx, bus, options.buffer, options.loop ?? null) : synthesisedVoice(ctx, bus);
|
|
13913
|
+
return {
|
|
13914
|
+
stop() {
|
|
13915
|
+
voice.stop();
|
|
13916
|
+
route.stop();
|
|
13917
|
+
}
|
|
13918
|
+
};
|
|
13919
|
+
}
|
|
13920
|
+
function fileVoice(ctx, bus, buffer, loop) {
|
|
13921
|
+
const FILE_LEVEL = 1;
|
|
13922
|
+
const RAMP = 0.02;
|
|
13923
|
+
const LEAD = 0.08;
|
|
13787
13924
|
const gain = ctx.createGain();
|
|
13788
13925
|
gain.gain.value = 0;
|
|
13789
|
-
gain.connect(
|
|
13790
|
-
const
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13795
|
-
|
|
13796
|
-
|
|
13797
|
-
|
|
13926
|
+
gain.connect(bus);
|
|
13927
|
+
const src = ctx.createBufferSource();
|
|
13928
|
+
src.buffer = buffer;
|
|
13929
|
+
src.loop = true;
|
|
13930
|
+
const points = loop && loop.start >= 0 && loop.end > loop.start && loop.end <= buffer.duration + 1e-3 ? loop : null;
|
|
13931
|
+
if (points) {
|
|
13932
|
+
src.loopStart = points.start;
|
|
13933
|
+
src.loopEnd = points.end;
|
|
13934
|
+
}
|
|
13935
|
+
src.connect(gain);
|
|
13936
|
+
let stopped = false;
|
|
13937
|
+
void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
13938
|
+
}).finally(() => {
|
|
13939
|
+
if (stopped) return;
|
|
13940
|
+
const at = ctx.currentTime + LEAD;
|
|
13798
13941
|
gain.gain.setValueAtTime(0, at);
|
|
13799
|
-
gain.gain.linearRampToValueAtTime(
|
|
13800
|
-
|
|
13801
|
-
|
|
13942
|
+
gain.gain.linearRampToValueAtTime(FILE_LEVEL, at + RAMP);
|
|
13943
|
+
src.start(at, points?.start ?? 0);
|
|
13944
|
+
});
|
|
13945
|
+
return {
|
|
13946
|
+
stop() {
|
|
13947
|
+
stopped = true;
|
|
13948
|
+
try {
|
|
13949
|
+
src.stop();
|
|
13950
|
+
} catch {
|
|
13951
|
+
}
|
|
13952
|
+
try {
|
|
13953
|
+
gain.disconnect();
|
|
13954
|
+
} catch {
|
|
13955
|
+
}
|
|
13956
|
+
}
|
|
13802
13957
|
};
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13958
|
+
}
|
|
13959
|
+
function routeToDevice(ctx, bus, sinkId) {
|
|
13960
|
+
const toDefault = () => {
|
|
13961
|
+
bus.connect(ctx.destination);
|
|
13962
|
+
};
|
|
13963
|
+
const el = sinkId && typeof document !== "undefined" ? document.createElement("audio") : null;
|
|
13964
|
+
const dest = el && typeof ctx.createMediaStreamDestination === "function" ? ctx.createMediaStreamDestination() : null;
|
|
13965
|
+
const canRoute = el && dest && typeof el.setSinkId === "function";
|
|
13966
|
+
if (!canRoute || !sinkId) {
|
|
13967
|
+
toDefault();
|
|
13968
|
+
return { stop: () => safeDisconnect(bus) };
|
|
13969
|
+
}
|
|
13970
|
+
let stopped = false;
|
|
13971
|
+
el.style.display = "none";
|
|
13972
|
+
el.srcObject = dest.stream;
|
|
13973
|
+
document.body.appendChild(el);
|
|
13974
|
+
bus.connect(dest);
|
|
13975
|
+
void el.setSinkId(sinkId).then(() => el.play()).catch((error) => {
|
|
13976
|
+
if (stopped) return;
|
|
13977
|
+
console.warn("[softphone] sound could not be sent to the chosen device; using the default output", error);
|
|
13978
|
+
safeDisconnect(bus);
|
|
13979
|
+
teardown(el);
|
|
13980
|
+
toDefault();
|
|
13981
|
+
});
|
|
13982
|
+
return {
|
|
13983
|
+
stop() {
|
|
13984
|
+
stopped = true;
|
|
13985
|
+
safeDisconnect(bus);
|
|
13986
|
+
teardown(el);
|
|
13987
|
+
}
|
|
13988
|
+
};
|
|
13989
|
+
}
|
|
13990
|
+
function safeDisconnect(node) {
|
|
13991
|
+
try {
|
|
13992
|
+
node.disconnect();
|
|
13993
|
+
} catch {
|
|
13994
|
+
}
|
|
13995
|
+
}
|
|
13996
|
+
function teardown(el) {
|
|
13997
|
+
el.pause();
|
|
13998
|
+
el.srcObject = null;
|
|
13999
|
+
el.remove();
|
|
14000
|
+
}
|
|
14001
|
+
var TEST_TONE_MS = 900;
|
|
14002
|
+
function playTestTone(sinkId) {
|
|
14003
|
+
const ctx = sharedContext();
|
|
14004
|
+
if (!ctx) return { stop() {
|
|
14005
|
+
} };
|
|
14006
|
+
const bus = ctx.createGain();
|
|
14007
|
+
const route = routeToDevice(ctx, bus, sinkId);
|
|
14008
|
+
const LEVEL = 0.5;
|
|
14009
|
+
const NOTES = [
|
|
14010
|
+
{ hz: 660, at: 0.05 },
|
|
14011
|
+
{ hz: 880, at: 0.4 }
|
|
14012
|
+
];
|
|
14013
|
+
const oscs = [];
|
|
14014
|
+
let stopped = false;
|
|
14015
|
+
let timer = null;
|
|
14016
|
+
const stop = () => {
|
|
14017
|
+
if (stopped) return;
|
|
14018
|
+
stopped = true;
|
|
14019
|
+
if (timer) clearTimeout(timer);
|
|
14020
|
+
for (const o of oscs) {
|
|
14021
|
+
try {
|
|
14022
|
+
o.stop();
|
|
14023
|
+
} catch {
|
|
14024
|
+
}
|
|
14025
|
+
}
|
|
14026
|
+
route.stop();
|
|
14027
|
+
};
|
|
14028
|
+
void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
14029
|
+
}).finally(() => {
|
|
14030
|
+
if (stopped) return;
|
|
14031
|
+
const t0 = ctx.currentTime;
|
|
14032
|
+
for (const { hz, at } of NOTES) {
|
|
14033
|
+
const gain = ctx.createGain();
|
|
14034
|
+
gain.gain.value = 0;
|
|
14035
|
+
gain.connect(bus);
|
|
14036
|
+
const osc = ctx.createOscillator();
|
|
14037
|
+
osc.type = "sine";
|
|
14038
|
+
osc.frequency.value = hz;
|
|
14039
|
+
osc.connect(gain);
|
|
14040
|
+
const start = t0 + at;
|
|
14041
|
+
gain.gain.setValueAtTime(0, start);
|
|
14042
|
+
gain.gain.linearRampToValueAtTime(LEVEL, start + 0.015);
|
|
14043
|
+
gain.gain.setValueAtTime(LEVEL, start + 0.25);
|
|
14044
|
+
gain.gain.linearRampToValueAtTime(0, start + 0.32);
|
|
14045
|
+
osc.start(start);
|
|
14046
|
+
osc.stop(start + 0.34);
|
|
14047
|
+
oscs.push(osc);
|
|
14048
|
+
}
|
|
14049
|
+
timer = setTimeout(stop, TEST_TONE_MS);
|
|
14050
|
+
});
|
|
14051
|
+
return { stop };
|
|
14052
|
+
}
|
|
14053
|
+
function synthesisedVoice(ctx, bus) {
|
|
14054
|
+
const { input, output } = classicRingGraph(ctx, bus);
|
|
14055
|
+
const scheduled = /* @__PURE__ */ new Set();
|
|
14056
|
+
const track = (o) => {
|
|
14057
|
+
scheduled.add(o);
|
|
14058
|
+
o.onended = () => scheduled.delete(o);
|
|
13807
14059
|
};
|
|
13808
14060
|
const LOOKAHEAD = 0.5;
|
|
13809
14061
|
const TICK_MS = 150;
|
|
@@ -13812,7 +14064,7 @@ function playRingtone() {
|
|
|
13812
14064
|
let timer = null;
|
|
13813
14065
|
let nextAt = 0;
|
|
13814
14066
|
const pump = () => {
|
|
13815
|
-
while (nextAt < ctx.currentTime + LOOKAHEAD) nextAt +=
|
|
14067
|
+
while (nextAt < ctx.currentTime + LOOKAHEAD) nextAt += scheduleClassicCycle(ctx, input, nextAt, track);
|
|
13816
14068
|
};
|
|
13817
14069
|
void Promise.resolve(ctx.resume?.()).catch(() => {
|
|
13818
14070
|
}).finally(() => {
|
|
@@ -13825,12 +14077,15 @@ function playRingtone() {
|
|
|
13825
14077
|
stop() {
|
|
13826
14078
|
stopped = true;
|
|
13827
14079
|
if (timer) clearInterval(timer);
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
14080
|
+
for (const o of scheduled) {
|
|
14081
|
+
try {
|
|
14082
|
+
o.stop();
|
|
14083
|
+
} catch {
|
|
14084
|
+
}
|
|
13831
14085
|
}
|
|
14086
|
+
scheduled.clear();
|
|
13832
14087
|
try {
|
|
13833
|
-
|
|
14088
|
+
output.disconnect();
|
|
13834
14089
|
} catch {
|
|
13835
14090
|
}
|
|
13836
14091
|
}
|
|
@@ -13976,18 +14231,384 @@ function playRingback() {
|
|
|
13976
14231
|
}
|
|
13977
14232
|
};
|
|
13978
14233
|
}
|
|
14234
|
+
|
|
14235
|
+
// src/core/outbound-numbers.ts
|
|
14236
|
+
function isOutboundNumberRefused(value) {
|
|
14237
|
+
const refusal = value?.refusal;
|
|
14238
|
+
return value instanceof Error && !!refusal && (refusal.reason === "platform" || refusal.reason === "user") && typeof refusal.retryAfterSeconds === "number";
|
|
14239
|
+
}
|
|
14240
|
+
var stores = /* @__PURE__ */ new WeakMap();
|
|
14241
|
+
function outboundNumberStore(softphone) {
|
|
14242
|
+
const source = softphone.sources?.outboundNumbers;
|
|
14243
|
+
if (!source) return null;
|
|
14244
|
+
let store = stores.get(softphone);
|
|
14245
|
+
if (!store) {
|
|
14246
|
+
store = createOutboundNumberStore(source);
|
|
14247
|
+
stores.set(softphone, store);
|
|
14248
|
+
}
|
|
14249
|
+
return store;
|
|
14250
|
+
}
|
|
14251
|
+
function createOutboundNumberStore(source) {
|
|
14252
|
+
let settings = null;
|
|
14253
|
+
let inFlight = null;
|
|
14254
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
14255
|
+
const apply = (next) => {
|
|
14256
|
+
settings = next;
|
|
14257
|
+
for (const l of listeners) l();
|
|
14258
|
+
};
|
|
14259
|
+
return {
|
|
14260
|
+
getSettings: () => settings,
|
|
14261
|
+
refresh() {
|
|
14262
|
+
inFlight ??= (async () => {
|
|
14263
|
+
if (!settings && source.cached) {
|
|
14264
|
+
const early = await source.cached().catch(() => null);
|
|
14265
|
+
if (early && !settings) apply(early);
|
|
14266
|
+
}
|
|
14267
|
+
try {
|
|
14268
|
+
apply(await source.get());
|
|
14269
|
+
} catch (error) {
|
|
14270
|
+
console.warn("[softphone] outbound numbers could not be loaded; keeping the previous ones", error);
|
|
14271
|
+
} finally {
|
|
14272
|
+
inFlight = null;
|
|
14273
|
+
}
|
|
14274
|
+
})();
|
|
14275
|
+
return inFlight;
|
|
14276
|
+
},
|
|
14277
|
+
async setMine(id) {
|
|
14278
|
+
apply(await source.setMine(id));
|
|
14279
|
+
},
|
|
14280
|
+
subscribe(listener) {
|
|
14281
|
+
listeners.add(listener);
|
|
14282
|
+
return () => listeners.delete(listener);
|
|
14283
|
+
}
|
|
14284
|
+
};
|
|
14285
|
+
}
|
|
14286
|
+
var noSubscribe = () => () => {
|
|
14287
|
+
};
|
|
14288
|
+
var noSettings = () => null;
|
|
14289
|
+
function useOutboundNumbers(store) {
|
|
14290
|
+
const settings = React.useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
|
|
14291
|
+
const [failed, setFailed] = React.useState(false);
|
|
14292
|
+
const load = React.useCallback(() => {
|
|
14293
|
+
if (!store) return () => {
|
|
14294
|
+
};
|
|
14295
|
+
let live = true;
|
|
14296
|
+
setFailed(false);
|
|
14297
|
+
void store.refresh().then(() => {
|
|
14298
|
+
if (live) setFailed(store.getSettings() === null);
|
|
14299
|
+
});
|
|
14300
|
+
return () => {
|
|
14301
|
+
live = false;
|
|
14302
|
+
};
|
|
14303
|
+
}, [store]);
|
|
14304
|
+
React.useEffect(load, [load]);
|
|
14305
|
+
return { settings, failed, retry: load };
|
|
14306
|
+
}
|
|
14307
|
+
function useKnownOutboundNumbers(store) {
|
|
14308
|
+
return React.useSyncExternalStore(store?.subscribe ?? noSubscribe, store?.getSettings ?? noSettings);
|
|
14309
|
+
}
|
|
14310
|
+
function useCountdown(until) {
|
|
14311
|
+
const [, setTick] = React.useState(0);
|
|
14312
|
+
React.useEffect(() => {
|
|
14313
|
+
if (!until) return void 0;
|
|
14314
|
+
const timer = setInterval(() => {
|
|
14315
|
+
setTick((n) => n + 1);
|
|
14316
|
+
if (secondsLeft(until) === 0) clearInterval(timer);
|
|
14317
|
+
}, 1e3);
|
|
14318
|
+
return () => clearInterval(timer);
|
|
14319
|
+
}, [until]);
|
|
14320
|
+
return secondsLeft(until);
|
|
14321
|
+
}
|
|
14322
|
+
function secondsLeft(until) {
|
|
14323
|
+
return until ? Math.max(0, Math.ceil((until - Date.now()) / 1e3)) : 0;
|
|
14324
|
+
}
|
|
14325
|
+
function numberName(n, format = formatIntl) {
|
|
14326
|
+
if (!n) return "N\xFAmero de la organizaci\xF3n";
|
|
14327
|
+
return n.label || format(`+${n.number}`);
|
|
14328
|
+
}
|
|
14329
|
+
function effectiveNumber(settings) {
|
|
14330
|
+
return settings?.numbers.find((n) => n.id === settings.effective);
|
|
14331
|
+
}
|
|
14332
|
+
|
|
14333
|
+
// src/ui/outbound/outbound-change.ts
|
|
14334
|
+
var KIND_TAG = {
|
|
14335
|
+
mobile: { tag: "M\xF3vil", hint: "No usar para atenci\xF3n al cliente ni para llamadas comerciales no solicitadas." },
|
|
14336
|
+
commercial: { tag: "Comercial (400)", hint: "Solo para llamadas comerciales. No recibe llamadas." }
|
|
14337
|
+
};
|
|
14338
|
+
function noticeText(situation, seconds, current) {
|
|
14339
|
+
switch (situation) {
|
|
14340
|
+
case "platform":
|
|
14341
|
+
return `El cambio de n\xFAmero de salida no est\xE1 disponible temporalmente. Vuelve a intentarlo en ${seconds} s. Hasta entonces, tus llamadas seguir\xE1n saliendo desde ${current}.`;
|
|
14342
|
+
case "user":
|
|
14343
|
+
return `Acabas de cambiar el n\xFAmero de salida. Podr\xE1s cambiarlo de nuevo en ${seconds} s. Hasta entonces, tus llamadas saldr\xE1n desde ${current}.`;
|
|
14344
|
+
case "wait":
|
|
14345
|
+
return `Podr\xE1s cambiar el n\xFAmero de salida en ${seconds} s.`;
|
|
14346
|
+
case "pending":
|
|
14347
|
+
return seconds > 0 ? `Tu n\xFAmero de salida todav\xEDa no se ha aplicado. Podr\xE1s volver a elegirlo en ${seconds} s.` : "Tu n\xFAmero de salida todav\xEDa no se ha aplicado. Vuelve a elegirlo para aplicarlo.";
|
|
14348
|
+
}
|
|
14349
|
+
}
|
|
14350
|
+
function useOutboundNumberChange(store, settings, current) {
|
|
14351
|
+
const [busy, setBusy] = React.useState(false);
|
|
14352
|
+
const [notice, setNotice] = React.useState(null);
|
|
14353
|
+
const [waitUntil, setWaitUntil] = React.useState(null);
|
|
14354
|
+
React.useEffect(() => {
|
|
14355
|
+
const s = settings?.changeAvailableInSeconds ?? 0;
|
|
14356
|
+
setWaitUntil(s > 0 ? Date.now() + s * 1e3 : null);
|
|
14357
|
+
}, [settings]);
|
|
14358
|
+
const refusedUntil = notice?.kind === "refused" ? notice.until : null;
|
|
14359
|
+
const blockedFor = useCountdown(refusedUntil ?? waitUntil);
|
|
14360
|
+
React.useEffect(() => {
|
|
14361
|
+
if (notice?.kind === "refused" && blockedFor === 0) setNotice(null);
|
|
14362
|
+
}, [notice, blockedFor]);
|
|
14363
|
+
const locked = busy || blockedFor > 0;
|
|
14364
|
+
const choose = async (id) => {
|
|
14365
|
+
if (!store || !settings || locked) return false;
|
|
14366
|
+
if (id === settings.mine) return true;
|
|
14367
|
+
setBusy(true);
|
|
14368
|
+
setNotice(null);
|
|
14369
|
+
try {
|
|
14370
|
+
await store.setMine(id);
|
|
14371
|
+
return true;
|
|
14372
|
+
} catch (error) {
|
|
14373
|
+
setNotice(
|
|
14374
|
+
isOutboundNumberRefused(error) ? { kind: "refused", reason: error.refusal.reason, until: Date.now() + error.refusal.retryAfterSeconds * 1e3 } : { kind: "error", text: errorText(error) }
|
|
14375
|
+
);
|
|
14376
|
+
return false;
|
|
14377
|
+
} finally {
|
|
14378
|
+
setBusy(false);
|
|
14379
|
+
}
|
|
14380
|
+
};
|
|
14381
|
+
return {
|
|
14382
|
+
busy,
|
|
14383
|
+
locked,
|
|
14384
|
+
choose,
|
|
14385
|
+
/** What to say about the choice right now, if anything. */
|
|
14386
|
+
message: settings ? currentMessage(notice, settings.state, blockedFor, current) : null,
|
|
14387
|
+
/** The message is an error (the change failed), not information (wait, pending). */
|
|
14388
|
+
isError: notice?.kind === "error"
|
|
14389
|
+
};
|
|
14390
|
+
}
|
|
14391
|
+
function currentMessage(notice, state, blockedFor, current) {
|
|
14392
|
+
if (notice?.kind === "refused") return blockedFor > 0 ? noticeText(notice.reason, blockedFor, current) : null;
|
|
14393
|
+
if (notice?.kind === "error") return notice.text;
|
|
14394
|
+
if (state === "pending" || state === "failed") return noticeText("pending", blockedFor, current);
|
|
14395
|
+
return blockedFor > 0 ? noticeText("wait", blockedFor, current) : null;
|
|
14396
|
+
}
|
|
14397
|
+
function errorText(error) {
|
|
14398
|
+
const message = error instanceof Error ? error.message : "";
|
|
14399
|
+
return message && !/HTTP \d+/.test(message) ? message : "No se ha podido cambiar el n\xFAmero de salida. Int\xE9ntalo de nuevo.";
|
|
14400
|
+
}
|
|
14401
|
+
var CALLOUT_MS = 6e3;
|
|
14402
|
+
function OutboundNumberPicker({ onBusyChange }) {
|
|
14403
|
+
const softphone = useSoftphone();
|
|
14404
|
+
const store = outboundNumberStore(softphone);
|
|
14405
|
+
const { settings } = useOutboundNumbers(store);
|
|
14406
|
+
const formatIntl2 = useIntlFormatter();
|
|
14407
|
+
const current = settings ? numberName(effectiveNumber(settings), formatIntl2) : "";
|
|
14408
|
+
const { busy, locked, choose: apply, message, isError } = useOutboundNumberChange(store, settings, current);
|
|
14409
|
+
const [open, setOpen] = React.useState(false);
|
|
14410
|
+
const [applying, setApplying] = React.useState(void 0);
|
|
14411
|
+
const [callout, setCallout] = React.useState(false);
|
|
14412
|
+
const rootRef = React.useRef(null);
|
|
14413
|
+
const chipRef = React.useRef(null);
|
|
14414
|
+
const keys = useListKeyboard(open);
|
|
14415
|
+
React.useEffect(() => onBusyChange?.(busy), [busy, onBusyChange]);
|
|
14416
|
+
React.useEffect(() => {
|
|
14417
|
+
if (!message) setCallout(false);
|
|
14418
|
+
}, [message]);
|
|
14419
|
+
React.useEffect(() => {
|
|
14420
|
+
if (!callout) return void 0;
|
|
14421
|
+
const timer = setTimeout(() => setCallout(false), CALLOUT_MS);
|
|
14422
|
+
return () => clearTimeout(timer);
|
|
14423
|
+
}, [callout]);
|
|
14424
|
+
const floating = open || callout;
|
|
14425
|
+
React.useEffect(() => {
|
|
14426
|
+
if (!floating) return;
|
|
14427
|
+
const close = () => {
|
|
14428
|
+
setOpen(false);
|
|
14429
|
+
setCallout(false);
|
|
14430
|
+
};
|
|
14431
|
+
const root = rootRef.current?.getRootNode() ?? document;
|
|
14432
|
+
const onDown = (e) => {
|
|
14433
|
+
if (!rootRef.current?.contains(e.target)) close();
|
|
14434
|
+
};
|
|
14435
|
+
const onKey = (e) => {
|
|
14436
|
+
if (e.key !== "Escape") return;
|
|
14437
|
+
if (rootRef.current?.contains(e.target)) chipRef.current?.focus();
|
|
14438
|
+
close();
|
|
14439
|
+
};
|
|
14440
|
+
root.addEventListener("pointerdown", onDown);
|
|
14441
|
+
document.addEventListener("keydown", onKey);
|
|
14442
|
+
return () => {
|
|
14443
|
+
root.removeEventListener("pointerdown", onDown);
|
|
14444
|
+
document.removeEventListener("keydown", onKey);
|
|
14445
|
+
};
|
|
14446
|
+
}, [floating]);
|
|
14447
|
+
if (!store || !settings || settings.numbers.length < 2) return null;
|
|
14448
|
+
const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
|
|
14449
|
+
const applyingNumber = applying === null ? orgDefault : settings.numbers.find((n) => n.id === applying);
|
|
14450
|
+
const shown = applying === void 0 ? current : numberName(applyingNumber, formatIntl2);
|
|
14451
|
+
const choose = async (id) => {
|
|
14452
|
+
if (locked) return;
|
|
14453
|
+
setOpen(false);
|
|
14454
|
+
chipRef.current?.focus();
|
|
14455
|
+
if (id === settings.mine) return;
|
|
14456
|
+
setCallout(false);
|
|
14457
|
+
setApplying(id);
|
|
14458
|
+
const applied = await apply(id);
|
|
14459
|
+
setApplying(void 0);
|
|
14460
|
+
if (!applied) setCallout(true);
|
|
14461
|
+
};
|
|
14462
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-on", ref: rootRef, children: [
|
|
14463
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
14464
|
+
"button",
|
|
14465
|
+
{
|
|
14466
|
+
ref: chipRef,
|
|
14467
|
+
type: "button",
|
|
14468
|
+
className: "cv-on-chip",
|
|
14469
|
+
onClick: () => {
|
|
14470
|
+
setCallout(false);
|
|
14471
|
+
setOpen((o) => !o);
|
|
14472
|
+
},
|
|
14473
|
+
"aria-haspopup": "listbox",
|
|
14474
|
+
"aria-expanded": open,
|
|
14475
|
+
"aria-label": `Llamar desde: ${shown}${busy ? " (aplicando)" : ""}. Cambiar n\xFAmero de salida`,
|
|
14476
|
+
"aria-busy": busy,
|
|
14477
|
+
part: "outbound-number-chip",
|
|
14478
|
+
children: [
|
|
14479
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-label", children: "Llamar desde:" }),
|
|
14480
|
+
busy ? /* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { size: 13, className: "cv-on-icon cv-on-spin", "aria-hidden": true }) : null,
|
|
14481
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-current", children: shown }),
|
|
14482
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { size: 13, className: "cv-cp-caret", "data-open": open, "aria-hidden": true })
|
|
14483
|
+
]
|
|
14484
|
+
}
|
|
14485
|
+
),
|
|
14486
|
+
open ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14487
|
+
"div",
|
|
14488
|
+
{
|
|
14489
|
+
className: "cv-cp-pop cv-on-pop",
|
|
14490
|
+
role: "dialog",
|
|
14491
|
+
"aria-label": "Llamar desde",
|
|
14492
|
+
part: "outbound-number-list",
|
|
14493
|
+
onBlur: (e) => {
|
|
14494
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setOpen(false);
|
|
14495
|
+
},
|
|
14496
|
+
children: [
|
|
14497
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-title", children: "Llamar desde" }),
|
|
14498
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-cp-list", role: "listbox", "aria-disabled": locked, ref: keys.listRef, onKeyDown: keys.onKeyDown, children: [
|
|
14499
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14500
|
+
OptionRow,
|
|
14501
|
+
{
|
|
14502
|
+
selected: settings.mine === null,
|
|
14503
|
+
disabled: locked,
|
|
14504
|
+
title: "Por defecto de la organizaci\xF3n",
|
|
14505
|
+
sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
|
|
14506
|
+
onSelect: () => void choose(null)
|
|
14507
|
+
}
|
|
14508
|
+
),
|
|
14509
|
+
settings.numbers.map((n) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
14510
|
+
OptionRow,
|
|
14511
|
+
{
|
|
14512
|
+
selected: settings.mine === n.id,
|
|
14513
|
+
disabled: locked,
|
|
14514
|
+
title: numberName(n, formatIntl2),
|
|
14515
|
+
sub: n.label ? formatIntl2(`+${n.number}`) : null,
|
|
14516
|
+
tag: KIND_TAG[n.kind],
|
|
14517
|
+
onSelect: () => void choose(n.id)
|
|
14518
|
+
},
|
|
14519
|
+
n.id
|
|
14520
|
+
))
|
|
14521
|
+
] }),
|
|
14522
|
+
message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-notice", role: "status", "data-kind": isError ? "error" : "info", children: message }) : null
|
|
14523
|
+
]
|
|
14524
|
+
}
|
|
14525
|
+
) : null,
|
|
14526
|
+
callout && !open && message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-on-callout", role: "status", "data-kind": isError ? "error" : "info", part: "outbound-number-notice", children: message }) : null
|
|
14527
|
+
] });
|
|
14528
|
+
}
|
|
14529
|
+
function OptionRow({
|
|
14530
|
+
selected,
|
|
14531
|
+
disabled,
|
|
14532
|
+
title,
|
|
14533
|
+
sub,
|
|
14534
|
+
tag,
|
|
14535
|
+
onSelect
|
|
14536
|
+
}) {
|
|
14537
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14538
|
+
"button",
|
|
14539
|
+
{
|
|
14540
|
+
type: "button",
|
|
14541
|
+
role: "option",
|
|
14542
|
+
"aria-selected": selected,
|
|
14543
|
+
"aria-disabled": disabled,
|
|
14544
|
+
className: "cv-cp-item cv-on-item",
|
|
14545
|
+
tabIndex: -1,
|
|
14546
|
+
onClick: onSelect,
|
|
14547
|
+
children: [
|
|
14548
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-on-text", children: [
|
|
14549
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-on-line", children: [
|
|
14550
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-cp-name", children: title }),
|
|
14551
|
+
tag ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-tag", children: tag.tag }) : null
|
|
14552
|
+
] }),
|
|
14553
|
+
sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-sub", children: sub }) : null,
|
|
14554
|
+
tag ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-on-hint", children: tag.hint }) : null
|
|
14555
|
+
] }),
|
|
14556
|
+
/* @__PURE__ */ jsxRuntime.jsx(Check, { size: 14, className: "cv-cp-check", "data-on": selected, "aria-hidden": true })
|
|
14557
|
+
]
|
|
14558
|
+
}
|
|
14559
|
+
);
|
|
14560
|
+
}
|
|
14561
|
+
var optionsOf = (list) => list ? [...list.querySelectorAll('[role="option"]')] : [];
|
|
14562
|
+
var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
|
|
14563
|
+
var TYPEAHEAD_MS = 700;
|
|
14564
|
+
function useListKeyboard(open) {
|
|
14565
|
+
const listRef = React.useRef(null);
|
|
14566
|
+
const typed = React.useRef({ text: "", at: 0 });
|
|
14567
|
+
React.useEffect(() => {
|
|
14568
|
+
if (!open) return;
|
|
14569
|
+
const options = optionsOf(listRef.current);
|
|
14570
|
+
(options.find((o) => o.getAttribute("aria-selected") === "true") ?? options[0])?.focus();
|
|
14571
|
+
}, [open]);
|
|
14572
|
+
const onKeyDown = (e) => {
|
|
14573
|
+
const options = optionsOf(listRef.current);
|
|
14574
|
+
const at = options.indexOf(e.target);
|
|
14575
|
+
const move = {
|
|
14576
|
+
ArrowDown: Math.min(at + 1, options.length - 1),
|
|
14577
|
+
ArrowUp: Math.max(at - 1, 0),
|
|
14578
|
+
Home: 0,
|
|
14579
|
+
End: options.length - 1
|
|
14580
|
+
};
|
|
14581
|
+
let next = move[e.key];
|
|
14582
|
+
if (next === void 0 && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
14583
|
+
const now = Date.now();
|
|
14584
|
+
const text = now - typed.current.at < TYPEAHEAD_MS ? typed.current.text + e.key : e.key;
|
|
14585
|
+
typed.current = { text, at: now };
|
|
14586
|
+
const wanted = fold(text);
|
|
14587
|
+
const match = options.findIndex((o) => fold(o.querySelector(".cv-cp-name")?.textContent ?? "").startsWith(wanted));
|
|
14588
|
+
next = match >= 0 ? match : void 0;
|
|
14589
|
+
}
|
|
14590
|
+
if (next === void 0) return;
|
|
14591
|
+
e.preventDefault();
|
|
14592
|
+
options[next]?.focus();
|
|
14593
|
+
};
|
|
14594
|
+
return { listRef, onKeyDown };
|
|
14595
|
+
}
|
|
13979
14596
|
var NOT_DIALABLE = /* @__PURE__ */ new Set(["*", "#"]);
|
|
14597
|
+
var DISPLAY_SIZES = ["1.375rem", "1.25rem", "1.125rem", "1rem"];
|
|
13980
14598
|
function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
13981
14599
|
const softphone = useSoftphone();
|
|
13982
14600
|
const state = useSoftphoneState();
|
|
13983
14601
|
const [value, setValue] = React.useState("");
|
|
13984
14602
|
const [country2, setCountry] = React.useState(DEFAULT_COUNTRY);
|
|
14603
|
+
const [changingNumber, setChangingNumber] = React.useState(false);
|
|
13985
14604
|
const format = useNumberFormatter();
|
|
13986
14605
|
const inputRef = React.useRef(null);
|
|
13987
14606
|
const caretRef = React.useRef(null);
|
|
13988
14607
|
const display = format(country2, value);
|
|
13989
14608
|
const target = toE164(country2, value);
|
|
13990
|
-
const canCall = state === "online" && target.length > 1;
|
|
14609
|
+
const canCall = state === "online" && target.length > 1 && !changingNumber;
|
|
14610
|
+
const typedInternational = value.trim().startsWith("+");
|
|
14611
|
+
const numberRegion = regionOfInternational(value);
|
|
13991
14612
|
React.useLayoutEffect(() => {
|
|
13992
14613
|
const caret = caretRef.current;
|
|
13993
14614
|
if (caret === null || !inputRef.current) return;
|
|
@@ -13995,6 +14616,14 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
13995
14616
|
const pos = caretAfterDigits(display, caret);
|
|
13996
14617
|
inputRef.current.setSelectionRange(pos, pos);
|
|
13997
14618
|
}, [display]);
|
|
14619
|
+
React.useLayoutEffect(() => {
|
|
14620
|
+
const input = inputRef.current;
|
|
14621
|
+
if (!input) return;
|
|
14622
|
+
for (const size of DISPLAY_SIZES) {
|
|
14623
|
+
input.style.fontSize = size;
|
|
14624
|
+
if (input.scrollWidth <= input.clientWidth) break;
|
|
14625
|
+
}
|
|
14626
|
+
}, [display]);
|
|
13998
14627
|
const append = (key) => {
|
|
13999
14628
|
onIntent?.();
|
|
14000
14629
|
setValue((v) => v + key);
|
|
@@ -14010,9 +14639,16 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
14010
14639
|
void softphone.call(target);
|
|
14011
14640
|
};
|
|
14012
14641
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-view", children: [
|
|
14013
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14642
|
+
/* @__PURE__ */ jsxRuntime.jsx(OutboundNumberPicker, { onBusyChange: setChangingNumber }),
|
|
14014
14643
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-dial-head", children: [
|
|
14015
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14644
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14645
|
+
CountryPicker,
|
|
14646
|
+
{
|
|
14647
|
+
value: numberRegion ?? country2,
|
|
14648
|
+
onChange: setCountry,
|
|
14649
|
+
fromNumber: typedInternational
|
|
14650
|
+
}
|
|
14651
|
+
),
|
|
14016
14652
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14017
14653
|
"input",
|
|
14018
14654
|
{
|
|
@@ -14036,6 +14672,8 @@ function Dialpad({ outcome = null, onIntent, keyTones = true } = {}) {
|
|
|
14036
14672
|
type: "button",
|
|
14037
14673
|
className: "cv-dial-side",
|
|
14038
14674
|
"data-hidden": value.length === 0,
|
|
14675
|
+
tabIndex: value.length === 0 ? -1 : void 0,
|
|
14676
|
+
"aria-hidden": value.length === 0 || void 0,
|
|
14039
14677
|
onClick: backspace,
|
|
14040
14678
|
"aria-label": "Borrar",
|
|
14041
14679
|
children: /* @__PURE__ */ jsxRuntime.jsx(Delete, { size: 19 })
|
|
@@ -14460,6 +15098,9 @@ function CallScreen({
|
|
|
14460
15098
|
}) {
|
|
14461
15099
|
const view = useCall(call);
|
|
14462
15100
|
const softphone = useSoftphone();
|
|
15101
|
+
const outbound = useOutboundNumbers(call.direction === "outbound" ? outboundNumberStore(softphone) : null).settings;
|
|
15102
|
+
const formatIntl2 = useIntlFormatter();
|
|
15103
|
+
const fromName = outbound && outbound.numbers.length > 1 ? numberName(effectiveNumber(outbound), formatIntl2) : null;
|
|
14463
15104
|
const [panel, setPanel] = React.useState(null);
|
|
14464
15105
|
const showTransfer = panel === "transfer";
|
|
14465
15106
|
const showKeypad = panel === "keypad";
|
|
@@ -14670,6 +15311,13 @@ function CallScreen({
|
|
|
14670
15311
|
] }),
|
|
14671
15312
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call-name", children: name }),
|
|
14672
15313
|
showNumber ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-call-number", children: number }) : null,
|
|
15314
|
+
fromName ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-call-from", children: [
|
|
15315
|
+
/* @__PURE__ */ jsxRuntime.jsx(PhoneOutgoing, { size: 11, "aria-hidden": true }),
|
|
15316
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
15317
|
+
"Desde ",
|
|
15318
|
+
fromName
|
|
15319
|
+
] })
|
|
15320
|
+
] }) : null,
|
|
14673
15321
|
s === "active" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-timer", "aria-label": "Duraci\xF3n de la llamada", children: [
|
|
14674
15322
|
/* @__PURE__ */ jsxRuntime.jsx("b", { children: mm }),
|
|
14675
15323
|
/* @__PURE__ */ jsxRuntime.jsx("i", { children: ":" }),
|
|
@@ -15085,12 +15733,23 @@ function HandlerChain({ handlers }) {
|
|
|
15085
15733
|
] }, `${h.extension}-${i}`);
|
|
15086
15734
|
}) });
|
|
15087
15735
|
}
|
|
15736
|
+
function outboundFrom(entry, known, formatIntl2) {
|
|
15737
|
+
if (entry.direction !== "outbound" || !entry.from) return null;
|
|
15738
|
+
const number = formatIntl2(`+${entry.from}`);
|
|
15739
|
+
const label = known?.numbers.find((n) => n.number === entry.from)?.label;
|
|
15740
|
+
if (!label) return number;
|
|
15741
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
15742
|
+
label,
|
|
15743
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-detail-row-sub", children: number })
|
|
15744
|
+
] });
|
|
15745
|
+
}
|
|
15088
15746
|
function HistoryDetail({
|
|
15089
15747
|
entry,
|
|
15090
15748
|
onBack
|
|
15091
15749
|
}) {
|
|
15092
15750
|
const softphone = useSoftphone();
|
|
15093
15751
|
const formatIntl2 = useIntlFormatter();
|
|
15752
|
+
const knownNumbers = useKnownOutboundNumbers(outboundNumberStore(softphone));
|
|
15094
15753
|
const [detail, setDetail] = React.useState(null);
|
|
15095
15754
|
const [loadingDetail, setLoadingDetail] = React.useState(false);
|
|
15096
15755
|
React.useEffect(() => {
|
|
@@ -15106,6 +15765,7 @@ function HistoryDetail({
|
|
|
15106
15765
|
}, [entry]);
|
|
15107
15766
|
const formatted = formatIntl2(entry.number);
|
|
15108
15767
|
const name = entry.name ?? formatted;
|
|
15768
|
+
const from = outboundFrom(entry, knownNumbers, formatIntl2);
|
|
15109
15769
|
const handledBy = detail?.handledBy ?? entry.handledBy;
|
|
15110
15770
|
const recordingUrls = detail?.recordingUrls ?? [];
|
|
15111
15771
|
const loadingRecording = entry.recorded && loadingDetail && recordingUrls.length === 0;
|
|
@@ -15142,7 +15802,8 @@ function HistoryDetail({
|
|
|
15142
15802
|
entry.durationSec > 0 ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Timer, eyebrow: "Duraci\xF3n", text: formatDuration(entry.durationSec) }) : null,
|
|
15143
15803
|
/* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Phone, eyebrow: "N\xFAmero", text: formatted }),
|
|
15144
15804
|
handledBy.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: User, eyebrow: handlerLabel, text: /* @__PURE__ */ jsxRuntime.jsx(HandlerChain, { handlers: handledBy }) }) : null,
|
|
15145
|
-
entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null
|
|
15805
|
+
entry.direction === "inbound" && entry.did ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: Hash, eyebrow: "N\xFAmero marcado (DID)", text: formatIntl2(entry.did) }) : null,
|
|
15806
|
+
from ? /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { icon: PhoneOutgoing, eyebrow: "Llamada desde", text: from }) : null
|
|
15146
15807
|
] })
|
|
15147
15808
|
] }),
|
|
15148
15809
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-detail-foot", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -15335,6 +15996,110 @@ function HistoryTab({ source }) {
|
|
|
15335
15996
|
selected ? /* @__PURE__ */ jsxRuntime.jsx(HistoryDetail, { entry: selected, onBack: () => setSelected(null) }) : null
|
|
15336
15997
|
] });
|
|
15337
15998
|
}
|
|
15999
|
+
|
|
16000
|
+
// src/ui/audio/ringtone-store.ts
|
|
16001
|
+
var stores2 = /* @__PURE__ */ new WeakMap();
|
|
16002
|
+
function ringtoneStore(softphone) {
|
|
16003
|
+
const source = softphone.sources?.ringtones;
|
|
16004
|
+
if (!source) return null;
|
|
16005
|
+
let store = stores2.get(softphone);
|
|
16006
|
+
if (!store) {
|
|
16007
|
+
store = createRingtoneStore(source);
|
|
16008
|
+
stores2.set(softphone, store);
|
|
16009
|
+
}
|
|
16010
|
+
return store;
|
|
16011
|
+
}
|
|
16012
|
+
function createRingtoneStore(source, decodeImpl = decodeRingtone) {
|
|
16013
|
+
let settings = null;
|
|
16014
|
+
let audioWanted = false;
|
|
16015
|
+
let loaded = null;
|
|
16016
|
+
let loading2 = null;
|
|
16017
|
+
let inFlight = null;
|
|
16018
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
16019
|
+
const notify = () => {
|
|
16020
|
+
for (const l of listeners) l();
|
|
16021
|
+
};
|
|
16022
|
+
const effectiveUrl = () => {
|
|
16023
|
+
if (!settings) return null;
|
|
16024
|
+
return settings.catalogue.find((r) => r.id === settings.effective)?.url ?? null;
|
|
16025
|
+
};
|
|
16026
|
+
const loadEffective = async () => {
|
|
16027
|
+
const url = effectiveUrl();
|
|
16028
|
+
if (!audioWanted || !url || loaded?.url === url || loading2 === url) return;
|
|
16029
|
+
loading2 = url;
|
|
16030
|
+
try {
|
|
16031
|
+
const buffer = await decodeImpl(url);
|
|
16032
|
+
if (audioWanted && effectiveUrl() === url) {
|
|
16033
|
+
loaded = { url, buffer };
|
|
16034
|
+
notify();
|
|
16035
|
+
}
|
|
16036
|
+
} catch (error) {
|
|
16037
|
+
console.warn(`[softphone] ringtone could not be loaded, using the built-in tone: ${url}`, error);
|
|
16038
|
+
} finally {
|
|
16039
|
+
if (loading2 === url) loading2 = null;
|
|
16040
|
+
}
|
|
16041
|
+
};
|
|
16042
|
+
const apply = (next) => {
|
|
16043
|
+
settings = next;
|
|
16044
|
+
if (loaded && loaded.url !== effectiveUrl()) loaded = null;
|
|
16045
|
+
notify();
|
|
16046
|
+
};
|
|
16047
|
+
return {
|
|
16048
|
+
getSettings: () => settings,
|
|
16049
|
+
readyBuffer: () => loaded && loaded.url === effectiveUrl() ? loaded.buffer : null,
|
|
16050
|
+
readyLoop: () => {
|
|
16051
|
+
if (!loaded || loaded.url !== effectiveUrl() || !settings) return null;
|
|
16052
|
+
return settings.catalogue.find((r) => r.id === settings.effective)?.loop ?? null;
|
|
16053
|
+
},
|
|
16054
|
+
refresh() {
|
|
16055
|
+
inFlight ??= (async () => {
|
|
16056
|
+
if (!settings && source.cached) {
|
|
16057
|
+
const early = await source.cached().catch(() => null);
|
|
16058
|
+
if (early && !settings) {
|
|
16059
|
+
apply(early);
|
|
16060
|
+
void loadEffective();
|
|
16061
|
+
}
|
|
16062
|
+
}
|
|
16063
|
+
try {
|
|
16064
|
+
apply(await source.get());
|
|
16065
|
+
} catch (error) {
|
|
16066
|
+
console.warn("[softphone] ringtone settings could not be loaded; keeping the previous ones", error);
|
|
16067
|
+
} finally {
|
|
16068
|
+
inFlight = null;
|
|
16069
|
+
}
|
|
16070
|
+
await loadEffective();
|
|
16071
|
+
})();
|
|
16072
|
+
return inFlight;
|
|
16073
|
+
},
|
|
16074
|
+
async setMine(id) {
|
|
16075
|
+
apply(await source.setMine(id));
|
|
16076
|
+
void loadEffective();
|
|
16077
|
+
},
|
|
16078
|
+
wantAudio(on) {
|
|
16079
|
+
if (audioWanted === on) return;
|
|
16080
|
+
audioWanted = on;
|
|
16081
|
+
if (!on) {
|
|
16082
|
+
loaded = null;
|
|
16083
|
+
notify();
|
|
16084
|
+
} else {
|
|
16085
|
+
void loadEffective();
|
|
16086
|
+
}
|
|
16087
|
+
},
|
|
16088
|
+
decode: (url) => decodeImpl(url),
|
|
16089
|
+
subscribe(listener) {
|
|
16090
|
+
listeners.add(listener);
|
|
16091
|
+
return () => listeners.delete(listener);
|
|
16092
|
+
}
|
|
16093
|
+
};
|
|
16094
|
+
}
|
|
16095
|
+
async function decodeRingtone(url) {
|
|
16096
|
+
const Offline = typeof OfflineAudioContext !== "undefined" ? OfflineAudioContext : globalThis.webkitOfflineAudioContext;
|
|
16097
|
+
if (!Offline) throw new Error("this browser cannot decode audio files");
|
|
16098
|
+
const res = await fetch(url, { credentials: "omit" });
|
|
16099
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
16100
|
+
const bytes = await res.arrayBuffer();
|
|
16101
|
+
return new Offline(1, 1, 48e3).decodeAudioData(bytes);
|
|
16102
|
+
}
|
|
15338
16103
|
function SettingsSection({
|
|
15339
16104
|
title,
|
|
15340
16105
|
children
|
|
@@ -15344,11 +16109,641 @@ function SettingsSection({
|
|
|
15344
16109
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-settings-section-body", children })
|
|
15345
16110
|
] });
|
|
15346
16111
|
}
|
|
16112
|
+
function SettingsNavRow({
|
|
16113
|
+
icon: Icon3,
|
|
16114
|
+
title,
|
|
16115
|
+
value,
|
|
16116
|
+
onOpen,
|
|
16117
|
+
rowRef
|
|
16118
|
+
}) {
|
|
16119
|
+
const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
16120
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-settings-row-icon", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx(Icon3, { size: 16 }) }),
|
|
16121
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-settings-nav-text", children: [
|
|
16122
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-settings-nav-title", children: title }),
|
|
16123
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-settings-nav-value", children: value })
|
|
16124
|
+
] }),
|
|
16125
|
+
onOpen ? /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, { size: 16, className: "cv-settings-nav-chevron", "aria-hidden": true }) : null
|
|
16126
|
+
] });
|
|
16127
|
+
return onOpen ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "cv-settings-nav", onClick: onOpen, ref: rowRef, children: inner }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-settings-nav cv-settings-nav--static", children: inner });
|
|
16128
|
+
}
|
|
16129
|
+
function ChoiceList({
|
|
16130
|
+
label,
|
|
16131
|
+
choices,
|
|
16132
|
+
value,
|
|
16133
|
+
onSelect,
|
|
16134
|
+
onReselect,
|
|
16135
|
+
disabled = false
|
|
16136
|
+
}) {
|
|
16137
|
+
const name = React.useId();
|
|
16138
|
+
const pointer = React.useRef(false);
|
|
16139
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-choices", role: "radiogroup", "aria-label": label, "aria-disabled": disabled || void 0, children: choices.map((c) => {
|
|
16140
|
+
const checked = c.value === value;
|
|
16141
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16142
|
+
"label",
|
|
16143
|
+
{
|
|
16144
|
+
className: "cv-choice",
|
|
16145
|
+
"data-checked": checked || void 0,
|
|
16146
|
+
"data-disabled": disabled || void 0,
|
|
16147
|
+
onPointerDown: () => {
|
|
16148
|
+
pointer.current = true;
|
|
16149
|
+
},
|
|
16150
|
+
children: [
|
|
16151
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16152
|
+
"input",
|
|
16153
|
+
{
|
|
16154
|
+
type: "radio",
|
|
16155
|
+
className: "cv-choice-input",
|
|
16156
|
+
name,
|
|
16157
|
+
value: c.value,
|
|
16158
|
+
checked,
|
|
16159
|
+
onChange: () => {
|
|
16160
|
+
const via = pointer.current ? "pointer" : "keyboard";
|
|
16161
|
+
pointer.current = false;
|
|
16162
|
+
if (!disabled) onSelect(c.value, via);
|
|
16163
|
+
},
|
|
16164
|
+
onClick: () => {
|
|
16165
|
+
if (c.value === value) {
|
|
16166
|
+
pointer.current = false;
|
|
16167
|
+
if (!disabled) onReselect?.(c.value);
|
|
16168
|
+
}
|
|
16169
|
+
}
|
|
16170
|
+
}
|
|
16171
|
+
),
|
|
16172
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-text", children: [
|
|
16173
|
+
c.tag ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-line", children: [
|
|
16174
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-label", children: c.label }),
|
|
16175
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-tag", children: c.tag })
|
|
16176
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-label", children: c.label }),
|
|
16177
|
+
c.sub ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-sub", children: c.sub }) : null,
|
|
16178
|
+
c.hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-choice-hint", children: c.hint }) : null
|
|
16179
|
+
] }),
|
|
16180
|
+
c.trailing,
|
|
16181
|
+
/* @__PURE__ */ jsxRuntime.jsx(Check, { size: 16, className: "cv-choice-check", "aria-hidden": true })
|
|
16182
|
+
]
|
|
16183
|
+
},
|
|
16184
|
+
c.value
|
|
16185
|
+
);
|
|
16186
|
+
}) });
|
|
16187
|
+
}
|
|
16188
|
+
function SettingsPage({
|
|
16189
|
+
title,
|
|
16190
|
+
intro,
|
|
16191
|
+
onBack,
|
|
16192
|
+
children
|
|
16193
|
+
}) {
|
|
16194
|
+
const titleId = React.useId();
|
|
16195
|
+
const heading = React.useRef(null);
|
|
16196
|
+
React.useEffect(() => heading.current?.focus(), []);
|
|
16197
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16198
|
+
"div",
|
|
16199
|
+
{
|
|
16200
|
+
className: "cv-detail cv-settings-page",
|
|
16201
|
+
role: "region",
|
|
16202
|
+
"aria-labelledby": titleId,
|
|
16203
|
+
onKeyDown: (e) => {
|
|
16204
|
+
if (e.key === "Escape") {
|
|
16205
|
+
e.stopPropagation();
|
|
16206
|
+
onBack();
|
|
16207
|
+
}
|
|
16208
|
+
},
|
|
16209
|
+
children: [
|
|
16210
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "cv-detail-head", children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "cv-detail-back", onClick: onBack, children: [
|
|
16211
|
+
/* @__PURE__ */ jsxRuntime.jsx(ArrowLeft, { size: 16, "aria-hidden": true }),
|
|
16212
|
+
"Ajustes"
|
|
16213
|
+
] }) }),
|
|
16214
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-settings-page-body", children: [
|
|
16215
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "cv-settings-page-title", id: titleId, tabIndex: -1, ref: heading, children: title }),
|
|
16216
|
+
intro ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-settings-page-intro", children: intro }) : null,
|
|
16217
|
+
children
|
|
16218
|
+
] })
|
|
16219
|
+
]
|
|
16220
|
+
}
|
|
16221
|
+
);
|
|
16222
|
+
}
|
|
16223
|
+
var SYSTEM_DEFAULT = "";
|
|
16224
|
+
var PAGES = {
|
|
16225
|
+
input: {
|
|
16226
|
+
title: "Micr\xF3fono",
|
|
16227
|
+
intro: "El micr\xF3fono que usa el tel\xE9fono en las llamadas.",
|
|
16228
|
+
empty: "No se encontr\xF3 ning\xFAn micr\xF3fono."
|
|
16229
|
+
},
|
|
16230
|
+
output: {
|
|
16231
|
+
title: "Altavoces",
|
|
16232
|
+
intro: "D\xF3nde oyes a la otra persona durante una llamada.",
|
|
16233
|
+
empty: "No se encontr\xF3 ning\xFAn altavoz."
|
|
16234
|
+
},
|
|
16235
|
+
ringtone: {
|
|
16236
|
+
title: "Salida del tono",
|
|
16237
|
+
intro: "D\xF3nde suena el tono cuando entra una llamada. La llamada se oye por \xABAltavoces\xBB.",
|
|
16238
|
+
empty: "No se encontr\xF3 ning\xFAn altavoz."
|
|
16239
|
+
}
|
|
16240
|
+
};
|
|
16241
|
+
function deviceSummary(kind, list, id) {
|
|
16242
|
+
const i = id ? list.findIndex((d) => d.deviceId === id) : -1;
|
|
16243
|
+
return i >= 0 ? deviceLabel(list[i], i, kind) : "Predeterminado del sistema";
|
|
16244
|
+
}
|
|
16245
|
+
function DevicePage({ kind, onBack }) {
|
|
16246
|
+
const devices = useDevices();
|
|
16247
|
+
const page = PAGES[kind];
|
|
16248
|
+
const list = kind === "input" ? devices.inputs : devices.outputs;
|
|
16249
|
+
const current = kind === "input" ? devices.currentInputId : kind === "output" ? devices.currentOutputId : devices.currentRingtoneOutputId;
|
|
16250
|
+
const apply = (id) => kind === "input" ? devices.setInput(id) : kind === "output" ? devices.setOutput(id) : devices.setRingtoneOutput(id);
|
|
16251
|
+
const choices = [
|
|
16252
|
+
{ value: SYSTEM_DEFAULT, label: "Predeterminado del sistema" },
|
|
16253
|
+
...list.map((d, i) => ({ value: d.deviceId, label: deviceLabel(d, i, kind) }))
|
|
16254
|
+
];
|
|
16255
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(SettingsPage, { title: page.title, intro: page.intro, onBack, children: [
|
|
16256
|
+
list.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
16257
|
+
ChoiceList,
|
|
16258
|
+
{
|
|
16259
|
+
label: page.title,
|
|
16260
|
+
choices,
|
|
16261
|
+
value: current ?? SYSTEM_DEFAULT,
|
|
16262
|
+
onSelect: (v) => void apply(v === SYSTEM_DEFAULT ? null : v)
|
|
16263
|
+
}
|
|
16264
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-settings-page-empty", children: page.empty }),
|
|
16265
|
+
kind === "output" ? /* @__PURE__ */ jsxRuntime.jsx(SpeakerTest, { sinkId: devices.currentOutputId }) : null
|
|
16266
|
+
] });
|
|
16267
|
+
}
|
|
16268
|
+
function SpeakerTest({ sinkId }) {
|
|
16269
|
+
const [testing, setTesting] = React.useState(false);
|
|
16270
|
+
const tone = React.useRef(null);
|
|
16271
|
+
const timer = React.useRef(null);
|
|
16272
|
+
React.useEffect(
|
|
16273
|
+
() => () => {
|
|
16274
|
+
tone.current?.stop();
|
|
16275
|
+
if (timer.current) clearTimeout(timer.current);
|
|
16276
|
+
},
|
|
16277
|
+
[]
|
|
16278
|
+
);
|
|
16279
|
+
const test = () => {
|
|
16280
|
+
primeAudio();
|
|
16281
|
+
tone.current?.stop();
|
|
16282
|
+
tone.current = playTestTone(sinkId);
|
|
16283
|
+
setTesting(true);
|
|
16284
|
+
if (timer.current) clearTimeout(timer.current);
|
|
16285
|
+
timer.current = setTimeout(() => setTesting(false), TEST_TONE_MS);
|
|
16286
|
+
};
|
|
16287
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "cv-btn cv-settings-test", onClick: test, disabled: testing, children: [
|
|
16288
|
+
/* @__PURE__ */ jsxRuntime.jsx(Volume2, { size: 16, "aria-hidden": true }),
|
|
16289
|
+
testing ? "Sonando\u2026" : "Probar altavoz"
|
|
16290
|
+
] });
|
|
16291
|
+
}
|
|
16292
|
+
|
|
16293
|
+
// src/ui/audio/preview.ts
|
|
16294
|
+
var PREVIEW_MAX_MS = 1e4;
|
|
16295
|
+
var BUILT_IN_PREVIEW_MS = 6e3;
|
|
16296
|
+
function previewDurationMs(buffer, loop) {
|
|
16297
|
+
if (!buffer) return BUILT_IN_PREVIEW_MS;
|
|
16298
|
+
const seconds = loop ? loop.end - loop.start : buffer.duration;
|
|
16299
|
+
return Math.min(seconds * 1e3, PREVIEW_MAX_MS);
|
|
16300
|
+
}
|
|
16301
|
+
function previewRingtone(ringtone, options = {}) {
|
|
16302
|
+
primeAudio();
|
|
16303
|
+
let tone = null;
|
|
16304
|
+
let timer = null;
|
|
16305
|
+
let over = false;
|
|
16306
|
+
let settle;
|
|
16307
|
+
let fail;
|
|
16308
|
+
const done = new Promise((resolve, reject) => {
|
|
16309
|
+
settle = resolve;
|
|
16310
|
+
fail = reject;
|
|
16311
|
+
});
|
|
16312
|
+
done.catch(() => {
|
|
16313
|
+
});
|
|
16314
|
+
const stop = () => {
|
|
16315
|
+
if (over) return;
|
|
16316
|
+
over = true;
|
|
16317
|
+
if (timer) clearTimeout(timer);
|
|
16318
|
+
tone?.stop();
|
|
16319
|
+
settle();
|
|
16320
|
+
};
|
|
16321
|
+
const play = (buffer) => {
|
|
16322
|
+
if (over) return;
|
|
16323
|
+
const loop = buffer ? ringtone.loop ?? null : null;
|
|
16324
|
+
tone = playRingtone({ buffer, loop, sinkId: options.sinkId ?? null });
|
|
16325
|
+
timer = setTimeout(stop, previewDurationMs(buffer, loop));
|
|
16326
|
+
};
|
|
16327
|
+
if (!ringtone.url) {
|
|
16328
|
+
play(null);
|
|
16329
|
+
} else {
|
|
16330
|
+
decodeRingtone(ringtone.url).then(play, (error) => {
|
|
16331
|
+
if (over) return;
|
|
16332
|
+
over = true;
|
|
16333
|
+
fail(error);
|
|
16334
|
+
});
|
|
16335
|
+
}
|
|
16336
|
+
return { stop, done };
|
|
16337
|
+
}
|
|
16338
|
+
|
|
16339
|
+
// src/ui/views/settings/ringtone-state.ts
|
|
16340
|
+
var noSubscribe2 = () => () => {
|
|
16341
|
+
};
|
|
16342
|
+
var noSettings2 = () => null;
|
|
16343
|
+
function useRingtoneSettings(store) {
|
|
16344
|
+
const settings = React.useSyncExternalStore(store?.subscribe ?? noSubscribe2, store?.getSettings ?? noSettings2);
|
|
16345
|
+
const [failed, setFailed] = React.useState(false);
|
|
16346
|
+
const load = React.useCallback(() => {
|
|
16347
|
+
if (!store) return () => {
|
|
16348
|
+
};
|
|
16349
|
+
let live = true;
|
|
16350
|
+
setFailed(false);
|
|
16351
|
+
void store.refresh().then(() => {
|
|
16352
|
+
if (live) setFailed(store.getSettings() === null);
|
|
16353
|
+
});
|
|
16354
|
+
return () => {
|
|
16355
|
+
live = false;
|
|
16356
|
+
};
|
|
16357
|
+
}, [store]);
|
|
16358
|
+
React.useEffect(load, [load]);
|
|
16359
|
+
return { settings, failed, retry: load };
|
|
16360
|
+
}
|
|
16361
|
+
function ringtoneSummary(settings, failed) {
|
|
16362
|
+
if (!settings) return failed ? "No disponible" : "Cargando\u2026";
|
|
16363
|
+
const byId = (id) => id ? settings.catalogue.find((r) => r.id === id) : void 0;
|
|
16364
|
+
const mine = byId(settings.mine);
|
|
16365
|
+
if (mine) return mine.name;
|
|
16366
|
+
const org = byId(settings.orgDefault);
|
|
16367
|
+
if (org) return `${org.name} \xB7 organizaci\xF3n`;
|
|
16368
|
+
return settings.catalogue.find((r) => r.url === null)?.name ?? "Predeterminado";
|
|
16369
|
+
}
|
|
16370
|
+
function useRingtonePreview(store, sinkId) {
|
|
16371
|
+
const [playing, setPlaying] = React.useState(null);
|
|
16372
|
+
const [error, setError] = React.useState(null);
|
|
16373
|
+
const current = React.useRef(null);
|
|
16374
|
+
const generation = React.useRef(0);
|
|
16375
|
+
const stop = React.useCallback(() => {
|
|
16376
|
+
generation.current += 1;
|
|
16377
|
+
const c = current.current;
|
|
16378
|
+
current.current = null;
|
|
16379
|
+
if (c?.timer) clearTimeout(c.timer);
|
|
16380
|
+
c?.tone?.stop();
|
|
16381
|
+
setPlaying(null);
|
|
16382
|
+
}, []);
|
|
16383
|
+
React.useEffect(() => stop, [stop]);
|
|
16384
|
+
const start = (settings, choice) => {
|
|
16385
|
+
stop();
|
|
16386
|
+
primeAudio();
|
|
16387
|
+
setError(null);
|
|
16388
|
+
const gen = generation.current;
|
|
16389
|
+
const orgDefault = settings.catalogue.some((r) => r.id === settings.orgDefault) ? settings.orgDefault : null;
|
|
16390
|
+
const id = choice || orgDefault;
|
|
16391
|
+
const option = settings.catalogue.find((r) => r.id === id) ?? settings.catalogue.find((r) => r.url === null);
|
|
16392
|
+
setPlaying(choice);
|
|
16393
|
+
current.current = { tone: null, timer: null };
|
|
16394
|
+
const play = (buffer) => {
|
|
16395
|
+
if (gen !== generation.current) return;
|
|
16396
|
+
const loop = buffer ? option?.loop ?? null : null;
|
|
16397
|
+
const tone = playRingtone({ buffer, loop, sinkId });
|
|
16398
|
+
current.current = { tone, timer: setTimeout(stop, previewDurationMs(buffer, loop)) };
|
|
16399
|
+
};
|
|
16400
|
+
if (!option?.url) {
|
|
16401
|
+
play(null);
|
|
16402
|
+
return;
|
|
16403
|
+
}
|
|
16404
|
+
store.decode(option.url).then(play, () => {
|
|
16405
|
+
if (gen !== generation.current) return;
|
|
16406
|
+
stop();
|
|
16407
|
+
setError("No se pudo reproducir el tono.");
|
|
16408
|
+
});
|
|
16409
|
+
};
|
|
16410
|
+
return { playing, error, start, stop };
|
|
16411
|
+
}
|
|
16412
|
+
var ORG_DEFAULT = "";
|
|
16413
|
+
var KEYBOARD_SETTLE_MS = 300;
|
|
16414
|
+
function RingtonePage({
|
|
16415
|
+
store,
|
|
16416
|
+
settings,
|
|
16417
|
+
failed,
|
|
16418
|
+
onRetry,
|
|
16419
|
+
onBack
|
|
16420
|
+
}) {
|
|
16421
|
+
const softphone = useSoftphone();
|
|
16422
|
+
const devices = useDevices();
|
|
16423
|
+
const preview = useRingtonePreview(store, devices.canSelectRingtoneOutput ? devices.currentRingtoneOutputId : null);
|
|
16424
|
+
const [pending, setPending] = React.useState(void 0);
|
|
16425
|
+
const [error, setError] = React.useState(null);
|
|
16426
|
+
const save = useSerialSave(store, {
|
|
16427
|
+
onSettled: () => setPending(void 0),
|
|
16428
|
+
onFailed: () => {
|
|
16429
|
+
setPending(void 0);
|
|
16430
|
+
setError("No se pudo guardar el tono. Int\xE9ntalo de nuevo.");
|
|
16431
|
+
}
|
|
16432
|
+
});
|
|
16433
|
+
const latest = React.useRef(settings);
|
|
16434
|
+
latest.current = settings;
|
|
16435
|
+
const settle = React.useRef(null);
|
|
16436
|
+
const { stop: stopPreview } = preview;
|
|
16437
|
+
React.useEffect(() => softphone.on("incomingCall" /* IncomingCall */, stopPreview), [softphone, stopPreview]);
|
|
16438
|
+
React.useEffect(
|
|
16439
|
+
() => () => {
|
|
16440
|
+
if (settle.current) {
|
|
16441
|
+
clearTimeout(settle.current.timer);
|
|
16442
|
+
save(settle.current.value || null);
|
|
16443
|
+
}
|
|
16444
|
+
},
|
|
16445
|
+
[save]
|
|
16446
|
+
);
|
|
16447
|
+
const commit = (value) => {
|
|
16448
|
+
save(value || null);
|
|
16449
|
+
if (latest.current) preview.start(latest.current, value);
|
|
16450
|
+
};
|
|
16451
|
+
const onSelect = (value, via) => {
|
|
16452
|
+
setError(null);
|
|
16453
|
+
setPending(value);
|
|
16454
|
+
if (settle.current) clearTimeout(settle.current.timer);
|
|
16455
|
+
settle.current = null;
|
|
16456
|
+
if (via === "pointer") {
|
|
16457
|
+
commit(value);
|
|
16458
|
+
return;
|
|
16459
|
+
}
|
|
16460
|
+
preview.stop();
|
|
16461
|
+
settle.current = {
|
|
16462
|
+
value,
|
|
16463
|
+
timer: setTimeout(() => {
|
|
16464
|
+
settle.current = null;
|
|
16465
|
+
commit(value);
|
|
16466
|
+
}, KEYBOARD_SETTLE_MS)
|
|
16467
|
+
};
|
|
16468
|
+
};
|
|
16469
|
+
const onReselect = (value) => {
|
|
16470
|
+
if (!settings) return;
|
|
16471
|
+
if (preview.playing === value) preview.stop();
|
|
16472
|
+
else preview.start(settings, value);
|
|
16473
|
+
};
|
|
16474
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
16475
|
+
SettingsPage,
|
|
16476
|
+
{
|
|
16477
|
+
title: "Tono de llamada",
|
|
16478
|
+
intro: "Lo que suena cuando entra una llamada. Se guarda en tu cuenta, as\xED que es el mismo en todos tus dispositivos.",
|
|
16479
|
+
onBack,
|
|
16480
|
+
children: settings ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
16481
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16482
|
+
ChoiceList,
|
|
16483
|
+
{
|
|
16484
|
+
label: "Tono de llamada",
|
|
16485
|
+
choices: choicesFor(settings, preview.playing),
|
|
16486
|
+
value: pending !== void 0 ? pending : currentValue(settings),
|
|
16487
|
+
onSelect,
|
|
16488
|
+
onReselect
|
|
16489
|
+
}
|
|
16490
|
+
),
|
|
16491
|
+
error ?? preview.error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-settings-error", role: "alert", children: error ?? preview.error }) : null
|
|
16492
|
+
] }) : failed ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-empty", children: [
|
|
16493
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-empty-title", children: "No se pudieron cargar los tonos." }),
|
|
16494
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "cv-btn cv-settings-retry", onClick: onRetry, children: "Reintentar" })
|
|
16495
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-empty", role: "status", children: [
|
|
16496
|
+
/* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { className: "cv-empty-spinner", size: 18, "aria-hidden": true }),
|
|
16497
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-empty-title", children: "Cargando\u2026" })
|
|
16498
|
+
] })
|
|
16499
|
+
}
|
|
16500
|
+
);
|
|
16501
|
+
}
|
|
16502
|
+
function currentValue(settings) {
|
|
16503
|
+
return settings.mine && settings.catalogue.some((r) => r.id === settings.mine) ? settings.mine : ORG_DEFAULT;
|
|
16504
|
+
}
|
|
16505
|
+
function choicesFor(settings, playing) {
|
|
16506
|
+
const org = settings.orgDefault ? settings.catalogue.find((r) => r.id === settings.orgDefault) : void 0;
|
|
16507
|
+
const builtIn = settings.catalogue.find((r) => r.url === null);
|
|
16508
|
+
const mark = (value) => playing === value ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-playing", children: [
|
|
16509
|
+
/* @__PURE__ */ jsxRuntime.jsx(Volume2, { size: 15, "aria-hidden": true }),
|
|
16510
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-sr-only", children: "Sonando" })
|
|
16511
|
+
] }) : void 0;
|
|
16512
|
+
const orgChoice = {
|
|
16513
|
+
value: ORG_DEFAULT,
|
|
16514
|
+
label: org ? "Predeterminado de la organizaci\xF3n" : "Predeterminado",
|
|
16515
|
+
sub: (org ?? builtIn)?.name ?? "",
|
|
16516
|
+
trailing: mark(ORG_DEFAULT)
|
|
16517
|
+
};
|
|
16518
|
+
return [orgChoice, ...settings.catalogue.map((r) => ({ value: r.id, label: r.name, trailing: mark(r.id) }))];
|
|
16519
|
+
}
|
|
16520
|
+
function useSerialSave(store, handlers) {
|
|
16521
|
+
const wanted = React.useRef(null);
|
|
16522
|
+
const busy = React.useRef(false);
|
|
16523
|
+
const on = React.useRef(handlers);
|
|
16524
|
+
on.current = handlers;
|
|
16525
|
+
const [save] = React.useState(() => (id) => {
|
|
16526
|
+
wanted.current = { id };
|
|
16527
|
+
if (busy.current) return;
|
|
16528
|
+
busy.current = true;
|
|
16529
|
+
void (async () => {
|
|
16530
|
+
while (wanted.current) {
|
|
16531
|
+
const { id: next } = wanted.current;
|
|
16532
|
+
wanted.current = null;
|
|
16533
|
+
try {
|
|
16534
|
+
await store.setMine(next);
|
|
16535
|
+
if (!wanted.current) on.current.onSettled();
|
|
16536
|
+
} catch {
|
|
16537
|
+
if (!wanted.current) on.current.onFailed();
|
|
16538
|
+
}
|
|
16539
|
+
}
|
|
16540
|
+
busy.current = false;
|
|
16541
|
+
})();
|
|
16542
|
+
});
|
|
16543
|
+
return save;
|
|
16544
|
+
}
|
|
16545
|
+
var ORG_DEFAULT2 = "";
|
|
16546
|
+
var KEYBOARD_SETTLE_MS2 = 800;
|
|
16547
|
+
function OutboundNumberPage({
|
|
16548
|
+
store,
|
|
16549
|
+
settings,
|
|
16550
|
+
onBack
|
|
16551
|
+
}) {
|
|
16552
|
+
const formatIntl2 = useIntlFormatter();
|
|
16553
|
+
const current = numberName(effectiveNumber(settings), formatIntl2);
|
|
16554
|
+
const change = useOutboundNumberChange(store, settings, current);
|
|
16555
|
+
const [pending, setPending] = React.useState(void 0);
|
|
16556
|
+
const settle = React.useRef(null);
|
|
16557
|
+
const choose = React.useRef(change.choose);
|
|
16558
|
+
choose.current = change.choose;
|
|
16559
|
+
React.useEffect(
|
|
16560
|
+
() => () => {
|
|
16561
|
+
if (settle.current) clearTimeout(settle.current);
|
|
16562
|
+
},
|
|
16563
|
+
[]
|
|
16564
|
+
);
|
|
16565
|
+
const notice = React.useRef(null);
|
|
16566
|
+
const apply = async (value) => {
|
|
16567
|
+
setPending(value);
|
|
16568
|
+
const applied = await choose.current(value || null);
|
|
16569
|
+
setPending(void 0);
|
|
16570
|
+
if (!applied) requestAnimationFrame(() => notice.current?.scrollIntoView?.({ block: "nearest" }));
|
|
16571
|
+
};
|
|
16572
|
+
const onSelect = (value, via) => {
|
|
16573
|
+
if (settle.current) clearTimeout(settle.current);
|
|
16574
|
+
settle.current = null;
|
|
16575
|
+
if (via === "pointer") {
|
|
16576
|
+
void apply(value);
|
|
16577
|
+
return;
|
|
16578
|
+
}
|
|
16579
|
+
setPending(value);
|
|
16580
|
+
settle.current = setTimeout(() => {
|
|
16581
|
+
settle.current = null;
|
|
16582
|
+
void apply(value);
|
|
16583
|
+
}, KEYBOARD_SETTLE_MS2);
|
|
16584
|
+
};
|
|
16585
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
16586
|
+
SettingsPage,
|
|
16587
|
+
{
|
|
16588
|
+
title: "N\xFAmero de salida",
|
|
16589
|
+
intro: "El n\xFAmero que ve la persona a la que llamas. Es el mismo en todos tus dispositivos y tambi\xE9n puedes cambiarlo desde el teclado.",
|
|
16590
|
+
onBack,
|
|
16591
|
+
children: [
|
|
16592
|
+
change.message ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "cv-settings-notice", role: "status", "data-kind": change.isError ? "error" : "info", ref: notice, children: change.message }) : null,
|
|
16593
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16594
|
+
ChoiceList,
|
|
16595
|
+
{
|
|
16596
|
+
label: "N\xFAmero de salida",
|
|
16597
|
+
choices: choicesFor2(settings, formatIntl2, change.busy ? pending : void 0),
|
|
16598
|
+
value: pending ?? currentValue2(settings),
|
|
16599
|
+
onSelect,
|
|
16600
|
+
disabled: change.locked
|
|
16601
|
+
}
|
|
16602
|
+
)
|
|
16603
|
+
]
|
|
16604
|
+
}
|
|
16605
|
+
);
|
|
16606
|
+
}
|
|
16607
|
+
function currentValue2(settings) {
|
|
16608
|
+
return settings.mine && settings.numbers.some((n) => n.id === settings.mine) ? settings.mine : ORG_DEFAULT2;
|
|
16609
|
+
}
|
|
16610
|
+
function choicesFor2(settings, formatIntl2, applying) {
|
|
16611
|
+
const orgDefault = settings.numbers.find((n) => n.id === settings.orgDefault);
|
|
16612
|
+
const spinner = (value) => applying === value ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "cv-choice-applying", children: [
|
|
16613
|
+
/* @__PURE__ */ jsxRuntime.jsx(LoaderCircle, { size: 15, className: "cv-choice-applying-icon", "aria-hidden": true }),
|
|
16614
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cv-sr-only", children: "Aplicando" })
|
|
16615
|
+
] }) : void 0;
|
|
16616
|
+
return [
|
|
16617
|
+
{
|
|
16618
|
+
value: ORG_DEFAULT2,
|
|
16619
|
+
label: "Por defecto de la organizaci\xF3n",
|
|
16620
|
+
sub: orgDefault ? numberName(orgDefault, formatIntl2) : "Sin n\xFAmero por defecto",
|
|
16621
|
+
trailing: spinner(ORG_DEFAULT2)
|
|
16622
|
+
},
|
|
16623
|
+
...settings.numbers.map((n) => {
|
|
16624
|
+
const kind = KIND_TAG[n.kind];
|
|
16625
|
+
return {
|
|
16626
|
+
value: n.id,
|
|
16627
|
+
label: numberName(n, formatIntl2),
|
|
16628
|
+
...n.label ? { sub: formatIntl2(`+${n.number}`) } : {},
|
|
16629
|
+
...kind ? { tag: kind.tag, hint: kind.hint } : {},
|
|
16630
|
+
trailing: spinner(n.id)
|
|
16631
|
+
};
|
|
16632
|
+
})
|
|
16633
|
+
];
|
|
16634
|
+
}
|
|
16635
|
+
function outboundSummary(settings, formatIntl2) {
|
|
16636
|
+
const effective = effectiveNumber(settings);
|
|
16637
|
+
const name = numberName(effective, formatIntl2);
|
|
16638
|
+
return settings.mine || !effective ? name : `${name} \xB7 organizaci\xF3n`;
|
|
16639
|
+
}
|
|
15347
16640
|
function SettingsTab() {
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
16641
|
+
const softphone = useSoftphone();
|
|
16642
|
+
const devices = useDevices();
|
|
16643
|
+
const store = ringtoneStore(softphone);
|
|
16644
|
+
const ringtone = useRingtoneSettings(store);
|
|
16645
|
+
const outStore = outboundNumberStore(softphone);
|
|
16646
|
+
const outbound = useOutboundNumbers(outStore).settings;
|
|
16647
|
+
const formatIntl2 = useIntlFormatter();
|
|
16648
|
+
const [page, setPage] = React.useState(null);
|
|
16649
|
+
const list = React.useRef(null);
|
|
16650
|
+
const rows = React.useRef({});
|
|
16651
|
+
const returnTo = React.useRef(null);
|
|
16652
|
+
React.useEffect(() => {
|
|
16653
|
+
void softphone.audio.refresh();
|
|
16654
|
+
}, [softphone]);
|
|
16655
|
+
React.useEffect(() => {
|
|
16656
|
+
list.current?.toggleAttribute("inert", page !== null);
|
|
16657
|
+
if (page === null && returnTo.current) {
|
|
16658
|
+
rows.current[returnTo.current]?.focus();
|
|
16659
|
+
returnTo.current = null;
|
|
16660
|
+
}
|
|
16661
|
+
}, [page]);
|
|
16662
|
+
const open = (p) => () => {
|
|
16663
|
+
returnTo.current = p;
|
|
16664
|
+
setPage(p);
|
|
16665
|
+
};
|
|
16666
|
+
const back = () => setPage(null);
|
|
16667
|
+
const rowRef = (p) => (el) => {
|
|
16668
|
+
rows.current[p] = el;
|
|
16669
|
+
};
|
|
16670
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-view cv-settings", children: [
|
|
16671
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "cv-settings-body", ref: list, children: [
|
|
16672
|
+
store ? /* @__PURE__ */ jsxRuntime.jsx(SettingsSection, { title: "Llamadas entrantes", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
16673
|
+
SettingsNavRow,
|
|
16674
|
+
{
|
|
16675
|
+
icon: Music,
|
|
16676
|
+
title: "Tono de llamada",
|
|
16677
|
+
value: ringtoneSummary(ringtone.settings, ringtone.failed),
|
|
16678
|
+
onOpen: open("tone"),
|
|
16679
|
+
rowRef: rowRef("tone")
|
|
16680
|
+
}
|
|
16681
|
+
) }) : null,
|
|
16682
|
+
outStore && outbound && outbound.numbers.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(SettingsSection, { title: "Llamadas salientes", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
16683
|
+
SettingsNavRow,
|
|
16684
|
+
{
|
|
16685
|
+
icon: PhoneOutgoing,
|
|
16686
|
+
title: "N\xFAmero de salida",
|
|
16687
|
+
value: outboundSummary(outbound, formatIntl2),
|
|
16688
|
+
onOpen: open("outbound"),
|
|
16689
|
+
rowRef: rowRef("outbound")
|
|
16690
|
+
}
|
|
16691
|
+
) }) : null,
|
|
16692
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SettingsSection, { title: "Dispositivos", children: [
|
|
16693
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16694
|
+
SettingsNavRow,
|
|
16695
|
+
{
|
|
16696
|
+
icon: Mic,
|
|
16697
|
+
title: "Micr\xF3fono",
|
|
16698
|
+
value: deviceSummary("input", devices.inputs, devices.currentInputId),
|
|
16699
|
+
onOpen: open("input"),
|
|
16700
|
+
rowRef: rowRef("input")
|
|
16701
|
+
}
|
|
16702
|
+
),
|
|
16703
|
+
devices.canSelectOutput ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
16704
|
+
SettingsNavRow,
|
|
16705
|
+
{
|
|
16706
|
+
icon: Headphones,
|
|
16707
|
+
title: "Altavoces",
|
|
16708
|
+
value: deviceSummary("output", devices.outputs, devices.currentOutputId),
|
|
16709
|
+
onOpen: open("output"),
|
|
16710
|
+
rowRef: rowRef("output")
|
|
16711
|
+
}
|
|
16712
|
+
) : (
|
|
16713
|
+
// Safari and most of Firefox have no `setSinkId`: offering a page whose choice silently does
|
|
16714
|
+
// nothing is worse than saying where the choice really lives.
|
|
16715
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
16716
|
+
SettingsNavRow,
|
|
16717
|
+
{
|
|
16718
|
+
icon: Headphones,
|
|
16719
|
+
title: "Altavoces",
|
|
16720
|
+
value: "Tu navegador no permite elegirlos desde aqu\xED \u2014 c\xE1mbialos en los ajustes del sistema."
|
|
16721
|
+
}
|
|
16722
|
+
)
|
|
16723
|
+
),
|
|
16724
|
+
devices.canSelectRingtoneOutput ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
16725
|
+
SettingsNavRow,
|
|
16726
|
+
{
|
|
16727
|
+
icon: BellRing,
|
|
16728
|
+
title: "Salida del tono",
|
|
16729
|
+
value: deviceSummary("ringtone", devices.outputs, devices.currentRingtoneOutputId),
|
|
16730
|
+
onOpen: open("ringtone"),
|
|
16731
|
+
rowRef: rowRef("ringtone")
|
|
16732
|
+
}
|
|
16733
|
+
) : null
|
|
16734
|
+
] })
|
|
16735
|
+
] }),
|
|
16736
|
+
page === "tone" && store ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
16737
|
+
RingtonePage,
|
|
16738
|
+
{
|
|
16739
|
+
store,
|
|
16740
|
+
settings: ringtone.settings,
|
|
16741
|
+
failed: ringtone.failed,
|
|
16742
|
+
onRetry: ringtone.retry,
|
|
16743
|
+
onBack: back
|
|
16744
|
+
}
|
|
16745
|
+
) : page === "outbound" && outStore && outbound ? /* @__PURE__ */ jsxRuntime.jsx(OutboundNumberPage, { store: outStore, settings: outbound, onBack: back }) : page !== null && page !== "tone" && page !== "outbound" ? /* @__PURE__ */ jsxRuntime.jsx(DevicePage, { kind: page, onBack: back }) : null
|
|
16746
|
+
] });
|
|
15352
16747
|
}
|
|
15353
16748
|
|
|
15354
16749
|
// src/core/env.ts
|
|
@@ -15587,14 +16982,29 @@ function useCallAudio(enabled) {
|
|
|
15587
16982
|
const offDeviceChange = softphone.on("deviceChange" /* DeviceChange */, () => {
|
|
15588
16983
|
void remote.current?.setOutputDevice(softphone.audio.currentOutputId);
|
|
15589
16984
|
});
|
|
16985
|
+
const ringtones = ringtoneStore(softphone);
|
|
16986
|
+
ringtones?.wantAudio(true);
|
|
16987
|
+
const refreshRingtone = () => {
|
|
16988
|
+
if (softphone.isLeader) void ringtones?.refresh();
|
|
16989
|
+
};
|
|
16990
|
+
refreshRingtone();
|
|
16991
|
+
const offLeader = softphone.on("leaderChanged" /* LeaderChanged */, ({ isLeader }) => {
|
|
16992
|
+
if (isLeader) refreshRingtone();
|
|
16993
|
+
});
|
|
15590
16994
|
const stopTone = () => {
|
|
15591
16995
|
tone.current?.stop();
|
|
15592
16996
|
tone.current = null;
|
|
15593
16997
|
};
|
|
15594
16998
|
const setTone = (next) => {
|
|
15595
16999
|
stopTone();
|
|
15596
|
-
if (next === "ringtone")
|
|
15597
|
-
|
|
17000
|
+
if (next === "ringtone") {
|
|
17001
|
+
const audio = softphone.audio;
|
|
17002
|
+
tone.current = playRingtone({
|
|
17003
|
+
buffer: ringtones?.readyBuffer() ?? null,
|
|
17004
|
+
loop: ringtones?.readyLoop() ?? null,
|
|
17005
|
+
sinkId: audio.canSelectRingtoneOutput ? audio.currentRingtoneOutputId : null
|
|
17006
|
+
});
|
|
17007
|
+
} else if (next === "ringback") tone.current = playRingback();
|
|
15598
17008
|
};
|
|
15599
17009
|
const perCall = [];
|
|
15600
17010
|
const followedCalls = /* @__PURE__ */ new Set();
|
|
@@ -15625,6 +17035,7 @@ function useCallAudio(enabled) {
|
|
|
15625
17035
|
stopTone();
|
|
15626
17036
|
remote.current?.detach();
|
|
15627
17037
|
if (wasConnected && (reason === "local" || reason === "remote")) playCallEndedTone();
|
|
17038
|
+
refreshRingtone();
|
|
15628
17039
|
})
|
|
15629
17040
|
);
|
|
15630
17041
|
};
|
|
@@ -15642,6 +17053,8 @@ function useCallAudio(enabled) {
|
|
|
15642
17053
|
offIncoming();
|
|
15643
17054
|
offActive();
|
|
15644
17055
|
offDeviceChange();
|
|
17056
|
+
offLeader();
|
|
17057
|
+
ringtones?.wantAudio(false);
|
|
15645
17058
|
for (const off of perCall) off();
|
|
15646
17059
|
stopTone();
|
|
15647
17060
|
remote.current?.destroy();
|
|
@@ -15816,6 +17229,7 @@ lucide-react/dist/esm/createLucideIcon.js:
|
|
|
15816
17229
|
lucide-react/dist/esm/icons/arrow-left-right.js:
|
|
15817
17230
|
lucide-react/dist/esm/icons/arrow-left.js:
|
|
15818
17231
|
lucide-react/dist/esm/icons/arrow-right.js:
|
|
17232
|
+
lucide-react/dist/esm/icons/bell-ring.js:
|
|
15819
17233
|
lucide-react/dist/esm/icons/calendar-clock.js:
|
|
15820
17234
|
lucide-react/dist/esm/icons/check.js:
|
|
15821
17235
|
lucide-react/dist/esm/icons/chevron-down.js:
|
|
@@ -15831,6 +17245,7 @@ lucide-react/dist/esm/icons/loader-circle.js:
|
|
|
15831
17245
|
lucide-react/dist/esm/icons/mic-off.js:
|
|
15832
17246
|
lucide-react/dist/esm/icons/mic.js:
|
|
15833
17247
|
lucide-react/dist/esm/icons/monitor-smartphone.js:
|
|
17248
|
+
lucide-react/dist/esm/icons/music.js:
|
|
15834
17249
|
lucide-react/dist/esm/icons/pause.js:
|
|
15835
17250
|
lucide-react/dist/esm/icons/phone-incoming.js:
|
|
15836
17251
|
lucide-react/dist/esm/icons/phone-missed.js:
|
|
@@ -15844,6 +17259,7 @@ lucide-react/dist/esm/icons/timer.js:
|
|
|
15844
17259
|
lucide-react/dist/esm/icons/triangle-alert.js:
|
|
15845
17260
|
lucide-react/dist/esm/icons/user.js:
|
|
15846
17261
|
lucide-react/dist/esm/icons/users.js:
|
|
17262
|
+
lucide-react/dist/esm/icons/volume-2.js:
|
|
15847
17263
|
lucide-react/dist/esm/icons/x.js:
|
|
15848
17264
|
lucide-react/dist/esm/lucide-react.js:
|
|
15849
17265
|
(**
|
|
@@ -15860,5 +17276,6 @@ exports.Dialpad = Dialpad;
|
|
|
15860
17276
|
exports.HistoryTab = HistoryTab;
|
|
15861
17277
|
exports.MicPermissionGate = MicPermissionGate;
|
|
15862
17278
|
exports.SoftphoneWidget = SoftphoneWidget;
|
|
17279
|
+
exports.previewRingtone = previewRingtone;
|
|
15863
17280
|
//# sourceMappingURL=index.cjs.map
|
|
15864
17281
|
//# sourceMappingURL=index.cjs.map
|