Use different default ace theme depending on site theme

This commit is contained in:
Evan 2023-02-07 20:10:25 -05:00 committed by Tudor Brindus
parent 861fcaaf26
commit e00396fc2a
12 changed files with 82 additions and 8 deletions

View File

@ -76,6 +76,8 @@
editor = ace.edit(div), editor = ace.edit(div),
mode = widget.getAttribute('data-mode'), mode = widget.getAttribute('data-mode'),
theme = widget.getAttribute('data-theme'), theme = widget.getAttribute('data-theme'),
default_light_theme = widget.getAttribute('data-default-light-theme'),
default_dark_theme = widget.getAttribute('data-default-dark-theme'),
wordwrap = widget.getAttribute('data-wordwrap'), wordwrap = widget.getAttribute('data-wordwrap'),
toolbar = prev(widget), toolbar = prev(widget),
main_block = toolbar.parentNode; main_block = toolbar.parentNode;
@ -98,6 +100,21 @@
} }
if (theme) { if (theme) {
editor.setTheme("ace/theme/" + theme); editor.setTheme("ace/theme/" + theme);
} else {
if (window.matchMedia) {
const setEditorTheme = function (is_dark) {
if (is_dark) {
editor.setTheme("ace/theme/" + default_dark_theme);
} else {
editor.setTheme("ace/theme/" + default_light_theme);
}
}
setEditorTheme(window.matchMedia('(prefers-color-scheme: dark)').matches);
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(ev) {
setEditorTheme(ev.matches);
})
}
} }
if (wordwrap == "true") { if (wordwrap == "true") {
editor.getSession().setUseWrapMode(true); editor.getSession().setUseWrapMode(true);

View File

@ -42,6 +42,8 @@ class AceWidget(forms.Textarea):
ace_attrs['data-mode'] = self.mode ace_attrs['data-mode'] = self.mode
if self.theme: if self.theme:
ace_attrs['data-theme'] = self.theme ace_attrs['data-theme'] = self.theme
ace_attrs['data-default-light-theme'] = settings.ACE_DEFAULT_LIGHT_THEME
ace_attrs['data-default-dark-theme'] = settings.ACE_DEFAULT_DARK_THEME
if self.wordwrap: if self.wordwrap:
ace_attrs['data-wordwrap'] = 'true' ace_attrs['data-wordwrap'] = 'true'

View File

@ -106,6 +106,11 @@ DMOJ_THEME_CSS = {
'light': 'style.css', 'light': 'style.css',
'dark': 'dark/style.css', 'dark': 'dark/style.css',
} }
# At the bare minimum, dark and light ace themes must be declared
DMOJ_THEME_DEFAULT_ACE_THEME = {
'light': 'github',
'dark': 'twilight',
}
DMOJ_SELECT2_THEME = 'dmoj' DMOJ_SELECT2_THEME = 'dmoj'
MARKDOWN_STYLES = {} MARKDOWN_STYLES = {}
@ -601,3 +606,7 @@ except IOError:
# Check settings are consistent # Check settings are consistent
assert DMOJ_PROBLEM_MIN_USER_POINTS_VOTE >= DMOJ_PROBLEM_MIN_PROBLEM_POINTS assert DMOJ_PROBLEM_MIN_USER_POINTS_VOTE >= DMOJ_PROBLEM_MIN_PROBLEM_POINTS
# Compute these values after local_settings.py is loaded
ACE_DEFAULT_LIGHT_THEME = DMOJ_THEME_DEFAULT_ACE_THEME['light']
ACE_DEFAULT_DARK_THEME = DMOJ_THEME_DEFAULT_ACE_THEME['dark']

View File

@ -308,7 +308,9 @@ class ContestAdmin(NoBatchDeleteMixin, VersionAdmin):
if 'problem_label_script' in form.base_fields: if 'problem_label_script' in form.base_fields:
# form.base_fields['problem_label_script'] does not exist when the user has only view permission # form.base_fields['problem_label_script'] does not exist when the user has only view permission
# on the model. # on the model.
form.base_fields['problem_label_script'].widget = AceWidget('lua', request.profile.ace_theme) form.base_fields['problem_label_script'].widget = AceWidget(
mode='lua', theme=request.profile.resolved_ace_theme,
)
perms = ('edit_own_contest', 'edit_all_contest') perms = ('edit_own_contest', 'edit_all_contest')
form.base_fields['curators'].queryset = Profile.objects.filter( form.base_fields['curators'].queryset = Profile.objects.filter(

View File

@ -126,5 +126,7 @@ class ProfileAdmin(NoBatchDeleteMixin, VersionAdmin):
form = super(ProfileAdmin, self).get_form(request, obj, **kwargs) form = super(ProfileAdmin, self).get_form(request, obj, **kwargs)
if 'user_script' in form.base_fields: if 'user_script' in form.base_fields:
# form.base_fields['user_script'] does not exist when the user has only view permission on the model. # form.base_fields['user_script'] does not exist when the user has only view permission on the model.
form.base_fields['user_script'].widget = AceWidget('javascript', request.profile.ace_theme) form.base_fields['user_script'].widget = AceWidget(
mode='javascript', theme=request.profile.resolved_ace_theme,
)
return form return form

View File

@ -27,7 +27,9 @@ class LanguageAdmin(VersionAdmin):
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
form = super(LanguageAdmin, self).get_form(request, obj, **kwargs) form = super(LanguageAdmin, self).get_form(request, obj, **kwargs)
if obj is not None: if obj is not None:
form.base_fields['template'].widget = AceWidget(obj.ace, request.profile.ace_theme) form.base_fields['template'].widget = AceWidget(
mode=obj.ace, theme=request.profile.resolved_ace_theme,
)
return form return form

View File

@ -107,8 +107,9 @@ class SubmissionSourceInline(admin.StackedInline):
extra = 0 extra = 0
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
kwargs.setdefault('widgets', {})['source'] = AceWidget(mode=obj and obj.language.ace, kwargs.setdefault('widgets', {})['source'] = AceWidget(
theme=request.profile.ace_theme) mode=obj and obj.language.ace, theme=request.profile.resolved_ace_theme,
)
return super().get_formset(request, obj, **kwargs) return super().get_formset(request, obj, **kwargs)

View File

@ -92,7 +92,7 @@ class ProfileForm(ModelForm):
) )
if not self.fields['organizations'].queryset: if not self.fields['organizations'].queryset:
self.fields.pop('organizations') self.fields.pop('organizations')
self.fields['user_script'].widget = AceWidget(theme=user.profile.ace_theme, mode='javascript') self.fields['user_script'].widget = AceWidget(mode='javascript', theme=user.profile.resolved_ace_theme)
class DownloadDataForm(Form): class DownloadDataForm(Form):

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.16 on 2023-02-08 01:11
from django.db import migrations, models
def github_to_auto(apps, schema_editor):
Profile = apps.get_model('judge', 'Profile')
Profile.objects.filter(ace_theme='github').update(ace_theme='auto')
def auto_to_github(apps, schema_editor):
Profile = apps.get_model('judge', 'Profile')
Profile.objects.filter(ace_theme='auto').update(ace_theme='github')
class Migration(migrations.Migration):
dependencies = [
('judge', '0137_profile_site_theme'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='ace_theme',
field=models.CharField(choices=[('auto', 'Follow theme default'), ('ambiance', 'Ambiance'), ('chaos', 'Chaos'), ('chrome', 'Chrome'), ('clouds', 'Clouds'), ('clouds_midnight', 'Clouds Midnight'), ('cobalt', 'Cobalt'), ('crimson_editor', 'Crimson Editor'), ('dawn', 'Dawn'), ('dreamweaver', 'Dreamweaver'), ('eclipse', 'Eclipse'), ('github', 'Github'), ('idle_fingers', 'Idle Fingers'), ('katzenmilch', 'Katzenmilch'), ('kr_theme', 'KR Theme'), ('kuroir', 'Kuroir'), ('merbivore', 'Merbivore'), ('merbivore_soft', 'Merbivore Soft'), ('mono_industrial', 'Mono Industrial'), ('monokai', 'Monokai'), ('pastel_on_dark', 'Pastel on Dark'), ('solarized_dark', 'Solarized Dark'), ('solarized_light', 'Solarized Light'), ('terminal', 'Terminal'), ('textmate', 'Textmate'), ('tomorrow', 'Tomorrow'), ('tomorrow_night', 'Tomorrow Night'), ('tomorrow_night_blue', 'Tomorrow Night Blue'), ('tomorrow_night_bright', 'Tomorrow Night Bright'), ('tomorrow_night_eighties', 'Tomorrow Night Eighties'), ('twilight', 'Twilight'), ('vibrant_ink', 'Vibrant Ink'), ('xcode', 'XCode')], default='auto', max_length=30, verbose_name='Ace theme'),
),
migrations.RunPython(github_to_auto, auto_to_github, atomic=True),
]

View File

@ -21,6 +21,7 @@ TIMEZONE = make_timezones()
del make_timezones del make_timezones
ACE_THEMES = ( ACE_THEMES = (
('auto', _('Follow theme default')),
('ambiance', 'Ambiance'), ('ambiance', 'Ambiance'),
('chaos', 'Chaos'), ('chaos', 'Chaos'),
('chrome', 'Chrome'), ('chrome', 'Chrome'),

View File

@ -154,7 +154,7 @@ class Profile(models.Model):
points = models.FloatField(default=0, db_index=True) points = models.FloatField(default=0, db_index=True)
performance_points = models.FloatField(default=0, db_index=True) performance_points = models.FloatField(default=0, db_index=True)
problem_count = models.IntegerField(default=0, db_index=True) problem_count = models.IntegerField(default=0, db_index=True)
ace_theme = models.CharField(max_length=30, verbose_name=_('Ace theme'), choices=ACE_THEMES, default='github') ace_theme = models.CharField(max_length=30, verbose_name=_('Ace theme'), choices=ACE_THEMES, default='auto')
site_theme = models.CharField(max_length=10, verbose_name=_('site theme'), choices=SITE_THEMES, default='auto') site_theme = models.CharField(max_length=10, verbose_name=_('site theme'), choices=SITE_THEMES, default='auto')
last_access = models.DateTimeField(verbose_name=_('last access time'), default=now) last_access = models.DateTimeField(verbose_name=_('last access time'), default=now)
ip = models.GenericIPAddressField(verbose_name=_('last IP'), blank=True, null=True) ip = models.GenericIPAddressField(verbose_name=_('last IP'), blank=True, null=True)
@ -226,6 +226,15 @@ class Profile(models.Model):
def has_any_solves(self): def has_any_solves(self):
return self.submission_set.filter(result='AC', case_points__gte=F('case_total')).exists() return self.submission_set.filter(result='AC', case_points__gte=F('case_total')).exists()
@cached_property
def resolved_ace_theme(self):
if self.ace_theme != 'auto':
return self.ace_theme
if self.site_theme != 'auto':
return settings.DMOJ_THEME_DEFAULT_ACE_THEME.get(self.site_theme)
# This must be resolved client-side using prefers-color-scheme.
return None
_pp_table = [pow(settings.DMOJ_PP_STEP, i) for i in range(settings.DMOJ_PP_ENTRIES)] _pp_table = [pow(settings.DMOJ_PP_STEP, i) for i in range(settings.DMOJ_PP_ENTRIES)]
def calculate_points(self, table=_pp_table): def calculate_points(self, table=_pp_table):

View File

@ -691,7 +691,7 @@ class ProblemSubmit(LoginRequiredMixin, ProblemMixin, TitleMixin, SingleObjectFo
form_data = getattr(form, 'cleaned_data', form.initial) form_data = getattr(form, 'cleaned_data', form.initial)
if 'language' in form_data: if 'language' in form_data:
form.fields['source'].widget.mode = form_data['language'].ace form.fields['source'].widget.mode = form_data['language'].ace
form.fields['source'].widget.theme = self.request.profile.ace_theme form.fields['source'].widget.theme = self.request.profile.resolved_ace_theme
return form return form