Conversation
41192e4 to
07c4b52
Compare
6d3537d to
e0cd896
Compare
| if (!files?.length) return; | ||
| try { | ||
| selection.value = selection.value.map((f) => { | ||
| if (files.includes(f)) return { ...f, nature: "Model" }; |
There was a problem hiding this comment.
Do we need to rebuild a selection ?
Maybe we can just change f.nature = "Model" and Vue reactivity works
| loadingFileIds.value = loadingFileIds.value.filter((id) => id !== file.id); | ||
| } | ||
| }; | ||
| const createModelFromFiles = async (files, type) => { |
There was a problem hiding this comment.
rename files so we can know what are these files
There was a problem hiding this comment.
Pull request overview
This PR updates the GED files UI to replace “type” sorting with “type” filtering, and adds multi-selection actions to create models/photospheres or remove models.
Changes:
- Replace “type” column sorting with filtering in folder and all-files tables, and compute a displayable
typevalue for filtering. - Add multi-selection actions in the files action bar and implement bulk create-model/create-photosphere/remove-model flows in
FilesManager. - Add i18n tokens for plural notifications and model/photosphere labels, and display model/photosphere info in the filename cell.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/i18n/lang/fr.json | Adds new FileNameCell labels and plural/bulk notifications in French. |
| src/i18n/lang/en.json | Adds new FileNameCell labels and plural/bulk notifications in English. |
| src/components/specific/files/folder-table/columns.js | Switches “type” column from sortable to filterable. |
| src/components/specific/files/folder-table/FoldersTable.vue | Feeds formatted rows (with computed type) to enable “type” filtering. |
| src/components/specific/files/files-table/file-name-cell/FileNameCell.vue | Adds model/photosphere indicator tooltip/icon and related logic. |
| src/components/specific/files/files-manager/files-action-bar/FilesActionBar.vue | Adds bulk actions (create models/photospheres, remove models) for multi-selection. |
| src/components/specific/files/files-manager/FilesManager.vue | Implements bulk create/remove handlers and loading state propagation. |
| src/components/specific/files/all-files-table/columns.js | Switches “type” column to filterable (removes sort function). |
| src/components/specific/files/all-files-table/AllFilesTable.vue | Adds type filter UI, improves selection handling, and formats rows to include a computed type. |
| src/components/specific/files/all-files-table/AllFilesTable.scss | Adds missing positioning styling for the “type” header/filter container. |
Comments suppressed due to low confidence (3)
src/components/specific/files/all-files-table/AllFilesTable.vue:330
- When
fileExtension(file.name)returns an empty string (no extension), the current logic producestype: ""(because optional chaining still callsreplaceon ""). Add an explicit fallback so extensionless documents showt("t.file")(and folders showt("t.folder")).
: file.name
? fileExtension(file.name)?.replace(".", "").toUpperCase()
: t("t.file"),
src/components/specific/files/all-files-table/AllFilesTable.vue:371
- The header checkbox uses a length equality check against
displayedListFiles. With filters,selectionmay contain items not currently displayed, which forces an indeterminate state even if all displayed rows are selected. Compute this by checking whether everydisplayedListFilesitem is selected (by id) instead of comparing lengths.
const mainSelectionCheckboxValue = computed(() => {
if (props.selection.length === 0) {
return false;
} else if (props.selection.length === displayedListFiles.value.length) {
return true;
src/components/specific/files/files-manager/files-action-bar/FilesActionBar.vue:96
isModelis imported but never used in this component. Removing the unused import will avoid lint/build warnings and keep the module dependencies accurate.
import { computed } from "vue";
import { useToggle } from "../../../../../composables/toggle.js";
import { useUser } from "../../../../../state/user.js";
import { isFolder } from "../../../../../utils/file-structure.js";
import { isConvertible, isConvertibleToPhotosphere, isModel } from "../../../../../utils/models.js";
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const canConvertAllToModel = computed(() => | ||
| props.files.every( | ||
| (file) => !isFolder(file) && isConvertible(file) && file.nature === "Document", | ||
| ), | ||
| ); | ||
| const canConvertAllToPhotosphere = computed(() => | ||
| props.files.every( | ||
| (file) => !isFolder(file) && isConvertibleToPhotosphere(file) && file.nature === "Document", | ||
| ), |
There was a problem hiding this comment.
canConvertAllToModel / canConvertAllToPhotosphere are based on props.files.every(...), which evaluates to true when props.files is empty. This will show the create-model/photosphere actions even when nothing is selected. Add a props.files.length > 0 guard (similar to canRemoveAllModels).
| const canConvertAllToModel = computed(() => | |
| props.files.every( | |
| (file) => !isFolder(file) && isConvertible(file) && file.nature === "Document", | |
| ), | |
| ); | |
| const canConvertAllToPhotosphere = computed(() => | |
| props.files.every( | |
| (file) => !isFolder(file) && isConvertibleToPhotosphere(file) && file.nature === "Document", | |
| ), | |
| const canConvertAllToModel = computed( | |
| () => | |
| props.files.length > 0 && | |
| props.files.every( | |
| (file) => !isFolder(file) && isConvertible(file) && file.nature === "Document", | |
| ), | |
| ); | |
| const canConvertAllToPhotosphere = computed( | |
| () => | |
| props.files.length > 0 && | |
| props.files.every( | |
| (file) => | |
| !isFolder(file) && | |
| isConvertibleToPhotosphere(file) && | |
| file.nature === "Document", | |
| ), |
| selection.value = selection.value.map((f) => { | ||
| if (files.includes(f)) return { ...f, nature: "Model" }; | ||
| return f; | ||
| }); | ||
|
|
||
| loadingFileIds.value.push(...files.map((f) => f.id)); |
There was a problem hiding this comment.
createModelFromFiles optimistically sets selection items to nature: "Model" before the API calls and never rolls back on failure. If a conversion fails (or the request rejects), the UI can show items as models even though the backend state didn't change. Apply the UI update only after success, or revert it based on per-file results.
| if (!modelsToDelete.length) return; | ||
|
|
There was a problem hiding this comment.
If modelsToDelete.length is 0, the function returns early after having already changed selection and added loadingFileIds. This leaves UI state modified even though no delete occurred. Consider checking modelsToDelete before mutating UI state, or ensure you revert selection/loading before returning.
| position="right" | ||
| class="flex items-center" | ||
| > | ||
| <BIMDataIconSetAsModel v-if="file.nature === 'Model' || file.type === 'PHOTOSPHERE'" /> |
There was a problem hiding this comment.
The photosphere marker uses file.type === 'PHOTOSPHERE', but type is now used elsewhere as the displayed file extension ("PDF", etc.). Photosphere documents (model_type === 'PHOTOSPHERE' with nature === 'Document') won’t render the icon, making the tooltip effectively unreachable. Use file.model_type / MODEL_TYPE.PHOTOSPHERE (or isModel(file)) instead.
| <BIMDataIconSetAsModel v-if="file.nature === 'Model' || file.type === 'PHOTOSPHERE'" /> | |
| <BIMDataIconSetAsModel v-if="file.nature === 'Model' || file.model_type === 'PHOTOSPHERE'" /> |
| selection.value = selection.value.map((f) => { | ||
| if (files.includes(f)) return { ...f, nature: "Model" }; | ||
| return f; | ||
| }); |
There was a problem hiding this comment.
files.includes(f) relies on object identity; selection items and the files arg may be different object instances with the same id (especially now that some tables map/format rows). Compare by id instead (e.g., build a Set of ids) to ensure the correct rows are updated.
| selection.value = selection.value.map((f) => { | ||
| if (files.includes(f)) return { ...f, nature: "Document" }; | ||
| return f; | ||
| }); | ||
| loadingFileIds.value.push(...files.map((f) => f.id)); |
There was a problem hiding this comment.
removeModels optimistically mutates selection before deleting models and uses files.includes(f) (object identity). This can fail to update the intended rows and can also leave the UI in an incorrect state if deletion fails. Use id-based matching and update/revert selection based on actual deletion outcomes.
| return props.allFiles.map((file) => ({ | ||
| ...file, | ||
| type: | ||
| file.nature === "folder" |
There was a problem hiding this comment.
file.nature === "folder" doesn’t match the codebase’s folder nature ("Folder" / isFolder(file)). This will misclassify folders and produce incorrect type values for filtering/display. Use isFolder(file) or FILE_TYPE.FOLDER here.
| file.nature === "folder" | |
| file.nature === "Folder" |
| } | ||
| }; | ||
|
|
||
| const removeModels = async (files) => { |
There was a problem hiding this comment.
if files === selection.value, we can just use selection.value
| if (files.includes(f)) return { ...f, nature: "Document" }; | ||
| return f; | ||
| }); | ||
| loadingFileIds.value.push(...files.map((f) => f.id)); |
There was a problem hiding this comment.
Can we use a boolean to disable the button instead of maintaining the list of ids ?
https://platform-dev-model-ged.bimdata.io
Description
This PR introduces two improvements in the GED:
create models,create photospheres, andremove modelsactions based on multi-selectionTypes of changes
Checklist