Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 74 additions & 30 deletions web-app/django/VIM/apps/instruments/views/instrument_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand Down
22 changes: 17 additions & 5 deletions web-app/django/VIM/templates/instruments/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,23 @@ <h5>Image (Optional)</h5>
<div class="row">
<div class="col-md-6 mb-3">
<label for="image" class="form-label">Image File</label>
<input type="file"
class="form-control"
id="image"
name="image"
accept="image/jpeg,image/png,image/gif,image/webp" />
<div class="input-group">
<input type="file"
class="form-control"
id="image"
name="image"
accept="image/jpeg,image/png,image/gif,image/webp" />
<button type="button"
class="btn btn-outline-danger"
id="deleteImageBtn"
style="display: none"
tabindex="-1">
<i class="bi bi-trash"></i> Delete
</button>
</div>
<div class="form-text d-none" id="selectDifferentImageText">
To select a different image, click on “Choose File” again.
</div>
<div class="form-text">
Accepted formats: JPEG, PNG, GIF, WebP.
<br />
Expand Down
101 changes: 62 additions & 39 deletions web-app/frontend/src/instruments/helpers/CreateInstrumentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
}

/**
Expand Down
Loading