✨ feat: Add automatic download rule settings.
This commit is contained in:
parent
88f65da57b
commit
0c5016e3f3
11 changed files with 716 additions and 67 deletions
|
|
@ -11,6 +11,7 @@ import io.vertx.core.Promise;
|
|||
import io.vertx.core.json.Json;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import org.drinkless.tdlib.TdApi;
|
||||
import org.jooq.lambda.tuple.Tuple2;
|
||||
import telegram.files.repository.FileRecord;
|
||||
import telegram.files.repository.SettingAutoRecords;
|
||||
import telegram.files.repository.SettingKey;
|
||||
|
|
@ -34,7 +35,7 @@ public class AutoDownloadVerticle extends AbstractVerticle {
|
|||
|
||||
private static final int DOWNLOAD_INTERVAL = 10 * 1000;
|
||||
|
||||
private static final List<String> FILE_TYPE_ORDER = List.of("photo", "video", "audio", "file");
|
||||
private static final List<String> DEFAULT_FILE_TYPE_ORDER = List.of("photo", "video", "audio", "file");
|
||||
|
||||
// telegramId -> messages
|
||||
private final Map<Long, LinkedList<TdApi.Message>> waitingDownloadMessages = new ConcurrentHashMap<>();
|
||||
|
|
@ -137,11 +138,11 @@ public class AutoDownloadVerticle extends AbstractVerticle {
|
|||
log.debug("Scan history end! TelegramId: %d ChatId: %d".formatted(auto.telegramId, auto.chatId));
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(auto.nextFileType)) {
|
||||
auto.nextFileType = FILE_TYPE_ORDER.getFirst();
|
||||
}
|
||||
Tuple2<String, List<String>> rule = handleRule(auto);
|
||||
|
||||
TelegramVerticle telegramVerticle = this.getTelegramVerticle(auto.telegramId);
|
||||
TdApi.SearchChatMessages searchChatMessages = new TdApi.SearchChatMessages();
|
||||
searchChatMessages.query = rule.v1;
|
||||
searchChatMessages.chatId = auto.chatId;
|
||||
searchChatMessages.fromMessageId = auto.nextFromMessageId;
|
||||
searchChatMessages.limit = Math.min(MAX_WAITING_LENGTH, 100);
|
||||
|
|
@ -153,10 +154,11 @@ public class AutoDownloadVerticle extends AbstractVerticle {
|
|||
return;
|
||||
}
|
||||
if (foundChatMessages.messages.length == 0) {
|
||||
int nextTypeIndex = FILE_TYPE_ORDER.indexOf(auto.nextFileType) + 1;
|
||||
if (nextTypeIndex < FILE_TYPE_ORDER.size()) {
|
||||
List<String> fileTypes = rule.v2;
|
||||
int nextTypeIndex = fileTypes.indexOf(auto.nextFileType) + 1;
|
||||
if (nextTypeIndex < fileTypes.size()) {
|
||||
String originalType = auto.nextFileType;
|
||||
auto.nextFileType = FILE_TYPE_ORDER.get(nextTypeIndex);
|
||||
auto.nextFileType = fileTypes.get(nextTypeIndex);
|
||||
auto.nextFromMessageId = 0;
|
||||
log.debug("%s No more %s files found! Switch to %s".formatted(auto.uniqueKey(), originalType, auto.nextFileType));
|
||||
addHistoryMessage(auto, currentTimeMillis);
|
||||
|
|
@ -181,6 +183,25 @@ public class AutoDownloadVerticle extends AbstractVerticle {
|
|||
}
|
||||
}
|
||||
|
||||
private Tuple2<String, List<String>> handleRule(SettingAutoRecords.Item auto) {
|
||||
SettingAutoRecords.Rule rule = auto.rule;
|
||||
String query = null;
|
||||
List<String> fileTypes = DEFAULT_FILE_TYPE_ORDER;
|
||||
if (rule != null) {
|
||||
if (StrUtil.isNotBlank(rule.query)) {
|
||||
query = rule.query;
|
||||
}
|
||||
if (CollUtil.isNotEmpty(rule.fileTypes)) {
|
||||
fileTypes = rule.fileTypes;
|
||||
}
|
||||
}
|
||||
if (StrUtil.isBlank(auto.nextFileType)) {
|
||||
auto.nextFileType = fileTypes.getFirst();
|
||||
}
|
||||
|
||||
return new Tuple2<>(query, fileTypes);
|
||||
}
|
||||
|
||||
private boolean isExceedLimit(long telegramId) {
|
||||
List<TdApi.Message> waitingMessages = this.waitingDownloadMessages.get(telegramId);
|
||||
return getSurplusSize(telegramId) <= 0 || (waitingMessages != null && waitingMessages.size() > limit);
|
||||
|
|
|
|||
|
|
@ -605,8 +605,8 @@ public class HttpVerticle extends AbstractVerticle {
|
|||
ctx.fail(400);
|
||||
return;
|
||||
}
|
||||
|
||||
telegramVerticle.toggleAutoDownload(Convert.toLong(chatId))
|
||||
JsonObject params = ctx.body().asJsonObject();
|
||||
telegramVerticle.toggleAutoDownload(Convert.toLong(chatId), params)
|
||||
.onSuccess(r -> ctx.end())
|
||||
.onFailure(ctx::fail);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package telegram.files;
|
|||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateField;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
|
|
@ -439,7 +440,7 @@ public class TelegramVerticle extends AbstractVerticle {
|
|||
.mapEmpty();
|
||||
}
|
||||
|
||||
public Future<Void> toggleAutoDownload(Long chatId) {
|
||||
public Future<Void> toggleAutoDownload(Long chatId, JsonObject params) {
|
||||
return DataVerticle.settingRepository.<SettingAutoRecords>getByKey(SettingKey.autoDownload)
|
||||
.compose(settingAutoRecords -> {
|
||||
if (settingAutoRecords == null) {
|
||||
|
|
@ -448,7 +449,14 @@ public class TelegramVerticle extends AbstractVerticle {
|
|||
if (settingAutoRecords.exists(this.telegramRecord.id(), chatId)) {
|
||||
settingAutoRecords.remove(this.telegramRecord.id(), chatId);
|
||||
} else {
|
||||
settingAutoRecords.add(this.telegramRecord.id(), chatId);
|
||||
SettingAutoRecords.Rule rule = null;
|
||||
if (params != null && params.containsKey("rule")) {
|
||||
rule = params.getJsonObject("rule").mapTo(SettingAutoRecords.Rule.class);
|
||||
if (StrUtil.isBlank(rule.query) && CollUtil.isEmpty(rule.fileTypes)) {
|
||||
rule = null;
|
||||
}
|
||||
}
|
||||
settingAutoRecords.add(this.telegramRecord.id(), chatId, rule);
|
||||
}
|
||||
return DataVerticle.settingRepository.createOrUpdate(SettingKey.autoDownload.name(), Json.encode(settingAutoRecords));
|
||||
})
|
||||
|
|
@ -824,19 +832,22 @@ public class TelegramVerticle extends AbstractVerticle {
|
|||
|
||||
private Future<JsonArray> convertChat(List<TdApi.Chat> chats) {
|
||||
return DataVerticle.settingRepository.<SettingAutoRecords>getByKey(SettingKey.autoDownload)
|
||||
.map(settingAutoRecords -> settingAutoRecords == null ? Collections.emptySet()
|
||||
: settingAutoRecords.getChatIds(this.telegramRecord.id()))
|
||||
.map(enableAutoChatIds -> new JsonArray(chats.stream()
|
||||
.map(chat -> new JsonObject()
|
||||
.put("id", Convert.toStr(chat.id))
|
||||
.put("name", chat.id == this.telegramRecord.id() ? "Saved Messages" : chat.title)
|
||||
.put("type", TdApiHelp.getChatType(chat.type))
|
||||
.put("avatar", Base64.encode((byte[]) BeanUtil.getProperty(chat, "photo.minithumbnail.data")))
|
||||
.put("unreadCount", chat.unreadCount)
|
||||
.put("lastMessage", "")
|
||||
.put("lastMessageTime", "")
|
||||
.put("autoEnabled", enableAutoChatIds.contains(chat.id))
|
||||
)
|
||||
.map(settingAutoRecords -> settingAutoRecords == null ? new HashMap<Long, SettingAutoRecords.Item>()
|
||||
: settingAutoRecords.getItems(this.telegramRecord.id()))
|
||||
.map(enableAutoChats -> new JsonArray(chats.stream()
|
||||
.map(chat -> {
|
||||
SettingAutoRecords.Item autoItem = enableAutoChats.get(chat.id);
|
||||
return new JsonObject()
|
||||
.put("id", Convert.toStr(chat.id))
|
||||
.put("name", chat.id == this.telegramRecord.id() ? "Saved Messages" : chat.title)
|
||||
.put("type", TdApiHelp.getChatType(chat.type))
|
||||
.put("avatar", Base64.encode((byte[]) BeanUtil.getProperty(chat, "photo.minithumbnail.data")))
|
||||
.put("unreadCount", chat.unreadCount)
|
||||
.put("lastMessage", "")
|
||||
.put("lastMessageTime", "")
|
||||
.put("autoEnabled", autoItem != null)
|
||||
.put("autoRule", autoItem == null ? null : autoItem.rule);
|
||||
})
|
||||
.toList()
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ package telegram.files.repository;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SettingAutoRecords {
|
||||
|
|
@ -18,12 +19,15 @@ public class SettingAutoRecords {
|
|||
|
||||
public long nextFromMessageId;
|
||||
|
||||
public Rule rule;
|
||||
|
||||
public Item() {
|
||||
}
|
||||
|
||||
public Item(long telegramId, long chatId) {
|
||||
public Item(long telegramId, long chatId, Rule rule) {
|
||||
this.telegramId = telegramId;
|
||||
this.chatId = chatId;
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
public String uniqueKey() {
|
||||
|
|
@ -31,6 +35,20 @@ public class SettingAutoRecords {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Rule {
|
||||
public String query;
|
||||
|
||||
public List<String> fileTypes;
|
||||
|
||||
public Rule() {
|
||||
}
|
||||
|
||||
public Rule(String query, List<String> fileTypes) {
|
||||
this.query = query;
|
||||
this.fileTypes = fileTypes;
|
||||
}
|
||||
}
|
||||
|
||||
public SettingAutoRecords() {
|
||||
this.items = new ArrayList<>();
|
||||
}
|
||||
|
|
@ -48,19 +66,18 @@ public class SettingAutoRecords {
|
|||
items.add(item);
|
||||
}
|
||||
|
||||
public void add(long telegramId, long chatId) {
|
||||
items.add(new Item(telegramId, chatId));
|
||||
public void add(long telegramId, long chatId, Rule rule) {
|
||||
items.add(new Item(telegramId, chatId, rule));
|
||||
}
|
||||
|
||||
public void remove(long telegramId, long chatId) {
|
||||
items.removeIf(item -> item.telegramId == telegramId && item.chatId == chatId);
|
||||
}
|
||||
|
||||
public Set<Long> getChatIds(long telegramId) {
|
||||
public Map<Long, Item> getItems(long telegramId) {
|
||||
return items.stream()
|
||||
.filter(item -> item.telegramId == telegramId)
|
||||
.map(item -> item.chatId)
|
||||
.collect(Collectors.toSet());
|
||||
.collect(Collectors.toMap(i -> i.chatId, Function.identity()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
333
web/package-lock.json
generated
333
web/package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
|||
"@dnd-kit/modifiers": "^8.0.0",
|
||||
"@dnd-kit/sortable": "^9.0.0",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@radix-ui/react-accordion": "^1.2.2",
|
||||
"@radix-ui/react-avatar": "^1.1.1",
|
||||
"@radix-ui/react-checkbox": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
|
|
@ -970,6 +971,119 @@
|
|||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.2.tgz",
|
||||
"integrity": "sha512-b1oh54x4DMCdGsB4/7ahiSrViXxaBwRPotiZNnYXjLha9vfuURSAZErki6qjDoSIV0eXx5v57XnTGVtGwnfp2g==",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-collapsible": "1.1.2",
|
||||
"@radix-ui/react-collection": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
|
||||
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
|
||||
"integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
||||
|
|
@ -1046,6 +1160,116 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",
|
||||
"integrity": "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
|
||||
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
|
||||
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
|
||||
|
|
@ -8322,6 +8546,62 @@
|
|||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
|
||||
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
|
||||
},
|
||||
"@radix-ui/react-accordion": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.2.tgz",
|
||||
"integrity": "sha512-b1oh54x4DMCdGsB4/7ahiSrViXxaBwRPotiZNnYXjLha9vfuURSAZErki6qjDoSIV0eXx5v57XnTGVtGwnfp2g==",
|
||||
"requires": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-collapsible": "1.1.2",
|
||||
"@radix-ui/react-collection": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
|
||||
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
|
||||
},
|
||||
"@radix-ui/react-collection": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
|
||||
"integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
|
||||
"requires": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||
"requires": {
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-slot": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||
"requires": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-arrow": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
|
||||
|
|
@ -8356,6 +8636,59 @@
|
|||
"@radix-ui/react-use-size": "1.1.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-collapsible": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",
|
||||
"integrity": "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==",
|
||||
"requires": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
|
||||
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="
|
||||
},
|
||||
"@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@radix-ui/react-presence": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
|
||||
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
|
||||
"requires": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||
"requires": {
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-slot": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||
"requires": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-collection": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"@dnd-kit/modifiers": "^8.0.0",
|
||||
"@dnd-kit/sortable": "^9.0.0",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@radix-ui/react-accordion": "^1.2.2",
|
||||
"@radix-ui/react-avatar": "^1.1.1",
|
||||
"@radix-ui/react-checkbox": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { POST } from "@/lib/api";
|
||||
import { useDebounce } from "use-debounce";
|
||||
|
|
@ -15,17 +15,41 @@ import { useToast } from "@/hooks/use-toast";
|
|||
import { AutoDownloadButton } from "@/components/auto-download-button";
|
||||
import { useTelegramChat } from "@/hooks/use-telegram-chat";
|
||||
import { useTelegramAccount } from "@/hooks/use-telegram-account";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "./ui/accordion";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { type AutoDownloadRule, type FileType } from "@/lib/types";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
export default function AutoDownloadDialog() {
|
||||
const { accountId } = useTelegramAccount();
|
||||
const { isLoading, chat, reload } = useTelegramChat();
|
||||
const { toast } = useToast();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [rule, setRule] = useState<AutoDownloadRule>({
|
||||
query: "",
|
||||
fileTypes: [],
|
||||
});
|
||||
const { trigger: triggerAuto, isMutating: isAutoMutating } = useSWRMutation(
|
||||
!accountId || !chat
|
||||
? undefined
|
||||
: `/file/auto-download?telegramId=${accountId}&chatId=${chat?.id}`,
|
||||
POST,
|
||||
(key, { arg }: { arg: { rule: AutoDownloadRule } }) => {
|
||||
return POST(key, arg);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
|
|
@ -74,43 +98,109 @@ export default function AutoDownloadDialog() {
|
|||
</DialogHeader>
|
||||
<DialogDescription></DialogDescription>
|
||||
{chat?.autoEnabled ? (
|
||||
<div className="space-y-4 rounded-md border border-gray-200 bg-gray-50 p-4">
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
This will disable auto download for this chat. You can always
|
||||
enable it later.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
Files that are being downloaded will be paused and you can
|
||||
enable automatic downloading again later.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
{chat.autoRule && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center">
|
||||
<Label className="text-sm font-semibold text-gray-900">
|
||||
Current Rule
|
||||
</Label>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="ml-2 border-blue-200 bg-blue-50 px-2 py-0.5 text-xs text-blue-700"
|
||||
>
|
||||
Active
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* Filter Keyword Section */}
|
||||
<div className="rounded-lg bg-gray-50 p-3">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span className="text-xs font-medium text-gray-500">
|
||||
Filter Keyword
|
||||
</span>
|
||||
<span className="text-sm text-gray-900">
|
||||
{chat.autoRule.query || "No keyword specified"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-gray-50 p-3">
|
||||
<span className="text-xs font-medium text-gray-500">
|
||||
File Types
|
||||
</span>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{chat.autoRule.fileTypes.length > 0 ? (
|
||||
chat.autoRule.fileTypes.map((type) => (
|
||||
<Badge
|
||||
key={type}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1 border-gray-200 bg-white px-3 py-1 capitalize text-gray-700"
|
||||
>
|
||||
{type}
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<span className="text-sm text-gray-500">
|
||||
No file types selected
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4 rounded-md border border-gray-200 bg-gray-50 p-4">
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-yellow-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
This will disable auto download for this chat. You can always
|
||||
enable it later.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-yellow-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
Files that are being downloaded will be paused and you can
|
||||
enable automatic downloading again later.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4 rounded-md border border-gray-200 bg-gray-50 p-4">
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
This will enable auto download for this chat. Files will be
|
||||
downloaded automatically.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
Files in historical messages will be downloaded first, and then
|
||||
files in new messages will be downloaded automatically.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 rounded-md border border-gray-200 bg-gray-50 p-4">
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
This will enable auto download for this chat. Files will be
|
||||
downloaded automatically.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
Files in historical messages will be downloaded first, and
|
||||
then files in new messages will be downloaded automatically.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<span className="mr-3 mt-1.5 h-3 w-2 flex-shrink-0 rounded-full bg-cyan-400"></span>
|
||||
<p className="text-sm leading-6 text-gray-700">
|
||||
Download Order:
|
||||
<span className="ml-1 rounded bg-blue-100 px-2">
|
||||
{"Photo -> Video -> Audio -> File"}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<AutoDownloadRule value={rule} onChange={setRule} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
onClick={() => triggerAuto()}
|
||||
onClick={() => triggerAuto({ rule })}
|
||||
variant={chat?.autoEnabled ? "destructive" : "default"}
|
||||
disabled={debounceIsAutoMutating}
|
||||
>
|
||||
|
|
@ -128,3 +218,92 @@ export default function AutoDownloadDialog() {
|
|||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
interface AutoDownloadRuleProps {
|
||||
value: AutoDownloadRule;
|
||||
onChange: (value: AutoDownloadRule) => void;
|
||||
}
|
||||
|
||||
function AutoDownloadRule({ value, onChange }: AutoDownloadRuleProps) {
|
||||
const handleQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({
|
||||
...value,
|
||||
query: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileTypeSelect = (type: string) => {
|
||||
if (value.fileTypes.includes(type as Exclude<FileType, "media">)) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange({
|
||||
...value,
|
||||
fileTypes: [...value.fileTypes, type as Exclude<FileType, "media">],
|
||||
});
|
||||
};
|
||||
|
||||
const removeFileType = (typeToRemove: string) => {
|
||||
onChange({
|
||||
...value,
|
||||
fileTypes: value.fileTypes.filter((type) => type !== typeToRemove),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="advanced">
|
||||
<AccordionTrigger className="hover:no-underline">
|
||||
Advanced
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex flex-col space-y-4 rounded-md border p-4 shadow">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label htmlFor="filter-keyword">Filter Keyword</Label>
|
||||
<Input
|
||||
id="filter-keyword"
|
||||
type="text"
|
||||
className="w-full"
|
||||
placeholder="Enter a keyword to filter files"
|
||||
value={value.query}
|
||||
onChange={handleQueryChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Label htmlFor="fileTypes">Filter File Types</Label>
|
||||
<Select onValueChange={handleFileTypeSelect}>
|
||||
<SelectTrigger id="fileTypes">
|
||||
<SelectValue placeholder="Select File Types" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{["photo", "video", "audio", "file"].map((type) => (
|
||||
<SelectItem key={type} value={type}>
|
||||
{type}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{value.fileTypes.map((type) => (
|
||||
<Badge
|
||||
key={type}
|
||||
className="flex items-center gap-1 px-2 py-1"
|
||||
variant="secondary"
|
||||
>
|
||||
{type}
|
||||
<X
|
||||
className="h-3 w-3 cursor-pointer"
|
||||
onClick={() => removeFileType(type)}
|
||||
/>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ const axisStyle = {
|
|||
};
|
||||
|
||||
const TelegramStats: React.FC<TelegramStatsProps> = ({ telegramId }) => {
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>("2");
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>("1");
|
||||
|
||||
const { data, error, isLoading } = useSWR<ApiResponse, Error>(
|
||||
`/telegram/${telegramId}/download-statistics?type=phase&timeRange=${timeRange}`,
|
||||
|
|
|
|||
57
web/src/components/ui/accordion.tsx
Normal file
57
web/src/components/ui/accordion.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn("border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
|
|
@ -19,6 +19,7 @@ export type TelegramChat = {
|
|||
lastMessage?: string;
|
||||
lastMessageTime?: string;
|
||||
autoEnabled: boolean;
|
||||
autoRule?: AutoDownloadRule;
|
||||
};
|
||||
|
||||
export type FileType = "media" | "photo" | "video" | "audio" | "file";
|
||||
|
|
@ -106,4 +107,9 @@ export type Proxy = {
|
|||
password: string;
|
||||
type: "http" | "socks5";
|
||||
isEnabled?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export type AutoDownloadRule = {
|
||||
query: string;
|
||||
fileTypes: Array<Exclude<FileType, "media">>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,15 +33,39 @@ export default {
|
|||
},
|
||||
},
|
||||
breathing: {
|
||||
'0%, 100%': { transform: 'scale(1)', opacity: '0.7' },
|
||||
'50%': { transform: 'scale(1.3)', opacity: '1' },
|
||||
"0%, 100%": {
|
||||
transform: "scale(1)",
|
||||
opacity: "0.7",
|
||||
},
|
||||
"50%": {
|
||||
transform: "scale(1.3)",
|
||||
opacity: "1",
|
||||
},
|
||||
},
|
||||
"accordion-down": {
|
||||
from: {
|
||||
height: "0",
|
||||
},
|
||||
to: {
|
||||
height: "var(--radix-accordion-content-height)",
|
||||
},
|
||||
},
|
||||
"accordion-up": {
|
||||
from: {
|
||||
height: "var(--radix-accordion-content-height)",
|
||||
},
|
||||
to: {
|
||||
height: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
||||
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
|
||||
orbit: "orbit calc(var(--duration)*1s) linear infinite",
|
||||
'breathing': 'breathing 6s ease-in-out infinite',
|
||||
breathing: "breathing 6s ease-in-out infinite",
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-geist-sans)", ...fontFamily.sans],
|
||||
|
|
|
|||
Loading…
Reference in a new issue