mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 22:52:12 -03:00
feat(selection): implement marquee selection for bulk operations
This commit is contained in:
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "{count} ausgewählt",
|
"selected": "{count} ausgewählt",
|
||||||
"selectedSuffix": "ausgewählt",
|
"selectedSuffix": "ausgewählt",
|
||||||
"viewSelected": "Klicken Sie, um ausgewählte Elemente anzuzeigen",
|
"viewSelected": "Auswahl anzeigen",
|
||||||
"addTags": "Tags hinzufügen",
|
"addTags": "Allen Tags hinzufügen",
|
||||||
"sendToWorkflow": "An Workflow senden",
|
"copyAll": "Alle Syntax kopieren",
|
||||||
"copyAll": "Alle kopieren",
|
"refreshAll": "Alle Metadaten aktualisieren",
|
||||||
"refreshAll": "Alle aktualisieren",
|
"moveAll": "Alle in Ordner verschieben",
|
||||||
"moveAll": "Alle verschieben",
|
"deleteAll": "Alle Modelle löschen",
|
||||||
"deleteAll": "Alle löschen",
|
"clear": "Auswahl löschen"
|
||||||
"clear": "Leeren"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "Civitai-Daten aktualisieren",
|
"refreshMetadata": "Civitai-Daten aktualisieren",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "Fehler: {error}",
|
"deleteFailed": "Fehler: {error}",
|
||||||
"deleteFailedGeneral": "Fehler beim Löschen der Modelle",
|
"deleteFailedGeneral": "Fehler beim Löschen der Modelle",
|
||||||
"selectedAdditional": "{count} zusätzliche {type}(s) ausgewählt",
|
"selectedAdditional": "{count} zusätzliche {type}(s) ausgewählt",
|
||||||
|
"marqueeSelectionComplete": "{count} {type}(s) mit Rahmenauswahl ausgewählt",
|
||||||
"refreshMetadataFailed": "Fehler beim Aktualisieren der Metadaten",
|
"refreshMetadataFailed": "Fehler beim Aktualisieren der Metadaten",
|
||||||
"nameCannotBeEmpty": "Modellname darf nicht leer sein",
|
"nameCannotBeEmpty": "Modellname darf nicht leer sein",
|
||||||
"nameUpdatedSuccessfully": "Modellname erfolgreich aktualisiert",
|
"nameUpdatedSuccessfully": "Modellname erfolgreich aktualisiert",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "{count} selected",
|
"selected": "{count} selected",
|
||||||
"selectedSuffix": "selected",
|
"selectedSuffix": "selected",
|
||||||
"viewSelected": "Click to view selected items",
|
"viewSelected": "View Selected",
|
||||||
"addTags": "Add Tags",
|
"addTags": "Add Tags to All",
|
||||||
"sendToWorkflow": "Send to Workflow",
|
"copyAll": "Copy All Syntax",
|
||||||
"copyAll": "Copy All",
|
"refreshAll": "Refresh All Metadata",
|
||||||
"refreshAll": "Refresh All",
|
"moveAll": "Move All to Folder",
|
||||||
"moveAll": "Move All",
|
"deleteAll": "Delete All Models",
|
||||||
"deleteAll": "Delete All",
|
"clear": "Clear Selection"
|
||||||
"clear": "Clear"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "Refresh Civitai Data",
|
"refreshMetadata": "Refresh Civitai Data",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "Error: {error}",
|
"deleteFailed": "Error: {error}",
|
||||||
"deleteFailedGeneral": "Failed to delete models",
|
"deleteFailedGeneral": "Failed to delete models",
|
||||||
"selectedAdditional": "Selected {count} additional {type}(s)",
|
"selectedAdditional": "Selected {count} additional {type}(s)",
|
||||||
|
"marqueeSelectionComplete": "Selected {count} {type}(s) with marquee selection",
|
||||||
"refreshMetadataFailed": "Failed to refresh metadata",
|
"refreshMetadataFailed": "Failed to refresh metadata",
|
||||||
"nameCannotBeEmpty": "Model name cannot be empty",
|
"nameCannotBeEmpty": "Model name cannot be empty",
|
||||||
"nameUpdatedSuccessfully": "Model name updated successfully",
|
"nameUpdatedSuccessfully": "Model name updated successfully",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "{count} seleccionados",
|
"selected": "{count} seleccionados",
|
||||||
"selectedSuffix": "seleccionados",
|
"selectedSuffix": "seleccionados",
|
||||||
"viewSelected": "Clic para ver elementos seleccionados",
|
"viewSelected": "Ver seleccionados",
|
||||||
"addTags": "Añadir etiquetas",
|
"addTags": "Añadir etiquetas a todos",
|
||||||
"sendToWorkflow": "Enviar al flujo de trabajo",
|
"copyAll": "Copiar toda la sintaxis",
|
||||||
"copyAll": "Copiar todo",
|
"refreshAll": "Actualizar todos los metadatos",
|
||||||
"refreshAll": "Actualizar todo",
|
"moveAll": "Mover todos a carpeta",
|
||||||
"moveAll": "Mover todo",
|
"deleteAll": "Eliminar todos los modelos",
|
||||||
"deleteAll": "Eliminar todo",
|
"clear": "Limpiar selección"
|
||||||
"clear": "Limpiar"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "Actualizar datos de Civitai",
|
"refreshMetadata": "Actualizar datos de Civitai",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "Error: {error}",
|
"deleteFailed": "Error: {error}",
|
||||||
"deleteFailedGeneral": "Error al eliminar modelos",
|
"deleteFailedGeneral": "Error al eliminar modelos",
|
||||||
"selectedAdditional": "Seleccionados {count} {type}(s) adicionales",
|
"selectedAdditional": "Seleccionados {count} {type}(s) adicionales",
|
||||||
|
"marqueeSelectionComplete": "Seleccionados {count} {type}(s) con selección de marco",
|
||||||
"refreshMetadataFailed": "Error al actualizar metadatos",
|
"refreshMetadataFailed": "Error al actualizar metadatos",
|
||||||
"nameCannotBeEmpty": "El nombre del modelo no puede estar vacío",
|
"nameCannotBeEmpty": "El nombre del modelo no puede estar vacío",
|
||||||
"nameUpdatedSuccessfully": "Nombre del modelo actualizado exitosamente",
|
"nameUpdatedSuccessfully": "Nombre del modelo actualizado exitosamente",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "{count} sélectionné(s)",
|
"selected": "{count} sélectionné(s)",
|
||||||
"selectedSuffix": "sélectionné(s)",
|
"selectedSuffix": "sélectionné(s)",
|
||||||
"viewSelected": "Cliquez pour voir les éléments sélectionnés",
|
"viewSelected": "Voir la sélection",
|
||||||
"addTags": "Ajouter des tags",
|
"addTags": "Ajouter des tags à tous",
|
||||||
"sendToWorkflow": "Envoyer vers le workflow",
|
"copyAll": "Copier toute la syntaxe",
|
||||||
"copyAll": "Tout copier",
|
"refreshAll": "Actualiser toutes les métadonnées",
|
||||||
"refreshAll": "Tout actualiser",
|
"moveAll": "Déplacer tout vers un dossier",
|
||||||
"moveAll": "Tout déplacer",
|
"deleteAll": "Supprimer tous les modèles",
|
||||||
"deleteAll": "Tout supprimer",
|
"clear": "Effacer la sélection"
|
||||||
"clear": "Effacer"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "Actualiser les données Civitai",
|
"refreshMetadata": "Actualiser les données Civitai",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "Erreur : {error}",
|
"deleteFailed": "Erreur : {error}",
|
||||||
"deleteFailedGeneral": "Échec de la suppression des modèles",
|
"deleteFailedGeneral": "Échec de la suppression des modèles",
|
||||||
"selectedAdditional": "{count} {type}(s) supplémentaire(s) sélectionné(s)",
|
"selectedAdditional": "{count} {type}(s) supplémentaire(s) sélectionné(s)",
|
||||||
|
"marqueeSelectionComplete": "{count} {type}(s) sélectionné(s) avec la sélection par glisser-déposer",
|
||||||
"refreshMetadataFailed": "Échec de l'actualisation des métadonnées",
|
"refreshMetadataFailed": "Échec de l'actualisation des métadonnées",
|
||||||
"nameCannotBeEmpty": "Le nom du modèle ne peut pas être vide",
|
"nameCannotBeEmpty": "Le nom du modèle ne peut pas être vide",
|
||||||
"nameUpdatedSuccessfully": "Nom du modèle mis à jour avec succès",
|
"nameUpdatedSuccessfully": "Nom du modèle mis à jour avec succès",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "{count} 選択中",
|
"selected": "{count} 選択中",
|
||||||
"selectedSuffix": "選択中",
|
"selectedSuffix": "選択中",
|
||||||
"viewSelected": "選択したアイテムを表示するにはクリック",
|
"viewSelected": "選択中を表示",
|
||||||
"addTags": "タグを追加",
|
"addTags": "すべてにタグを追加",
|
||||||
"sendToWorkflow": "ワークフローに送信",
|
"copyAll": "すべての構文をコピー",
|
||||||
"copyAll": "すべてコピー",
|
"refreshAll": "すべてのメタデータを更新",
|
||||||
"refreshAll": "すべて更新",
|
"moveAll": "すべてをフォルダに移動",
|
||||||
"moveAll": "すべて移動",
|
"deleteAll": "すべてのモデルを削除",
|
||||||
"deleteAll": "すべて削除",
|
"clear": "選択をクリア"
|
||||||
"clear": "クリア"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "Civitaiデータを更新",
|
"refreshMetadata": "Civitaiデータを更新",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "エラー:{error}",
|
"deleteFailed": "エラー:{error}",
|
||||||
"deleteFailedGeneral": "モデルの削除に失敗しました",
|
"deleteFailedGeneral": "モデルの削除に失敗しました",
|
||||||
"selectedAdditional": "{count} 追加{type}が選択されました",
|
"selectedAdditional": "{count} 追加{type}が選択されました",
|
||||||
|
"marqueeSelectionComplete": "マーキー選択で {count} の{type}が選択されました",
|
||||||
"refreshMetadataFailed": "メタデータの更新に失敗しました",
|
"refreshMetadataFailed": "メタデータの更新に失敗しました",
|
||||||
"nameCannotBeEmpty": "モデル名を空にすることはできません",
|
"nameCannotBeEmpty": "モデル名を空にすることはできません",
|
||||||
"nameUpdatedSuccessfully": "モデル名が正常に更新されました",
|
"nameUpdatedSuccessfully": "モデル名が正常に更新されました",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "{count}개 선택됨",
|
"selected": "{count}개 선택됨",
|
||||||
"selectedSuffix": "개 선택됨",
|
"selectedSuffix": "개 선택됨",
|
||||||
"viewSelected": "선택된 항목 보기",
|
"viewSelected": "선택 항목 보기",
|
||||||
"addTags": "태그 추가",
|
"addTags": "모두에 태그 추가",
|
||||||
"sendToWorkflow": "워크플로로 전송",
|
"copyAll": "모든 문법 복사",
|
||||||
"copyAll": "모두 복사",
|
"refreshAll": "모든 메타데이터 새로고침",
|
||||||
"refreshAll": "모두 새로고침",
|
"moveAll": "모두 폴더로 이동",
|
||||||
"moveAll": "모두 이동",
|
"deleteAll": "모든 모델 삭제",
|
||||||
"deleteAll": "모두 삭제",
|
"clear": "선택 지우기"
|
||||||
"clear": "지우기"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "Civitai 데이터 새로고침",
|
"refreshMetadata": "Civitai 데이터 새로고침",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "오류: {error}",
|
"deleteFailed": "오류: {error}",
|
||||||
"deleteFailedGeneral": "모델 삭제에 실패했습니다",
|
"deleteFailedGeneral": "모델 삭제에 실패했습니다",
|
||||||
"selectedAdditional": "추가로 {count}개의 {type}이(가) 선택되었습니다",
|
"selectedAdditional": "추가로 {count}개의 {type}이(가) 선택되었습니다",
|
||||||
|
"marqueeSelectionComplete": "마키 선택으로 {count}개의 {type}이(가) 선택되었습니다",
|
||||||
"refreshMetadataFailed": "메타데이터 새로고침에 실패했습니다",
|
"refreshMetadataFailed": "메타데이터 새로고침에 실패했습니다",
|
||||||
"nameCannotBeEmpty": "모델 이름은 비어있을 수 없습니다",
|
"nameCannotBeEmpty": "모델 이름은 비어있을 수 없습니다",
|
||||||
"nameUpdatedSuccessfully": "모델 이름이 성공적으로 업데이트되었습니다",
|
"nameUpdatedSuccessfully": "모델 이름이 성공적으로 업데이트되었습니다",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "Выбрано {count}",
|
"selected": "Выбрано {count}",
|
||||||
"selectedSuffix": "выбрано",
|
"selectedSuffix": "выбрано",
|
||||||
"viewSelected": "Нажмите для просмотра выбранных элементов",
|
"viewSelected": "Просмотреть выбранные",
|
||||||
"addTags": "Добавить теги",
|
"addTags": "Добавить теги ко всем",
|
||||||
"sendToWorkflow": "Отправить в Workflow",
|
"copyAll": "Копировать весь синтаксис",
|
||||||
"copyAll": "Копировать все",
|
"refreshAll": "Обновить все метаданные",
|
||||||
"refreshAll": "Обновить все",
|
"moveAll": "Переместить все в папку",
|
||||||
"moveAll": "Переместить все",
|
"deleteAll": "Удалить все модели",
|
||||||
"deleteAll": "Удалить все",
|
"clear": "Очистить выбор"
|
||||||
"clear": "Очистить"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "Обновить данные Civitai",
|
"refreshMetadata": "Обновить данные Civitai",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "Ошибка: {error}",
|
"deleteFailed": "Ошибка: {error}",
|
||||||
"deleteFailedGeneral": "Не удалось удалить модели",
|
"deleteFailedGeneral": "Не удалось удалить модели",
|
||||||
"selectedAdditional": "Выбрано дополнительно {count} {type}(ей)",
|
"selectedAdditional": "Выбрано дополнительно {count} {type}(ей)",
|
||||||
|
"marqueeSelectionComplete": "Выбрано {count} {type} с помощью выделения рамкой",
|
||||||
"refreshMetadataFailed": "Не удалось обновить метаданные",
|
"refreshMetadataFailed": "Не удалось обновить метаданные",
|
||||||
"nameCannotBeEmpty": "Название модели не может быть пустым",
|
"nameCannotBeEmpty": "Название модели не может быть пустым",
|
||||||
"nameUpdatedSuccessfully": "Название модели успешно обновлено",
|
"nameUpdatedSuccessfully": "Название модели успешно обновлено",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "已选中 {count} 项",
|
"selected": "已选中 {count} 项",
|
||||||
"selectedSuffix": "已选中",
|
"selectedSuffix": "已选中",
|
||||||
"viewSelected": "点击查看已选项目",
|
"viewSelected": "查看已选中",
|
||||||
"addTags": "批量添加标签",
|
"addTags": "为所有添加标签",
|
||||||
"sendToWorkflow": "发送到工作流",
|
"copyAll": "复制全部语法",
|
||||||
"copyAll": "全部复制",
|
"refreshAll": "刷新全部元数据",
|
||||||
"refreshAll": "全部刷新",
|
"moveAll": "全部移动到文件夹",
|
||||||
"moveAll": "全部移动",
|
"deleteAll": "删除所有模型",
|
||||||
"deleteAll": "全部删除",
|
"clear": "清除选择"
|
||||||
"clear": "清除"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "刷新 Civitai 数据",
|
"refreshMetadata": "刷新 Civitai 数据",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "错误:{error}",
|
"deleteFailed": "错误:{error}",
|
||||||
"deleteFailedGeneral": "删除模型失败",
|
"deleteFailedGeneral": "删除模型失败",
|
||||||
"selectedAdditional": "已选中 {count} 个额外 {type}",
|
"selectedAdditional": "已选中 {count} 个额外 {type}",
|
||||||
|
"marqueeSelectionComplete": "框选已选中 {count} 个 {type}",
|
||||||
"refreshMetadataFailed": "刷新元数据失败",
|
"refreshMetadataFailed": "刷新元数据失败",
|
||||||
"nameCannotBeEmpty": "模型名称不能为空",
|
"nameCannotBeEmpty": "模型名称不能为空",
|
||||||
"nameUpdatedSuccessfully": "模型名称更新成功",
|
"nameUpdatedSuccessfully": "模型名称更新成功",
|
||||||
|
|||||||
@@ -318,14 +318,13 @@
|
|||||||
"bulkOperations": {
|
"bulkOperations": {
|
||||||
"selected": "已選擇 {count} 項",
|
"selected": "已選擇 {count} 項",
|
||||||
"selectedSuffix": "已選擇",
|
"selectedSuffix": "已選擇",
|
||||||
"viewSelected": "點擊檢視已選項目",
|
"viewSelected": "檢視已選取",
|
||||||
"addTags": "新增標籤",
|
"addTags": "新增標籤到全部",
|
||||||
"sendToWorkflow": "傳送到工作流",
|
"copyAll": "複製全部語法",
|
||||||
"copyAll": "全部複製",
|
"refreshAll": "刷新全部 metadata",
|
||||||
"refreshAll": "全部刷新",
|
"moveAll": "全部移動到資料夾",
|
||||||
"moveAll": "全部移動",
|
"deleteAll": "刪除全部模型",
|
||||||
"deleteAll": "全部刪除",
|
"clear": "清除選取"
|
||||||
"clear": "清除"
|
|
||||||
},
|
},
|
||||||
"contextMenu": {
|
"contextMenu": {
|
||||||
"refreshMetadata": "刷新 Civitai 資料",
|
"refreshMetadata": "刷新 Civitai 資料",
|
||||||
@@ -983,6 +982,7 @@
|
|||||||
"deleteFailed": "錯誤:{error}",
|
"deleteFailed": "錯誤:{error}",
|
||||||
"deleteFailedGeneral": "刪除模型失敗",
|
"deleteFailedGeneral": "刪除模型失敗",
|
||||||
"selectedAdditional": "已選擇 {count} 個額外 {type}",
|
"selectedAdditional": "已選擇 {count} 個額外 {type}",
|
||||||
|
"marqueeSelectionComplete": "框選已選擇 {count} 個 {type}",
|
||||||
"refreshMetadataFailed": "刷新 metadata 失敗",
|
"refreshMetadataFailed": "刷新 metadata 失敗",
|
||||||
"nameCannotBeEmpty": "模型名稱不可為空",
|
"nameCannotBeEmpty": "模型名稱不可為空",
|
||||||
"nameUpdatedSuccessfully": "模型名稱已成功更新",
|
"nameUpdatedSuccessfully": "模型名稱已成功更新",
|
||||||
|
|||||||
@@ -1,81 +1,3 @@
|
|||||||
/* Bulk Operations Styles */
|
|
||||||
.bulk-operations-panel {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 20px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateY(100px) translateX(-50%);
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--border-radius-base);
|
|
||||||
padding: 12px 16px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: var(--z-overlay);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 420px;
|
|
||||||
max-width: 900px;
|
|
||||||
width: auto;
|
|
||||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
gap: 20px; /* Increase space between count and buttons */
|
|
||||||
}
|
|
||||||
|
|
||||||
#selectedCount {
|
|
||||||
font-weight: 500;
|
|
||||||
background: var(--bg-color);
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
min-width: 80px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions button {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
background: var(--bg-color);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-height: 36px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions button:hover {
|
|
||||||
background: var(--lora-accent);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--lora-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Danger button style - updated to use proper theme variables */
|
|
||||||
.bulk-operations-actions button.danger-btn {
|
|
||||||
background: oklch(70% 0.2 29); /* Light red background that works in both themes */
|
|
||||||
color: oklch(98% 0.01 0); /* Almost white text for good contrast */
|
|
||||||
border-color: var(--lora-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions button.danger-btn:hover {
|
|
||||||
background: var(--lora-error);
|
|
||||||
color: oklch(100% 0 0); /* Pure white text on hover for maximum contrast */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style for selected cards */
|
/* Style for selected cards */
|
||||||
.model-card.selected {
|
.model-card.selected {
|
||||||
box-shadow: 0 0 0 2px var(--lora-accent);
|
box-shadow: 0 0 0 2px var(--lora-accent);
|
||||||
@@ -99,203 +21,29 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update bulk operations button to match others when active */
|
/* Marquee selection styles */
|
||||||
#bulkOperationsBtn.active {
|
.marquee-selection {
|
||||||
background: var(--lora-accent);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--lora-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.bulk-operations-panel {
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
min-width: unset;
|
|
||||||
max-width: unset;
|
|
||||||
left: 20px;
|
|
||||||
transform: none;
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-panel.visible {
|
|
||||||
transform: translateY(0) translateX(-50%);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Thumbnail Strip Styles */
|
|
||||||
.selected-thumbnails-strip {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 80px; /* Position above the bulk operations panel */
|
border: 2px dashed var(--lora-accent, #007bff);
|
||||||
left: 50%;
|
background: rgba(0, 123, 255, 0.1);
|
||||||
transform: translateX(-50%) translateY(20px);
|
pointer-events: none;
|
||||||
background: var(--card-bg);
|
z-index: 9999;
|
||||||
border: 1px solid var(--border-color);
|
border-radius: 2px;
|
||||||
border-radius: var(--border-radius-base);
|
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
||||||
z-index: calc(var(--z-overlay) - 1); /* Just below the bulk panel z-index */
|
|
||||||
padding: 16px;
|
|
||||||
max-width: 80%;
|
|
||||||
width: auto;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
opacity: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-thumbnails-strip.visible {
|
/* Visual feedback when marquee selecting */
|
||||||
opacity: 1;
|
.marquee-selecting {
|
||||||
transform: translateX(-50%) translateY(0);
|
cursor: crosshair;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnails-container {
|
/* Prevent text selection during marquee */
|
||||||
display: flex;
|
.marquee-selecting * {
|
||||||
gap: 12px;
|
user-select: none;
|
||||||
overflow-x: auto;
|
-webkit-user-select: none;
|
||||||
padding-bottom: 8px; /* Space for scrollbar */
|
-moz-user-select: none;
|
||||||
max-width: 100%;
|
-ms-user-select: none;
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-thumbnail {
|
|
||||||
position: relative;
|
|
||||||
width: 80px;
|
|
||||||
min-width: 80px; /* Prevent shrinking */
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
background: var(--bg-color);
|
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-thumbnail:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-thumbnail img,
|
|
||||||
.selected-thumbnail video {
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail-name {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
color: white;
|
|
||||||
font-size: 10px;
|
|
||||||
padding: 3px 5px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail-remove {
|
|
||||||
position: absolute;
|
|
||||||
top: 3px;
|
|
||||||
right: 3px;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 10px;
|
|
||||||
opacity: 0.7;
|
|
||||||
transition: opacity 0.2s ease, background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnail-remove:hover {
|
|
||||||
opacity: 1;
|
|
||||||
background: var(--lora-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.strip-close-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 5px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
opacity: 0.7;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.strip-close-btn:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style the selectedCount to indicate it's clickable */
|
|
||||||
.selectable-count {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectable-count:hover {
|
|
||||||
background: var(--lora-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-caret {
|
|
||||||
font-size: 12px;
|
|
||||||
visibility: hidden; /* Will be shown via JS when items are selected */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollbar styling for the thumbnails container */
|
|
||||||
.thumbnails-container::-webkit-scrollbar {
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnails-container::-webkit-scrollbar-track {
|
|
||||||
background: var(--bg-color);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnails-container::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--border-color);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumbnails-container::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--lora-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile optimizations */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.selected-thumbnails-strip {
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
max-width: none;
|
|
||||||
left: 20px;
|
|
||||||
transform: translateY(20px);
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-thumbnails-strip.visible {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-thumbnail {
|
|
||||||
width: 70px;
|
|
||||||
min-width: 70px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,12 @@ export class BulkManager {
|
|||||||
// Remove bulk panel references since we're using context menu now
|
// Remove bulk panel references since we're using context menu now
|
||||||
this.bulkContextMenu = null; // Will be set by core initialization
|
this.bulkContextMenu = null; // Will be set by core initialization
|
||||||
|
|
||||||
|
// Marquee selection properties
|
||||||
|
this.isMarqueeActive = false;
|
||||||
|
this.marqueeStart = { x: 0, y: 0 };
|
||||||
|
this.marqueeElement = null;
|
||||||
|
this.initialSelectedModels = new Set();
|
||||||
|
|
||||||
// Model type specific action configurations
|
// Model type specific action configurations
|
||||||
this.actionConfig = {
|
this.actionConfig = {
|
||||||
[MODEL_TYPES.LORA]: {
|
[MODEL_TYPES.LORA]: {
|
||||||
@@ -44,6 +50,7 @@ export class BulkManager {
|
|||||||
initialize() {
|
initialize() {
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.setupGlobalKeyboardListeners();
|
this.setupGlobalKeyboardListeners();
|
||||||
|
this.setupMarqueeSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
setBulkContextMenu(bulkContextMenu) {
|
setBulkContextMenu(bulkContextMenu) {
|
||||||
@@ -734,6 +741,201 @@ export class BulkManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup marquee selection functionality
|
||||||
|
*/
|
||||||
|
setupMarqueeSelection() {
|
||||||
|
const container = document.querySelector('.models-container') || document.body;
|
||||||
|
|
||||||
|
container.addEventListener('mousedown', (e) => {
|
||||||
|
// Disable marquee if any modal is open
|
||||||
|
if (modalManager.isAnyModalOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only start marquee selection on left click in empty areas
|
||||||
|
if (e.button !== 0 || e.target.closest('.model-card') || e.target.closest('button') || e.target.closest('input')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent text selection during marquee
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.startMarqueeSelection(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
// Disable marquee update if any modal is open
|
||||||
|
if (modalManager.isAnyModalOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isMarqueeActive) {
|
||||||
|
this.updateMarqueeSelection(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', (e) => {
|
||||||
|
if (this.isMarqueeActive) {
|
||||||
|
this.endMarqueeSelection(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prevent context menu during marquee selection
|
||||||
|
document.addEventListener('contextmenu', (e) => {
|
||||||
|
if (this.isMarqueeActive) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start marquee selection
|
||||||
|
*/
|
||||||
|
startMarqueeSelection(e) {
|
||||||
|
// Store initial mouse position
|
||||||
|
this.marqueeStart.x = e.clientX;
|
||||||
|
this.marqueeStart.y = e.clientY;
|
||||||
|
|
||||||
|
// Store initial selection state
|
||||||
|
this.initialSelectedModels = new Set(state.selectedModels);
|
||||||
|
|
||||||
|
// Enter bulk mode if not already active
|
||||||
|
if (!state.bulkMode) {
|
||||||
|
this.toggleBulkMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create marquee element
|
||||||
|
this.createMarqueeElement();
|
||||||
|
|
||||||
|
this.isMarqueeActive = true;
|
||||||
|
|
||||||
|
// Add visual feedback class to body
|
||||||
|
document.body.classList.add('marquee-selecting');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the visual marquee selection rectangle
|
||||||
|
*/
|
||||||
|
createMarqueeElement() {
|
||||||
|
this.marqueeElement = document.createElement('div');
|
||||||
|
this.marqueeElement.className = 'marquee-selection';
|
||||||
|
this.marqueeElement.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
border: 2px dashed var(--lora-accent, #007bff);
|
||||||
|
background: rgba(0, 123, 255, 0.1);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
left: ${this.marqueeStart.x}px;
|
||||||
|
top: ${this.marqueeStart.y}px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
`;
|
||||||
|
document.body.appendChild(this.marqueeElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update marquee selection rectangle and selected items
|
||||||
|
*/
|
||||||
|
updateMarqueeSelection(e) {
|
||||||
|
if (!this.marqueeElement) return;
|
||||||
|
|
||||||
|
const currentX = e.clientX;
|
||||||
|
const currentY = e.clientY;
|
||||||
|
|
||||||
|
// Calculate rectangle bounds
|
||||||
|
const left = Math.min(this.marqueeStart.x, currentX);
|
||||||
|
const top = Math.min(this.marqueeStart.y, currentY);
|
||||||
|
const width = Math.abs(currentX - this.marqueeStart.x);
|
||||||
|
const height = Math.abs(currentY - this.marqueeStart.y);
|
||||||
|
|
||||||
|
// Update marquee element position and size
|
||||||
|
this.marqueeElement.style.left = left + 'px';
|
||||||
|
this.marqueeElement.style.top = top + 'px';
|
||||||
|
this.marqueeElement.style.width = width + 'px';
|
||||||
|
this.marqueeElement.style.height = height + 'px';
|
||||||
|
|
||||||
|
// Check which cards intersect with marquee
|
||||||
|
this.updateCardSelection(left, top, left + width, top + height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update card selection based on marquee bounds
|
||||||
|
*/
|
||||||
|
updateCardSelection(left, top, right, bottom) {
|
||||||
|
const cards = document.querySelectorAll('.model-card');
|
||||||
|
const newSelection = new Set(this.initialSelectedModels);
|
||||||
|
|
||||||
|
cards.forEach(card => {
|
||||||
|
const rect = card.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Check if card intersects with marquee rectangle
|
||||||
|
const intersects = !(rect.right < left ||
|
||||||
|
rect.left > right ||
|
||||||
|
rect.bottom < top ||
|
||||||
|
rect.top > bottom);
|
||||||
|
|
||||||
|
const filepath = card.dataset.filepath;
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
// Add to selection if intersecting
|
||||||
|
newSelection.add(filepath);
|
||||||
|
card.classList.add('selected');
|
||||||
|
|
||||||
|
// Cache metadata if not already cached
|
||||||
|
const metadataCache = this.getMetadataCache();
|
||||||
|
if (!metadataCache.has(filepath)) {
|
||||||
|
metadataCache.set(filepath, {
|
||||||
|
fileName: card.dataset.file_name,
|
||||||
|
usageTips: card.dataset.usage_tips,
|
||||||
|
previewUrl: this.getCardPreviewUrl(card),
|
||||||
|
isVideo: this.isCardPreviewVideo(card),
|
||||||
|
modelName: card.dataset.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (!this.initialSelectedModels.has(filepath)) {
|
||||||
|
// Remove from selection if not intersecting and wasn't initially selected
|
||||||
|
newSelection.delete(filepath);
|
||||||
|
card.classList.remove('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update global selection state
|
||||||
|
state.selectedModels = newSelection;
|
||||||
|
|
||||||
|
// Update context menu header if visible
|
||||||
|
if (this.bulkContextMenu) {
|
||||||
|
this.bulkContextMenu.updateSelectedCountHeader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End marquee selection
|
||||||
|
*/
|
||||||
|
endMarqueeSelection(e) {
|
||||||
|
this.isMarqueeActive = false;
|
||||||
|
|
||||||
|
// Remove marquee element
|
||||||
|
if (this.marqueeElement) {
|
||||||
|
this.marqueeElement.remove();
|
||||||
|
this.marqueeElement = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove visual feedback class
|
||||||
|
document.body.classList.remove('marquee-selecting');
|
||||||
|
|
||||||
|
// Show toast with selection count if any items were selected
|
||||||
|
const selectionCount = state.selectedModels.size;
|
||||||
|
if (selectionCount > 0) {
|
||||||
|
const currentConfig = MODEL_CONFIG[state.currentPageType];
|
||||||
|
showToast('toast.models.marqueeSelectionComplete', {
|
||||||
|
count: selectionCount,
|
||||||
|
type: currentConfig.displayName.toLowerCase()
|
||||||
|
}, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear initial selection state
|
||||||
|
this.initialSelectedModels.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkManager = new BulkManager();
|
export const bulkManager = new BulkManager();
|
||||||
|
|||||||
Reference in New Issue
Block a user