From bfae838639e28e196e25117f568d2747298d1de2 Mon Sep 17 00:00:00 2001 From: PouyaMohseni Date: Sun, 5 Apr 2026 16:51:41 -0400 Subject: [PATCH 1/2] fix: reorder instrument names based on creation date --- .../instruments/views/instrument_detail.py | 104 +++++++++++++----- 1 file changed, 74 insertions(+), 30 deletions(-) diff --git a/web-app/django/VIM/apps/instruments/views/instrument_detail.py b/web-app/django/VIM/apps/instruments/views/instrument_detail.py index 18add74b..4b751a43 100644 --- a/web-app/django/VIM/apps/instruments/views/instrument_detail.py +++ b/web-app/django/VIM/apps/instruments/views/instrument_detail.py @@ -4,6 +4,8 @@ from django.http import Http404 from django.views.generic import DetailView from VIM.apps.instruments.models import Instrument, Language +from django.db.models import Case, When, Value +from collections import defaultdict, OrderedDict class InstrumentDetail(DetailView): @@ -34,47 +36,89 @@ def get_object(self, queryset=None): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - # Query the instrument names in all languages + # Get the active language from the session; fallback to English. + active_language_en = self.request.session.get("active_language_en", "English") + try: + active_lang = Language.objects.get(en_label=active_language_en) + except Language.DoesNotExist: + active_lang = Language.objects.get(en_label="English") + context["active_language"] = active_lang + + # Query the instrument names in all languages, sorted instrument_names = ( - context["instrument"].instrumentname_set.all().select_related("language") + context["instrument"] + .instrumentname_set.all() + .select_related("language") + .annotate( + active_lang_first=Case( + When(language=active_lang, then=Value(1)), + default=Value(0), + ) + ) + .order_by( + "-active_lang_first", # active_lang matched names come first for display + "-umil_label", + "id", # earlier created names come first + ) ) + + # Determine label in the active language, prioritizing umil_label and earliest created + active_label_qs = instrument_names.filter(language=active_lang, umil_label=True) + if active_label_qs.exists(): + context["active_instrument_label"] = active_label_qs.earliest("id") + elif instrument_names.filter(language=active_lang).exists(): + context["active_instrument_label"] = instrument_names.filter( + language=active_lang + ).earliest("id") + else: + context["active_instrument_label"] = "" + + # Control visibility of names: verified only for guests if self.request.user.is_authenticated: - # Show all names for authenticated users - context["instrument_names"] = instrument_names.all() + instrument_names_visible = instrument_names.all() else: - # Show only verified names for unauthenticated users - context["instrument_names"] = instrument_names.filter( + instrument_names_visible = instrument_names.filter( verification_status="verified" ) + context["instrument_names"] = instrument_names_visible - # Initialize a dictionary to store label and aliases by language - label_aliases_dict = {} + # Build names by language + names_by_lang = defaultdict(list) for instrumentname in instrument_names: - language = instrumentname.language - if language not in label_aliases_dict: - label_aliases_dict[language] = {"label": None, "aliases": []} - if instrumentname.umil_label: - label_aliases_dict[language]["label"] = instrumentname + names_by_lang[instrumentname.language].append(instrumentname) + + # Build the language order: active first (if present), then all others by earliest InstrumentName creation + lang_candidate_order = [ + lang for lang in names_by_lang.keys() if lang != active_lang + ] + # Sort non-active languages by the minimum id of their names + lang_candidate_order.sort( + key=lambda language: min(n.id for n in names_by_lang[language]) + ) + lang_order = [active_lang] if active_lang in names_by_lang else [] + lang_order.extend(lang_candidate_order) + + label_aliases_dict = OrderedDict() + for language in lang_order: + namelist = names_by_lang[language] + label = None + aliases = [] + + # Find earliest umil_label for label + umil_labels = [n for n in namelist if n.umil_label] + if umil_labels: + label = sorted(umil_labels, key=lambda n: n.id)[0] + aliases = [n for n in namelist if n != label] else: - label_aliases_dict[language]["aliases"].append(instrumentname) - context["label_aliases_dict"] = label_aliases_dict + # No umil_label, use earliest name as label + ordered = sorted(namelist, key=lambda n: n.id) + if ordered: + label = ordered[0] + aliases = ordered[1:] - # Get the active language - active_language_en = self.request.session.get("active_language_en", None) - context["active_language"] = ( - Language.objects.get(en_label=active_language_en) - if active_language_en - else Language.objects.get(en_label="English") # default in English - ) + label_aliases_dict[language] = {"label": label, "aliases": aliases} - # Get the instrument label in the active language - # Set label to the first instrument name added in the language if there is no "umil_label" set - active_labels = instrument_names.filter(language=context["active_language"]) - umil_label = active_labels.filter(umil_label=True) - if umil_label.exists(): - context["active_instrument_label"] = umil_label.first() - else: - context["active_instrument_label"] = active_labels.first() + context["label_aliases_dict"] = label_aliases_dict # Get all languages for the dropdown context["languages"] = json.dumps( From 2059903d4a5a1dedec5d08b3d16dab6d0731ffc2 Mon Sep 17 00:00:00 2001 From: PouyaMohseni Date: Fri, 10 Apr 2026 01:01:39 -0400 Subject: [PATCH 2/2] fix: add a remove btn when image is added in the create page - Add a sentence to guide in selecting files when a file is already selected. resolves #535 --- .../VIM/templates/instruments/create.html | 22 +++- .../helpers/CreateInstrumentManager.ts | 101 +++++++++++------- 2 files changed, 79 insertions(+), 44 deletions(-) diff --git a/web-app/django/VIM/templates/instruments/create.html b/web-app/django/VIM/templates/instruments/create.html index ee9673a5..20f638d7 100644 --- a/web-app/django/VIM/templates/instruments/create.html +++ b/web-app/django/VIM/templates/instruments/create.html @@ -93,11 +93,23 @@
Image (Optional)
- +
+ + +
+
+ To select a different image, click on “Choose File” again. +
Accepted formats: JPEG, PNG, GIF, WebP.
diff --git a/web-app/frontend/src/instruments/helpers/CreateInstrumentManager.ts b/web-app/frontend/src/instruments/helpers/CreateInstrumentManager.ts index c5411491..ac7244a7 100644 --- a/web-app/frontend/src/instruments/helpers/CreateInstrumentManager.ts +++ b/web-app/frontend/src/instruments/helpers/CreateInstrumentManager.ts @@ -57,55 +57,78 @@ export class CreateInstrumentManager { * Toggles image source field visibility based on image selection */ setupImageFieldToggle(): void { - const imageInput = document.getElementById('image') as HTMLInputElement; + const imageInput = document.getElementById( + 'image', + ) as HTMLInputElement | null; const imageSourceContainer = document.getElementById( 'imageSourceContainer', ); const imagePreview = document.getElementById( 'imagePreview', - ) as HTMLImageElement; + ) as HTMLImageElement | null; const imagePreviewContainer = document.getElementById( 'imagePreviewContainer', ); + const deleteBtn = document.getElementById( + 'deleteImageBtn', + ) as HTMLButtonElement | null; + const selectDifferentText = document.getElementById( + 'selectDifferentImageText', + ); - if (imageInput && imageSourceContainer) { - imageInput.addEventListener('change', () => { - const files = imageInput.files; - if (files && files.length > 0) { - imageSourceContainer.classList.remove('d-none'); - const sourceInput = imageSourceContainer.querySelector( - 'input', - ) as HTMLInputElement; - if (sourceInput) { - sourceInput.required = true; - } - - // Show image preview - if (imagePreview && imagePreviewContainer) { - const reader = new FileReader(); - reader.onload = (e) => { - imagePreview.src = e.target?.result as string; - imagePreviewContainer.classList.remove('d-none'); - }; - reader.readAsDataURL(files[0]); - } - } else { - imageSourceContainer.classList.add('d-none'); - const sourceInput = imageSourceContainer.querySelector( - 'input', - ) as HTMLInputElement; - if (sourceInput) { - sourceInput.required = false; - sourceInput.value = ''; - } - - // Hide image preview - if (imagePreviewContainer) { - imagePreviewContainer.classList.add('d-none'); - } - } - }); + // Check for absence once + if ( + !imageInput || + !imageSourceContainer || + !imagePreview || + !imagePreviewContainer || + !deleteBtn || + !selectDifferentText + ) + return; + + const getSourceInput = () => + imageSourceContainer.querySelector('input') as HTMLInputElement | null; + + function resetImageInput() { + imageInput.value = ''; + imagePreview.src = ''; + imagePreviewContainer.classList.add('d-none'); + deleteBtn.style.display = 'none'; + selectDifferentText.classList.add('d-none'); + imageSourceContainer.classList.add('d-none'); + const sourceInput = getSourceInput(); + if (sourceInput) { + sourceInput.required = false; + sourceInput.value = ''; + } } + + imageInput.addEventListener('change', function () { + const file = imageInput.files && imageInput.files[0]; + if (file) { + // Show and require source + imageSourceContainer.classList.remove('d-none'); + const sourceInput = getSourceInput(); + if (sourceInput) sourceInput.required = true; + + // Preview and UI + const reader = new FileReader(); + reader.onload = function (e) { + imagePreview.src = e.target?.result as string; + imagePreviewContainer.classList.remove('d-none'); + }; + reader.readAsDataURL(file); + deleteBtn.style.display = ''; + selectDifferentText.classList.remove('d-none'); + } else { + resetImageInput(); + } + }); + + deleteBtn.addEventListener('click', function () { + resetImageInput(); + }); } /**