UI: Improve multi OS display #205
@ -100,13 +100,120 @@
|
||||
const btnInstallAction = document.querySelector('.js-btn-install-action');
|
||||
const btnInstallGroup = document.querySelector('.js-btn-install-group');
|
||||
|
||||
const dropdownAllVersionsItemDot = document.querySelectorAll('.js-dropdown-all-versions-item-dot');
|
||||
const dropdownAllVersionsItemIcon = document.querySelectorAll('.js-dropdown-all-versions-item-icon');
|
||||
const dropdownAllVersionsWrapper = document.querySelector('.js-dropdown-all-versions-wrapper');
|
||||
|
||||
// Create variables multiple
|
||||
const btnInstallActionItem = document.querySelectorAll('.js-btn-install-action-item');
|
||||
|
||||
// Create function isArchARM
|
||||
function isArchARM() {
|
||||
// Check if userAgentData and getHighEntropyValues are supported
|
||||
if (navigator.userAgentData && navigator.userAgentData.getHighEntropyValues) {
|
||||
navigator.userAgentData
|
||||
.getHighEntropyValues(["architecture"])
|
||||
.then((values) => {
|
||||
// Extract the architecture value
|
||||
const arch = values.architecture;
|
||||
// Check if the architecture is ARM
|
||||
if (arch.toLowerCase().includes('arm')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback for browsers that do not support userAgentData or getHighEntropyValues
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (btnInstall == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide dropdownAllVersionsWrapper by default
|
||||
dropdownAllVersionsWrapper.classList.add('d-none');
|
||||
|
||||
// Show multi OS install buttons based on active platform
|
||||
if (btnInstallActionItem.length > 1) {
|
||||
// Show dropdownAllVersionsWrapper
|
||||
dropdownAllVersionsWrapper.classList.remove('d-none');
|
||||
|
||||
// Get active platform
|
||||
|
||||
/*
|
||||
The navigator objects' 'platform' proporties are arbitrary, and specific to the browser.
|
||||
|
||||
For a light match, we can use the first 3 characters of the platform string.
|
||||
*/
|
||||
|
||||
const activePlatform = navigator.platform.toLowerCase();
|
||||
const activePlatformPrefix = activePlatform.substring(0, 3);
|
||||
|
||||
btnInstallActionItem.forEach(function(item) {
|
||||
// Hide all items by default
|
||||
item.classList.add('d-none');
|
||||
|
||||
// Hide dropdownAllVersionsItemDot by default
|
||||
dropdownAllVersionsItemDot.forEach(function(item) {
|
||||
item.classList.add('d-none');
|
||||
});
|
||||
|
||||
// Style dropdownAllVersionsItemIcon by default
|
||||
dropdownAllVersionsItemIcon.forEach(function(item) {
|
||||
item.classList.add('text-muted');
|
||||
});
|
||||
|
||||
// Get item platform
|
||||
const itemPlatform = item.getAttribute('data-platform');
|
||||
const itemPlatformBase = itemPlatform.split('-')[0];
|
||||
const itemPlatformBasePrefix = itemPlatformBase.substring(0, 3);
|
||||
|
||||
// Show button if item platform matches active platform
|
||||
if (itemPlatformBasePrefix.startsWith(activePlatformPrefix)) {
|
||||
item.classList.add('btn-install-action-item-active');
|
||||
item.classList.remove('d-none');
|
||||
|
||||
// TODO: WIP show only relevant item for active macOS architecture, if architecture detection reliably works
|
||||
if (activePlatformPrefix.startsWith('mac')) {
|
||||
if (isArchARM()) {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: refactor and DRY code
|
||||
dropdownAllVersionsItemDot.forEach(function(item) {
|
||||
// TODO: create named function to not repeat this (optional)
|
||||
// Get item platform
|
||||
const itemPlatform = item.getAttribute('data-platform');
|
||||
const itemPlatformBase = itemPlatform.split('-')[0];
|
||||
const itemPlatformBasePrefix = itemPlatformBase.substring(0, 3);
|
||||
|
||||
// Show dropdownAllVersionsItemDot if item platform matches active platform
|
||||
if (itemPlatformBasePrefix.startsWith(activePlatformPrefix)) {
|
||||
item.classList.remove('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
dropdownAllVersionsItemIcon.forEach(function(item) {
|
||||
// Get item platform
|
||||
const itemPlatform = item.getAttribute('data-platform');
|
||||
const itemPlatformBase = itemPlatform.split('-')[0];
|
||||
const itemPlatformBasePrefix = itemPlatformBase.substring(0, 3);
|
||||
|
||||
// Show dropdownAllVersionsItemIcon if item platform matches active platform
|
||||
if (itemPlatformBasePrefix.startsWith(activePlatformPrefix)) {
|
||||
item.classList.remove('text-muted');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show btnInstallAction
|
||||
btnInstall.addEventListener('click', function() {
|
||||
// Hide btnInstallGroup
|
||||
|
@ -167,8 +167,9 @@
|
||||
.btn-install-action-item
|
||||
+margin(3, bottom)
|
||||
|
||||
&:last-child
|
||||
+margin(0, bottom)
|
||||
.btn-install-action-item-active ~ .btn-install-action-item-active
|
||||
border-top: thin solid var(--border-color)
|
||||
+padding(3, top)
|
||||
|
||||
.btn-install-drag,
|
||||
.btn-install-drag:active
|
||||
@ -474,3 +475,11 @@ a
|
||||
|
||||
.featured-image-preview
|
||||
width: 16rem
|
||||
|
||||
.dropdown-all-versions-item-dot
|
||||
background-color: var(--color-accent)
|
||||
border-radius: 50%
|
||||
height: var(--spacer-1)
|
||||
position: absolute
|
||||
transform: translateX(-1.2rem)
|
||||
width: var(--spacer-1)
|
||||
|
@ -35,6 +35,9 @@
|
||||
.mw-#{$i}
|
||||
min-width: var(--spacer-#{$i})
|
||||
|
||||
.list-style-none
|
||||
list-style: none
|
||||
|
||||
.opacity-50
|
||||
opacity: 0.5
|
||||
|
||||
|
28
extensions/migrations/0042_platform_name.py
Normal file
28
extensions/migrations/0042_platform_name.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Generated by Django 4.2.11 on 2024-05-14 11:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def update_platforms(apps, schema_editor):
|
||||
lookup = {
|
||||
"windows-x64": "Windows",
|
||||
"windows-arm64": "Windows Arm",
|
||||
"macos-x64": "macOS Intel",
|
||||
"macos-arm64": "macOS Apple Silicon",
|
||||
"linux-x64": "Linux",
|
||||
}
|
||||
Platform = apps.get_model('extensions', 'Platform')
|
||||
for ob in Platform.objects.all():
|
||||
ob.name = lookup.get(ob.slug, ob.name)
|
||||
ob.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extensions', '0041_remove_version_file'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_platforms),
|
||||
]
|
@ -64,6 +64,20 @@ class Platform(CreatedModifiedMixin, models.Model):
|
||||
def get_by_slug(cls, slug: str):
|
||||
return cls.objects.filter(slug=slug).first()
|
||||
|
||||
@property
|
||||
def name_first_word(self):
|
||||
"""Used for presentation in download_list."""
|
||||
return self.name.split(None, 1)[0]
|
||||
|
||||
@property
|
||||
def name_rest(self):
|
||||
"""Used for presentation in download_list."""
|
||||
parts = self.name.split(None, 1)
|
||||
if len(parts) > 1:
|
||||
return parts[1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class ExtensionManager(models.Manager):
|
||||
@property
|
||||
@ -706,6 +720,18 @@ class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
||||
return file
|
||||
return None
|
||||
|
||||
def get_supported_platforms(self):
|
||||
supported_platforms = {}
|
||||
for platform in self.platforms.all():
|
||||
architectures = supported_platforms.get(platform.name_first_word, [])
|
||||
architectures.append(platform.name_rest)
|
||||
supported_platforms[platform.name_first_word] = architectures
|
||||
result = []
|
||||
for platform, architectures in sorted(supported_platforms.items()):
|
||||
item = {'name': platform, 'architectures': sorted(architectures)}
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
@property
|
||||
def is_listed(self):
|
||||
# To be public, at least one version file must have a public status.
|
||||
@ -785,7 +811,7 @@ class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
||||
'url': self.get_download_url(file),
|
||||
}
|
||||
]
|
||||
platform2file = {}
|
||||
platform_slug2file = {}
|
||||
for file in files:
|
||||
platforms = file.get_platforms()
|
||||
if not platforms:
|
||||
@ -793,16 +819,17 @@ class Version(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
||||
f'data error: Version pk={self.pk} has multiple files, but File pk={file.pk} '
|
||||
f'is not platform-specific'
|
||||
)
|
||||
for platform in platforms:
|
||||
platform2file[platform] = file
|
||||
for platform_slug in platforms:
|
||||
platform_slug2file[platform_slug] = file
|
||||
all_platforms_by_slug = {p.slug: p for p in Platform.objects.all()}
|
||||
return [
|
||||
{
|
||||
'name': self.get_download_name(file),
|
||||
'platform': p,
|
||||
'platform': all_platforms_by_slug.get(platform_slug),
|
||||
'size': file.size_bytes,
|
||||
'url': self.get_download_url(file),
|
||||
}
|
||||
for p, file in platform2file.items()
|
||||
for platform_slug, file in sorted(platform_slug2file.items(), reverse=True)
|
||||
]
|
||||
|
||||
def get_build_list(self) -> List[dict]:
|
||||
|
@ -6,7 +6,7 @@
|
||||
{% has_maintainer extension as is_maintainer %}
|
||||
{% with latest=extension.latest_version %}
|
||||
|
||||
<div class="hero extension-detail">
|
||||
<div class="hero extension-detail position-relative z-2">
|
||||
<div class="container">
|
||||
<div class="hero-content">
|
||||
{% block hero_breadcrumbs %}
|
||||
|
@ -67,16 +67,13 @@
|
||||
</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="dl-col">
|
||||
<dt>{% trans 'Size' %}</dt>
|
||||
{% if version.has_single_file %}
|
||||
<dd>{{ version.files.first.size_bytes|filesizeformat }}</dd>
|
||||
{% else %}
|
||||
{% for file in version.files.all %}
|
||||
<dd>{{ file.size_bytes|filesizeformat }} ({{ file.get_platforms|join:", " }})</dd>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if version.has_single_file %}
|
||||
<div class="dl-col">
|
||||
<dt>{% trans 'Size' %}</dt>
|
||||
<dd>{{ version.files.first.size_bytes|filesizeformat }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="dl-row">
|
||||
|
@ -1,16 +1,18 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% if version.platforms.all %}
|
||||
{% with supported_platforms=version.get_supported_platforms %}
|
||||
{% if supported_platforms %}
|
||||
<div class="dl-row">
|
||||
<div class="dl-col">
|
||||
<dt>{% trans "Supported Platforms" %}</dt>
|
||||
<dd>
|
||||
<ul class="mb-0 ps-3">
|
||||
{% for p in version.platforms.all %}
|
||||
<li>{{p.name}}</li>
|
||||
{% endfor %}
|
||||
<ul class="list-style-none mb-0 ps-0">
|
||||
{% for item in supported_platforms|dictsortreversed:"name" %}
|
||||
<li><i class="i-{{ item.name|lower }}"></i> {{ item.name }} <span class="text-muted">{{ item.architectures|join:', ' }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
@ -110,7 +110,7 @@
|
||||
|
||||
{# Sidebar #}
|
||||
<div class="col-md-4">
|
||||
<aside class="is-sticky pt-2">
|
||||
<aside class="is-sticky pt-2 z-1">
|
||||
|
||||
{# Info Summary #}
|
||||
{% block extension_summary_sidebar %}
|
||||
@ -185,23 +185,13 @@
|
||||
<dt>{% trans 'Downloads' %}</dt>
|
||||
<dd>{{ extension.download_count }}</dd>
|
||||
</div>
|
||||
<div class="dl-col">
|
||||
<dt>{% trans 'Size' %}</dt>
|
||||
{% if latest.has_single_file %}
|
||||
|
||||
{% if latest.has_single_file %}
|
||||
<div class="dl-col">
|
||||
<dt>{% trans 'Size' %}</dt>
|
||||
<dd>{{ latest.files.first.size_bytes|filesizeformat }}</dd>
|
||||
{% else %}
|
||||
<dd>
|
||||
<ul class="ps-3">
|
||||
{% for file in latest.files.all %}
|
||||
<li class="lh-sm">
|
||||
{{ file.size_bytes|filesizeformat }}<br>
|
||||
<span class="fs-sm text-muted">({{ file.get_platforms|join:", " }})</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="dl-row">
|
||||
@ -272,12 +262,27 @@
|
||||
</div>
|
||||
<div class="fade js-btn-install-action">
|
||||
{% for download_item in download_list %}
|
||||
<div class="btn-install-action-item js-btn-install-action-item" data-install-url="{{ request.scheme }}://{{ request.get_host }}{{ download_item.url }}" download="{{ download_item.name }}">
|
||||
<div class="btn-install-action-item js-btn-install-action-item" data-install-url="{{ request.scheme }}://{{ request.get_host }}{{ download_item.url }}" data-platform="{{ download_item.platform.slug }}" download="{{ download_item.name }}">
|
||||
<div class="btn-install-drag-group js-btn-install-drag-group mb-2 rounded">
|
||||
<button class="btn btn-flex btn-primary btn-install-drag cursor-move js-btn-install-drag w-100" draggable="true">
|
||||
<button class="btn btn-flex btn-primary btn-install-drag cursor-move js-btn-install-drag mb-2 w-100" draggable="true">
|
||||
<i class="i-move"></i>
|
||||
<span>{% trans 'Drag and Drop into Blender' %} <span class="fs-sm">{{ download_item.platform }}</span></span>
|
||||
<span>{% trans 'Drag and Drop into Blender' %}
|
||||
</span>
|
||||
</button>
|
||||
<div class="text-center">
|
||||
{% if download_list|length > 1 %}
|
||||
<strong>
|
||||
{% with platform=download_item.platform %}
|
||||
{{ platform.name_first_word }}
|
||||
|
||||
{% if platform.name_rest %}
|
||||
<span class="fw-normal"> - {{ platform.name_rest }}</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</strong>
|
||||
– {{ download_item.size|filesizeformat }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<small class="d-block text-center w-100">
|
||||
{# TODO @front-end: Replace URL of the manual /dev/ with /latest/. #}
|
||||
@ -286,6 +291,27 @@
|
||||
</small>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="dropdown js-dropdown-all-versions-wrapper">
|
||||
<button class="btn btn-link dropdown-toggle js-dropdown-toggle w-100" data-toggle-menu-id="js-dropdown-all-versions">
|
||||
</i> {% trans 'Other versions' %}<i class="i-chevron-down"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu js-dropdown-menu w-100" id="js-dropdown-all-versions">
|
||||
{% for download_item in download_list %}
|
||||
<li>
|
||||
<a class="align-items-center d-flex dropdown-item justify-content-between" download="{{ download_item.name }}" href="{{ request.scheme }}://{{ request.get_host }}{{ download_item.url }}">
|
||||
{% with platform=download_item.platform %}
|
||||
<span class="dropdown-all-versions-item-dot js-dropdown-all-versions-item-dot" data-platform="{{ platform.name_first_word|lower|slice:"3" }}"></span>
|
||||
<span>
|
||||
<i class="i-{{ platform.name_first_word|lower }} js-dropdown-all-versions-item-icon me-0" data-platform="{{ platform.name_first_word|lower|slice:"3" }}"></i> {{ platform.name_first_word }} <span class="text-muted">{{ platform.name_rest}}</span>
|
||||
</span>
|
||||
{% endwith %}
|
||||
<span class="text-muted">{{ download_item.size|filesizeformat }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# If JavaScript is disabled. #}
|
||||
@ -294,7 +320,7 @@
|
||||
<div class="btn-col text-center">
|
||||
{% for download_item in download_list %}
|
||||
<a class="btn btn-flex btn-accent" href="{{ request.scheme }}://{{ request.get_host }}{{ download_item.url }}" download="{{ download_item.name }}">
|
||||
<i class="i-download"></i><span>{% trans 'Download' %} {{ extension.get_type_display }} {{ download_item.platform }}</span>
|
||||
<i class="i-download"></i><span>{% trans 'Download' %} {{ extension.get_type_display }} {{ download_item.platform.slug }}</span>
|
||||
</a>
|
||||
<small class="mt-3">...and <a class="text-underline text-primary text-center" href="https://docs.blender.org/manual/en/dev/editors/preferences/extensions.html#install" target="_blank">Install from Disk</a></small>
|
||||
{% endfor %}
|
||||
|
@ -75,16 +75,12 @@
|
||||
<dt>Downloads</dt>
|
||||
<dd>{{ version.download_count }}</dd>
|
||||
</div>
|
||||
<div class="dl-col">
|
||||
<dt>Size</dt>
|
||||
{% if version.has_single_file %}
|
||||
<dd>{{ version.files.first.size_bytes|filesizeformat }}</dd>
|
||||
{% else %}
|
||||
{% for file in version.files.all %}
|
||||
<dd>{{ file.size_bytes|filesizeformat }} ({{ file.get_platforms|join:", " }})</dd>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if version.has_single_file %}
|
||||
<div class="dl-col">
|
||||
<dt>Size</dt>
|
||||
<dd>{{ version.files.first.size_bytes|filesizeformat }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="dl-row">
|
||||
<div class="dl-col">
|
||||
@ -115,9 +111,14 @@
|
||||
<div class="btn-col">
|
||||
{% with download_list=version.get_download_list %}
|
||||
{% for download_item in download_list %}
|
||||
<a href="{{ download_item.url }}" download="{{ download_item.name }}" class="btn btn-primary btn-block">
|
||||
<i class="i-download"></i>
|
||||
<span>{% trans 'Download' %} v{{ version.version }} {{ download_item.platform }}</span>
|
||||
<a href="{{ download_item.url }}" download="{{ download_item.name }}" class="btn btn-primary btn-block d-flex justify-content-between text-start">
|
||||
<span>
|
||||
{% with platform=download_item.platform %}
|
||||
<i class="i-{{ platform.name_first_word|lower }} me-1"></i> {{ platform.name }}
|
||||
{% endwith %}
|
||||
v{{ version.version }}
|
||||
</span>
|
||||
<span>{{ download_item.size|filesizeformat }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
|
Loading…
Reference in New Issue
Block a user