@kwirthmagnify/kwirth-sender-timed 0.1.2 → 0.1.4
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/back.js +3 -36
- package/front.js +77 -110
- package/package.json +1 -1
package/back.js
CHANGED
|
@@ -115,20 +115,6 @@ function tzOffset(tz) {
|
|
|
115
115
|
return "";
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
function parseMinutes(hhmm) {
|
|
119
|
-
const [h, m] = hhmm.split(":").map(Number);
|
|
120
|
-
return (h ?? 0) * 60 + (m ?? 0);
|
|
121
|
-
}
|
|
122
|
-
function currentContext(timezone) {
|
|
123
|
-
const now = timezone ? new Date((/* @__PURE__ */ new Date()).toLocaleString("en-US", { timeZone: timezone })) : /* @__PURE__ */ new Date();
|
|
124
|
-
return { minutes: now.getHours() * 60 + now.getMinutes(), day: now.getDay() };
|
|
125
|
-
}
|
|
126
|
-
function matchesWindow(rule, minutes, day) {
|
|
127
|
-
if (rule.days && rule.days.length > 0 && !rule.days.includes(day)) return false;
|
|
128
|
-
const from = parseMinutes(rule.from);
|
|
129
|
-
const to = parseMinutes(rule.to);
|
|
130
|
-
return from <= to ? minutes >= from && minutes < to : minutes >= from || minutes < to;
|
|
131
|
-
}
|
|
132
118
|
var TimedSender = class {
|
|
133
119
|
constructor() {
|
|
134
120
|
this.id = "timed";
|
|
@@ -148,22 +134,7 @@ var TimedSender = class {
|
|
|
148
134
|
getConfigNames() {
|
|
149
135
|
return Array.from(this.configs.keys());
|
|
150
136
|
}
|
|
151
|
-
async send(
|
|
152
|
-
const config = this.configs.get(configName);
|
|
153
|
-
if (!config) throw new Error(`TimedSender: config '${configName}' not found`);
|
|
154
|
-
if (!this.senderAccess) throw new Error(`TimedSender: senderAccess not initialized`);
|
|
155
|
-
const { minutes, day } = currentContext(config.timezone);
|
|
156
|
-
for (const rule of config.rules) {
|
|
157
|
-
if (!matchesWindow(rule, minutes, day)) continue;
|
|
158
|
-
if (rule.action === "send" && rule.senderId && rule.configName) {
|
|
159
|
-
await this.senderAccess.send(rule.senderId, rule.configName, message);
|
|
160
|
-
}
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const defAction = config.defaultAction ?? "drop";
|
|
164
|
-
if (defAction === "send" && config.defaultSenderId && config.defaultConfigName) {
|
|
165
|
-
await this.senderAccess.send(config.defaultSenderId, config.defaultConfigName, message);
|
|
166
|
-
}
|
|
137
|
+
async send(_configName, _message) {
|
|
167
138
|
}
|
|
168
139
|
getConfigSchema() {
|
|
169
140
|
return [
|
|
@@ -173,16 +144,12 @@ var TimedSender = class {
|
|
|
173
144
|
return off ? `${tz} (${off})` : tz;
|
|
174
145
|
}) },
|
|
175
146
|
{ name: "rules", label: "Rules (JSON)", type: "json" },
|
|
176
|
-
{ name: "defaultAction", label: "Default action", type: "select", options: ["drop", "send"] }
|
|
177
|
-
{ name: "defaultSenderId", label: "Default sender ID" },
|
|
178
|
-
{ name: "defaultConfigName", label: "Default config name" }
|
|
147
|
+
{ name: "defaultAction", label: "Default action", type: "select", options: ["drop", "send"] }
|
|
179
148
|
];
|
|
180
149
|
}
|
|
181
|
-
async startSender(
|
|
182
|
-
this.senderAccess = senders;
|
|
150
|
+
async startSender(_senders) {
|
|
183
151
|
}
|
|
184
152
|
async stopSender() {
|
|
185
|
-
this.senderAccess = void 0;
|
|
186
153
|
}
|
|
187
154
|
};
|
|
188
155
|
var index_default = TimedSender;
|
package/front.js
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
var import_react = __toESM(require_react(), 1);
|
|
51
51
|
var import_material = __toESM(require_material(), 1);
|
|
52
52
|
var import_icons_material = __toESM(require_icons_material(), 1);
|
|
53
|
+
var import_icons_material2 = __toESM(require_icons_material(), 1);
|
|
53
54
|
function authGet(accessString) {
|
|
54
55
|
return { headers: { Authorization: accessString ? `Bearer ${accessString}` : "", "X-Kwirth-App": "true" } };
|
|
55
56
|
}
|
|
@@ -157,15 +158,10 @@
|
|
|
157
158
|
function emptyConfig() {
|
|
158
159
|
return { name: "", rules: [], defaultAction: "drop" };
|
|
159
160
|
}
|
|
160
|
-
var RuleRow = ({ rule,
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const days = rule.days ?? [];
|
|
165
|
-
const next = days.includes(day) ? days.filter((d) => d !== day) : [...days, day].sort();
|
|
166
|
-
onChange({ ...rule, days: next.length === 0 ? void 0 : next });
|
|
167
|
-
};
|
|
168
|
-
return /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { border: "1px solid", borderColor: "divider", borderRadius: 1, p: 1.5, mb: 1 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", useFlexGap: true }, /* @__PURE__ */ import_react.default.createElement(
|
|
161
|
+
var RuleRow = ({ rule, onChange, onDelete }) => {
|
|
162
|
+
const allDays = [0, 1, 2, 3, 4, 5, 6];
|
|
163
|
+
const selectedDays = rule.days ?? allDays;
|
|
164
|
+
return /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { borderBottom: 1, borderColor: "divider", px: 1, py: 0.75, display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 1, alignItems: "center", sx: { flex: 1 } }, /* @__PURE__ */ import_react.default.createElement(
|
|
169
165
|
import_material.TextField,
|
|
170
166
|
{
|
|
171
167
|
size: "small",
|
|
@@ -187,65 +183,34 @@
|
|
|
187
183
|
onChange: (e) => onChange({ ...rule, to: e.target.value }),
|
|
188
184
|
InputLabelProps: { shrink: true }
|
|
189
185
|
}
|
|
190
|
-
), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { minWidth:
|
|
186
|
+
), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { minWidth: 110, flexShrink: 0 } }, /* @__PURE__ */ import_react.default.createElement(import_material.InputLabel, null, "Action"), /* @__PURE__ */ import_react.default.createElement(
|
|
191
187
|
import_material.Select,
|
|
192
188
|
{
|
|
193
189
|
label: "Action",
|
|
194
190
|
value: rule.action,
|
|
195
|
-
onChange: (e) => {
|
|
196
|
-
const action = e.target.value;
|
|
197
|
-
onChange({ ...rule, action, senderId: action === "drop" ? void 0 : rule.senderId, configName: action === "drop" ? void 0 : rule.configName });
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "send" }, "send"),
|
|
201
|
-
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "drop" }, "drop")
|
|
202
|
-
)), rule.action === "send" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { minWidth: 130 } }, /* @__PURE__ */ import_react.default.createElement(import_material.InputLabel, null, "Sender"), /* @__PURE__ */ import_react.default.createElement(
|
|
203
|
-
import_material.Select,
|
|
204
|
-
{
|
|
205
|
-
label: "Sender",
|
|
206
|
-
value: rule.senderId ?? "",
|
|
207
|
-
onChange: (e) => onChange({ ...rule, senderId: e.target.value || void 0, configName: void 0 })
|
|
191
|
+
onChange: (e) => onChange({ ...rule, action: e.target.value })
|
|
208
192
|
},
|
|
209
|
-
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "" }, /* @__PURE__ */ import_react.default.createElement("
|
|
210
|
-
|
|
211
|
-
)), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: {
|
|
193
|
+
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "drop" }, /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { label: "drop", size: "small", color: "error", sx: { fontSize: 10, height: 18 } })),
|
|
194
|
+
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "send" }, /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { label: "send", size: "small", color: "success", sx: { fontSize: 10, height: 18 } }))
|
|
195
|
+
)), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { flex: 1, minWidth: 160 } }, /* @__PURE__ */ import_react.default.createElement(import_material.InputLabel, null, "Days"), /* @__PURE__ */ import_react.default.createElement(
|
|
212
196
|
import_material.Select,
|
|
213
197
|
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
import_material.Checkbox,
|
|
226
|
-
{
|
|
227
|
-
size: "small",
|
|
228
|
-
checked: !rule.days || rule.days.includes(i),
|
|
229
|
-
onChange: () => toggleDay(i),
|
|
230
|
-
sx: { p: 0.25 }
|
|
231
|
-
}
|
|
232
|
-
),
|
|
233
|
-
label: /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption" }, label),
|
|
234
|
-
sx: { mr: 0.5 }
|
|
235
|
-
}
|
|
236
|
-
)), rule.days && rule.days.length > 0 && /* @__PURE__ */ import_react.default.createElement(
|
|
237
|
-
import_material.Button,
|
|
238
|
-
{
|
|
239
|
-
size: "small",
|
|
240
|
-
sx: { fontSize: 10, p: 0.25, minWidth: 0 },
|
|
241
|
-
onClick: () => onChange({ ...rule, days: void 0 })
|
|
198
|
+
multiple: true,
|
|
199
|
+
label: "Days",
|
|
200
|
+
value: selectedDays,
|
|
201
|
+
onChange: (e) => {
|
|
202
|
+
const val = e.target.value;
|
|
203
|
+
onChange({ ...rule, days: val.length === 0 || val.length === 7 ? void 0 : [...val].sort((a, b) => a - b) });
|
|
204
|
+
},
|
|
205
|
+
renderValue: (sel) => {
|
|
206
|
+
const days = sel;
|
|
207
|
+
return days.length === 7 ? "All days" : days.map((d) => DAY_LABELS[d]).join(", ");
|
|
208
|
+
}
|
|
242
209
|
},
|
|
243
|
-
"
|
|
244
|
-
)));
|
|
210
|
+
DAY_LABELS.map((label, i) => /* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { key: i, value: i, sx: { py: 0.25 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Checkbox, { checked: selectedDays.includes(i), size: "small", sx: { p: 0.25 } }), /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2" }, label)))
|
|
211
|
+
))), /* @__PURE__ */ import_react.default.createElement(import_material.IconButton, { size: "small", color: "error", onClick: onDelete, sx: { alignSelf: "center", flexShrink: 0 } }, /* @__PURE__ */ import_react.default.createElement(import_icons_material2.Delete, { fontSize: "small" })));
|
|
245
212
|
};
|
|
246
|
-
var ConfigForm = ({ config,
|
|
247
|
-
const defSender = senders.find((s) => s.id === config.defaultSenderId);
|
|
248
|
-
const defConfigNames = defSender?.configNames ?? [];
|
|
213
|
+
var ConfigForm = ({ config, onChange }) => {
|
|
249
214
|
const updateRule = (i, rule) => {
|
|
250
215
|
const rules = [...config.rules];
|
|
251
216
|
rules[i] = rule;
|
|
@@ -257,16 +222,16 @@
|
|
|
257
222
|
const addRule = () => {
|
|
258
223
|
onChange({ ...config, rules: [...config.rules, emptyRule()] });
|
|
259
224
|
};
|
|
260
|
-
return /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { spacing: 2 }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 1
|
|
225
|
+
return /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { spacing: 2 }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 1, alignItems: "center" }, /* @__PURE__ */ import_react.default.createElement(
|
|
261
226
|
import_material.TextField,
|
|
262
227
|
{
|
|
263
228
|
size: "small",
|
|
264
229
|
label: "Name *",
|
|
265
230
|
value: config.name,
|
|
266
231
|
onChange: (e) => onChange({ ...config, name: e.target.value }),
|
|
267
|
-
sx: { flex:
|
|
232
|
+
sx: { flex: 2 }
|
|
268
233
|
}
|
|
269
|
-
), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: {
|
|
234
|
+
), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { flex: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.InputLabel, null, "Timezone"), /* @__PURE__ */ import_react.default.createElement(
|
|
270
235
|
import_material.Select,
|
|
271
236
|
{
|
|
272
237
|
label: "Timezone",
|
|
@@ -278,80 +243,71 @@
|
|
|
278
243
|
const off = tzOffset(tz);
|
|
279
244
|
return /* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { key: tz, value: tz }, tz, off ? ` (${off})` : "");
|
|
280
245
|
})
|
|
281
|
-
))
|
|
246
|
+
)), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { minWidth: 110, flexShrink: 0 } }, /* @__PURE__ */ import_react.default.createElement(import_material.InputLabel, null, "Default"), /* @__PURE__ */ import_react.default.createElement(
|
|
247
|
+
import_material.Select,
|
|
248
|
+
{
|
|
249
|
+
label: "Default",
|
|
250
|
+
value: config.defaultAction ?? "drop",
|
|
251
|
+
onChange: (e) => onChange({ ...config, defaultAction: e.target.value })
|
|
252
|
+
},
|
|
253
|
+
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "drop" }, /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { label: "drop", size: "small", color: "error", sx: { fontSize: 10, height: 18 } })),
|
|
254
|
+
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "send" }, /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { label: "send", size: "small", color: "success", sx: { fontSize: 10, height: 18 } }))
|
|
255
|
+
))), /* @__PURE__ */ import_react.default.createElement(
|
|
256
|
+
import_material.TextField,
|
|
257
|
+
{
|
|
258
|
+
size: "small",
|
|
259
|
+
label: "Description",
|
|
260
|
+
value: config.description ?? "",
|
|
261
|
+
onChange: (e) => onChange({ ...config, description: e.target.value || void 0 }),
|
|
262
|
+
fullWidth: true,
|
|
263
|
+
multiline: true,
|
|
264
|
+
maxRows: 2
|
|
265
|
+
}
|
|
266
|
+
), /* @__PURE__ */ import_react.default.createElement(import_material.Divider, null, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption" }, "Rules (evaluated in order \u2014 first match wins)")), config.rules.length === 0 && /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2", color: "text.secondary" }, "No rules yet. Add one below."), config.rules.map((rule, i) => /* @__PURE__ */ import_react.default.createElement(
|
|
282
267
|
RuleRow,
|
|
283
268
|
{
|
|
284
269
|
key: i,
|
|
285
270
|
rule,
|
|
286
|
-
senders,
|
|
287
271
|
onChange: (r) => updateRule(i, r),
|
|
288
272
|
onDelete: () => deleteRule(i)
|
|
289
273
|
}
|
|
290
|
-
)), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", startIcon: /* @__PURE__ */ import_react.default.createElement(
|
|
291
|
-
import_material.Select,
|
|
292
|
-
{
|
|
293
|
-
label: "Default action",
|
|
294
|
-
value: config.defaultAction ?? "drop",
|
|
295
|
-
onChange: (e) => {
|
|
296
|
-
const action = e.target.value;
|
|
297
|
-
onChange({ ...config, defaultAction: action, defaultSenderId: action === "drop" ? void 0 : config.defaultSenderId, defaultConfigName: action === "drop" ? void 0 : config.defaultConfigName });
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "drop" }, "drop"),
|
|
301
|
-
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "send" }, "send")
|
|
302
|
-
)), config.defaultAction === "send" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { minWidth: 160 } }, /* @__PURE__ */ import_react.default.createElement(import_material.InputLabel, null, "Default sender"), /* @__PURE__ */ import_react.default.createElement(
|
|
303
|
-
import_material.Select,
|
|
304
|
-
{
|
|
305
|
-
label: "Default sender",
|
|
306
|
-
value: config.defaultSenderId ?? "",
|
|
307
|
-
onChange: (e) => onChange({ ...config, defaultSenderId: e.target.value || void 0, defaultConfigName: void 0 })
|
|
308
|
-
},
|
|
309
|
-
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "" }, /* @__PURE__ */ import_react.default.createElement("em", null, "\u2014")),
|
|
310
|
-
senders.map((s) => /* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { key: s.id, value: s.id }, s.displayName ?? s.id))
|
|
311
|
-
)), /* @__PURE__ */ import_react.default.createElement(import_material.FormControl, { size: "small", sx: { minWidth: 160 }, disabled: !config.defaultSenderId || defConfigNames.length === 0 }, /* @__PURE__ */ import_react.default.createElement(import_material.InputLabel, null, "Default config"), /* @__PURE__ */ import_react.default.createElement(
|
|
312
|
-
import_material.Select,
|
|
313
|
-
{
|
|
314
|
-
label: "Default config",
|
|
315
|
-
value: config.defaultConfigName ?? "",
|
|
316
|
-
onChange: (e) => onChange({ ...config, defaultConfigName: e.target.value || void 0 })
|
|
317
|
-
},
|
|
318
|
-
/* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { value: "" }, /* @__PURE__ */ import_react.default.createElement("em", null, "\u2014")),
|
|
319
|
-
defConfigNames.map((c) => /* @__PURE__ */ import_react.default.createElement(import_material.MenuItem, { key: c, value: c }, c))
|
|
320
|
-
)))));
|
|
274
|
+
)), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", startIcon: /* @__PURE__ */ import_react.default.createElement(import_icons_material2.Add, null), onClick: addRule, sx: { alignSelf: "flex-start" } }, "Add rule"));
|
|
321
275
|
};
|
|
322
276
|
var TimedConfigDialog = ({ onClose, backendUrl, accessString }) => {
|
|
323
277
|
const [configs, setConfigs] = (0, import_react.useState)([]);
|
|
324
|
-
const [senders, setSenders] = (0, import_react.useState)([]);
|
|
325
278
|
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
326
279
|
const [saving, setSaving] = (0, import_react.useState)(false);
|
|
327
280
|
const [deletingName, setDeletingName] = (0, import_react.useState)();
|
|
328
281
|
const [showForm, setShowForm] = (0, import_react.useState)(false);
|
|
329
282
|
const [formConfig, setFormConfig] = (0, import_react.useState)(emptyConfig());
|
|
283
|
+
const [originalFormName, setOriginalFormName] = (0, import_react.useState)();
|
|
330
284
|
const [error, setError] = (0, import_react.useState)();
|
|
331
285
|
(0, import_react.useEffect)(() => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}),
|
|
335
|
-
fetch(`${backendUrl}/senders`, authGet(accessString)).then((r) => r.json()).then(setSenders).catch(() => {
|
|
336
|
-
})
|
|
337
|
-
]).finally(() => setLoading(false));
|
|
286
|
+
fetch(`${backendUrl}/senders/timed/configs`, authGet(accessString)).then((r) => r.json()).then((data) => setConfigs(Array.isArray(data) ? data : data.configs ?? [])).catch(() => {
|
|
287
|
+
}).finally(() => setLoading(false));
|
|
338
288
|
}, []);
|
|
339
289
|
const saveConfig = async () => {
|
|
340
|
-
|
|
290
|
+
const trimmed = formConfig.name.trim();
|
|
291
|
+
if (!trimmed) {
|
|
341
292
|
setError("Name is required");
|
|
342
293
|
return;
|
|
343
294
|
}
|
|
344
295
|
setSaving(true);
|
|
345
296
|
setError(void 0);
|
|
346
297
|
try {
|
|
347
|
-
const
|
|
298
|
+
const payload = { ...formConfig, name: trimmed };
|
|
299
|
+
const res = await fetch(`${backendUrl}/senders/timed/configs`, authPost(accessString, JSON.stringify(payload)));
|
|
348
300
|
if (!res.ok) throw new Error((await res.json()).error ?? `HTTP ${res.status}`);
|
|
301
|
+
if (originalFormName && originalFormName !== trimmed) {
|
|
302
|
+
await fetch(`${backendUrl}/senders/timed/configs/${encodeURIComponent(originalFormName)}`, authDelete(accessString));
|
|
303
|
+
}
|
|
349
304
|
setConfigs((prev) => {
|
|
350
|
-
const
|
|
351
|
-
|
|
305
|
+
const withoutOld = originalFormName && originalFormName !== trimmed ? prev.filter((c) => c.name !== originalFormName) : prev;
|
|
306
|
+
const idx = withoutOld.findIndex((c) => c.name === trimmed);
|
|
307
|
+
return idx >= 0 ? withoutOld.map((c, i) => i === idx ? payload : c) : [...withoutOld, payload];
|
|
352
308
|
});
|
|
353
|
-
|
|
354
|
-
setFormConfig(
|
|
309
|
+
setOriginalFormName(trimmed);
|
|
310
|
+
setFormConfig(payload);
|
|
355
311
|
} catch (err) {
|
|
356
312
|
setError(`Save failed: ${err}`);
|
|
357
313
|
} finally {
|
|
@@ -372,11 +328,19 @@
|
|
|
372
328
|
};
|
|
373
329
|
const startAdd = () => {
|
|
374
330
|
setFormConfig(emptyConfig());
|
|
331
|
+
setOriginalFormName(void 0);
|
|
375
332
|
setError(void 0);
|
|
376
333
|
setShowForm(true);
|
|
377
334
|
};
|
|
378
335
|
const startEdit = (config) => {
|
|
379
336
|
setFormConfig({ ...config });
|
|
337
|
+
setOriginalFormName(config.name);
|
|
338
|
+
setError(void 0);
|
|
339
|
+
setShowForm(true);
|
|
340
|
+
};
|
|
341
|
+
const startClone = (config) => {
|
|
342
|
+
setFormConfig({ ...config, name: `${config.name} (copy)` });
|
|
343
|
+
setOriginalFormName(void 0);
|
|
380
344
|
setError(void 0);
|
|
381
345
|
setShowForm(true);
|
|
382
346
|
};
|
|
@@ -387,10 +351,13 @@
|
|
|
387
351
|
parts.push(`default: ${c.defaultAction ?? "drop"}`);
|
|
388
352
|
return parts.join(" \xB7 ");
|
|
389
353
|
};
|
|
390
|
-
return /* @__PURE__ */ import_react.default.createElement(import_material.Dialog, { open: true, maxWidth: false, sx: { "& .MuiDialog-paper": { width: "
|
|
354
|
+
return /* @__PURE__ */ import_react.default.createElement(import_material.Dialog, { open: true, maxWidth: false, sx: { "& .MuiDialog-paper": { width: "900px", height: "640px" } } }, /* @__PURE__ */ import_react.default.createElement(import_material.DialogTitle, null, "Configure: Timed Sender"), /* @__PURE__ */ import_react.default.createElement(import_material.DialogContent, { sx: { display: "flex", gap: 2, p: "16px !important", overflow: "hidden", height: "100%" } }, /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { width: 190, display: "flex", flexDirection: "column", gap: 1, flexShrink: 0 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary", fontWeight: "bold" }, "Configs"), /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { flex: 1, border: 1, borderColor: "divider", borderRadius: 1, overflowY: "auto" } }, loading ? /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { p: 1 } }, /* @__PURE__ */ import_react.default.createElement(import_material.CircularProgress, { size: 16 })) : configs.length === 0 ? /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.disabled", sx: { p: 1, display: "block" } }, "No configs yet.") : configs.map((cfg) => /* @__PURE__ */ import_react.default.createElement(import_material.Box, { key: cfg.name, sx: { display: "flex", alignItems: "center", px: 1, py: 0.5, borderBottom: 1, borderColor: "divider", borderLeft: formConfig.name === cfg.name && showForm ? 3 : 0, borderLeftColor: "primary.main", bgcolor: formConfig.name === cfg.name && showForm ? "action.selected" : "transparent" } }, /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { flex: 1, minWidth: 0, overflow: "hidden", cursor: "pointer" }, onClick: () => startEdit(cfg) }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2", fontWeight: "bold", noWrap: true }, cfg.name), /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary", noWrap: true, display: "block" }, configSummary(cfg))), /* @__PURE__ */ import_react.default.createElement(import_material.Tooltip, { title: "Delete" }, /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement(import_material.IconButton, { size: "small", color: "error", disabled: deletingName === cfg.name, onClick: () => deleteConfig(cfg.name) }, deletingName === cfg.name ? /* @__PURE__ */ import_react.default.createElement(import_material.CircularProgress, { size: 12 }) : /* @__PURE__ */ import_react.default.createElement(import_icons_material2.Delete, { sx: { fontSize: 14 } }))))))), /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 0.5 }, /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", startIcon: /* @__PURE__ */ import_react.default.createElement(import_icons_material2.Add, null), onClick: startAdd, sx: { flex: 1 } }, "New"), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", startIcon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.ContentCopy, null), onClick: () => {
|
|
355
|
+
const c = configs.find((x) => x.name === formConfig.name && showForm);
|
|
356
|
+
if (c) startClone(c);
|
|
357
|
+
}, disabled: !showForm || !originalFormName, sx: { flex: 1 } }, "Clone"))), /* @__PURE__ */ import_react.default.createElement(import_material.Divider, { orientation: "vertical", flexItem: true }), /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { flex: 1, display: "flex", flexDirection: "column", gap: 1.5, minWidth: 0, overflow: "hidden" } }, showForm ? /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary", fontWeight: "bold" }, originalFormName ? `Editing: ${originalFormName}` : "New config"), /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { flex: 1, overflowY: "auto", pt: 1 } }, /* @__PURE__ */ import_react.default.createElement(ConfigForm, { config: formConfig, onChange: setFormConfig })), /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", justifyContent: "flex-end", alignItems: "center", spacing: 1 }, error && /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "error", sx: { flex: 1 } }, error), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", onClick: () => {
|
|
391
358
|
setShowForm(false);
|
|
392
359
|
setError(void 0);
|
|
393
|
-
} }, "Cancel"), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", variant: "contained", disabled: saving || !formConfig.name.trim(), onClick: saveConfig }, saving ? /* @__PURE__ */ import_react.default.createElement(import_material.CircularProgress, { size: 14 }) : "
|
|
360
|
+
} }, "Cancel"), /* @__PURE__ */ import_react.default.createElement(import_material.Button, { size: "small", variant: "contained", disabled: saving || !formConfig.name.trim(), onClick: saveConfig }, saving ? /* @__PURE__ */ import_react.default.createElement(import_material.CircularProgress, { size: 14 }) : originalFormName ? "Update" : "Add"))) : /* @__PURE__ */ import_react.default.createElement(import_material.Box, { sx: { m: "auto", color: "text.disabled" } }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "body2" }, "Select a config to edit or click New.")))), /* @__PURE__ */ import_react.default.createElement(import_material.DialogActions, { sx: { px: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Button, { onClick: onClose }, "Close")));
|
|
394
361
|
};
|
|
395
362
|
var TimedConfigDialog_default = TimedConfigDialog;
|
|
396
363
|
|
package/package.json
CHANGED