Skip to content

Commit 41acc85

Browse files
Allow changing the mirador theme and primary/secondary colors (#64)
* Allow changing the theme and primary/secondary colors * fix tests * Allow respecting OS settings for theme * remove unnecessary conditional * Update config/schema/islandora_mirador.schema.yml Co-authored-by: Adam <607975+adam-vessey@users.noreply.github.com> --------- Co-authored-by: Adam <607975+adam-vessey@users.noreply.github.com>
1 parent 2643c89 commit 41acc85

7 files changed

Lines changed: 332 additions & 5 deletions

File tree

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
mirador_library_installation_type: 'remote'
2-
mirador_enabled_plugins:
2+
mirador_enabled_plugins:
33
miradorImageToolsPlugin: miradorImageToolsPlugin
44
textOverlayPlugin: textOverlayPlugin
55
iiif_manifest_url: '[node:url:unaliased:absolute]/manifest'
66
mirador_library_minified: false
77
mirador_language_support: false
8+
mirador_selected_theme: 'light'
9+
mirador_theme_light_primary: '#1967d2'
10+
mirador_theme_light_secondary: '#1967d2'
11+
mirador_theme_dark_primary: '#4db6ac'
12+
mirador_theme_dark_secondary: '#4db6ac'

config/schema/islandora_mirador.schema.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,18 @@ islandora_mirador.settings:
2020
mirador_language_support:
2121
type: boolean
2222
label: 'Enable language support'
23+
mirador_selected_theme:
24+
type: string
25+
label: 'Selected Mirador theme'
26+
mirador_theme_light_primary:
27+
type: color_hex
28+
label: 'Light theme primary color'
29+
mirador_theme_light_secondary:
30+
type: color_hex
31+
label: 'Light theme secondary color'
32+
mirador_theme_dark_primary:
33+
type: color_hex
34+
label: 'Dark theme primary color'
35+
mirador_theme_dark_secondary:
36+
type: color_hex
37+
label: 'Dark theme secondary color'

islandora_mirador.module

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,31 @@ function template_preprocess_mirador(&$variables) {
7474
$variables['#cache']['contexts'][] = 'languages:language_interface';
7575
}
7676

77+
// Build theme configuration from settings.
78+
$selected_theme = $config->get('mirador_selected_theme');
79+
$theme_config = [];
80+
$light_primary = $config->get('mirador_theme_light_primary');
81+
$light_secondary = $config->get('mirador_theme_light_secondary');
82+
$dark_primary = $config->get('mirador_theme_dark_primary');
83+
$dark_secondary = $config->get('mirador_theme_dark_secondary');
84+
if ($light_primary) {
85+
$theme_config['light']['palette']['primary']['main'] = $light_primary;
86+
}
87+
if ($light_secondary) {
88+
$theme_config['light']['palette']['secondary']['main'] = $light_secondary;
89+
}
90+
if ($dark_primary) {
91+
$theme_config['dark']['palette']['primary']['main'] = $dark_primary;
92+
}
93+
if ($dark_secondary) {
94+
$theme_config['dark']['palette']['secondary']['main'] = $dark_secondary;
95+
}
96+
7797
// mirador.viewers is an associative array mapping CSS/jQuery/once selectors
7898
// to objects of mirador configuration.
79-
$variables['#attached']['drupalSettings']['mirador']['viewers']["#{$variables['mirador_view_id']}"] = [
99+
$viewer_config = [
80100
'id' => $variables['mirador_view_id'],
101+
'selectedTheme' => $selected_theme,
81102
'language' => $language,
82103
'manifests' => [
83104
$variables['iiif_manifest_url'] => [
@@ -93,6 +114,10 @@ function template_preprocess_mirador(&$variables) {
93114
],
94115
'workspace' => $variables['workspace_config'],
95116
];
117+
if (!empty($theme_config)) {
118+
$viewer_config['themes'] = $theme_config;
119+
}
120+
$variables['#attached']['drupalSettings']['mirador']['viewers']["#{$variables['mirador_view_id']}"] = $viewer_config;
96121
}
97122

98123
/**

islandora_mirador.post_update.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,30 @@ function islandora_mirador_post_update_enable_plugins() {
3434
$config->save(TRUE);
3535
}
3636
}
37+
38+
/**
39+
* Set default theme settings for existing sites.
40+
*/
41+
function islandora_mirador_post_update_theme_defaults() {
42+
$config = \Drupal::configFactory()->getEditable('islandora_mirador.settings');
43+
44+
$defaults = [
45+
'mirador_selected_theme' => 'light',
46+
'mirador_theme_light_primary' => '#1967d2',
47+
'mirador_theme_light_secondary' => '#1967d2',
48+
'mirador_theme_dark_primary' => '#4db6ac',
49+
'mirador_theme_dark_secondary' => '#4db6ac',
50+
];
51+
52+
$changed = FALSE;
53+
foreach ($defaults as $key => $value) {
54+
if ($config->get($key) === NULL) {
55+
$config->set($key, $value);
56+
$changed = TRUE;
57+
}
58+
}
59+
60+
if ($changed) {
61+
$config->save(TRUE);
62+
}
63+
}

js/mirador_viewer.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616
Drupal.IslandoraMirador.instances = Drupal.IslandoraMirador.instances || {}
1717
Object.entries(settings.mirador.viewers).forEach(entry => {
1818
const [base, values] = entry;
19-
once('mirador-viewer', base, context).forEach(() =>
19+
once('mirador-viewer', base, context).forEach(() => {
20+
// Resolve 'system' theme to the OS preference at render time.
21+
const resolvedValues = Object.assign({}, values);
22+
if (resolvedValues.selectedTheme === 'system') {
23+
resolvedValues.selectedTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
24+
}
2025
// save the mirador instance so other modules can interact
2126
// with the store/actions at e.g. Drupal.IslandoraMirador.instances["#mirador-xyz"].store
22-
Drupal.IslandoraMirador.instances[base] = Mirador.viewer(values, window.miradorPlugins || {})
23-
);
27+
Drupal.IslandoraMirador.instances[base] = Mirador.viewer(resolvedValues, window.miradorPlugins || {});
28+
});
2429
});
2530
},
2631
detach: function (context, settings) {

src/Form/MiradorConfigForm.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,57 @@ public function buildForm(array $form, FormStateInterface $form_state) {
7979
'#options' => $plugins,
8080
'#default_value' => $config->get('mirador_enabled_plugins'),
8181
];
82+
$form['mirador_theme_fieldset'] = [
83+
'#type' => 'fieldset',
84+
'#title' => $this->t('Theme'),
85+
'#description' => $this->t('Configure the Mirador viewer theme and color palette. See the <a href=":url">Mirador theming documentation</a> for more information.', [
86+
':url' => 'https://github.com/ProjectMirador/mirador/wiki/M3-Theming-Mirador',
87+
]),
88+
];
89+
$form['mirador_theme_fieldset']['mirador_selected_theme'] = [
90+
'#type' => 'radios',
91+
'#title' => $this->t('Selected theme'),
92+
'#options' => [
93+
'light' => $this->t('Light'),
94+
'dark' => $this->t('Dark'),
95+
'system' => $this->t("Use OS setting (follows the browser's light/dark preference)"),
96+
],
97+
'#default_value' => $config->get('mirador_selected_theme'),
98+
];
99+
$form['mirador_theme_fieldset']['mirador_theme_advanced'] = [
100+
'#type' => 'details',
101+
'#title' => $this->t('Advanced theming'),
102+
'#open' => FALSE,
103+
];
104+
$form['mirador_theme_fieldset']['mirador_theme_advanced']['mirador_theme_light'] = [
105+
'#type' => 'fieldset',
106+
'#title' => $this->t('Light theme colors'),
107+
];
108+
$form['mirador_theme_fieldset']['mirador_theme_advanced']['mirador_theme_light']['mirador_theme_light_primary'] = [
109+
'#type' => 'color',
110+
'#title' => $this->t('Primary color'),
111+
'#default_value' => $config->get('mirador_theme_light_primary'),
112+
];
113+
$form['mirador_theme_fieldset']['mirador_theme_advanced']['mirador_theme_light']['mirador_theme_light_secondary'] = [
114+
'#type' => 'color',
115+
'#title' => $this->t('Secondary color'),
116+
'#default_value' => $config->get('mirador_theme_light_secondary'),
117+
];
118+
$form['mirador_theme_fieldset']['mirador_theme_advanced']['mirador_theme_dark'] = [
119+
'#type' => 'fieldset',
120+
'#title' => $this->t('Dark theme colors'),
121+
];
122+
$form['mirador_theme_fieldset']['mirador_theme_advanced']['mirador_theme_dark']['mirador_theme_dark_primary'] = [
123+
'#type' => 'color',
124+
'#title' => $this->t('Primary color'),
125+
'#default_value' => $config->get('mirador_theme_dark_primary'),
126+
];
127+
$form['mirador_theme_fieldset']['mirador_theme_advanced']['mirador_theme_dark']['mirador_theme_dark_secondary'] = [
128+
'#type' => 'color',
129+
'#title' => $this->t('Secondary color'),
130+
'#default_value' => $config->get('mirador_theme_dark_secondary'),
131+
];
132+
82133
$form['iiif_manifest_url_fieldset'] = [
83134
'#type' => 'fieldset',
84135
'#title' => $this->t('IIIF Manifest URL'),
@@ -102,6 +153,31 @@ public function buildForm(array $form, FormStateInterface $form_state) {
102153
return $form;
103154
}
104155

156+
/**
157+
* {@inheritdoc}
158+
*/
159+
public function validateForm(array &$form, FormStateInterface $form_state) {
160+
parent::validateForm($form, $form_state);
161+
162+
$theme = $form_state->getValue('mirador_selected_theme');
163+
if (!in_array($theme, ['light', 'dark', 'system'], TRUE)) {
164+
$form_state->setErrorByName('mirador_selected_theme', $this->t('Theme must be "light", "dark", or "system".'));
165+
}
166+
167+
$color_fields = [
168+
'mirador_theme_light_primary',
169+
'mirador_theme_light_secondary',
170+
'mirador_theme_dark_primary',
171+
'mirador_theme_dark_secondary',
172+
];
173+
foreach ($color_fields as $field) {
174+
$value = $form_state->getValue($field);
175+
if ($value !== NULL && $value !== '' && !preg_match('/^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/', $value)) {
176+
$form_state->setErrorByName($field, $this->t('Color must be a valid CSS hex color (e.g. #fff or #1967d2).'));
177+
}
178+
}
179+
}
180+
105181
/**
106182
* {@inheritdoc}
107183
*/
@@ -112,6 +188,12 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
112188
$config->set('iiif_manifest_url', $form_state->getValue('iiif_manifest_url'));
113189
$config->set('mirador_library_minified', $form_state->getValue('mirador_library_minified'));
114190
$config->set('mirador_language_support', $form_state->getValue('mirador_language_support'));
191+
$config->set('mirador_selected_theme', $form_state->getValue('mirador_selected_theme'));
192+
$config->set('mirador_theme_light_primary', $form_state->getValue('mirador_theme_light_primary'));
193+
$config->set('mirador_theme_light_secondary', $form_state->getValue('mirador_theme_light_secondary'));
194+
$config->set('mirador_theme_dark_primary', $form_state->getValue('mirador_theme_dark_primary'));
195+
$config->set('mirador_theme_dark_secondary', $form_state->getValue('mirador_theme_dark_secondary'));
196+
115197
$config->save();
116198
parent::submitForm($form, $form_state);
117199
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
3+
namespace Drupal\Tests\islandora_mirador\Kernel;
4+
5+
use Drupal\Core\Form\FormState;
6+
use Drupal\KernelTests\KernelTestBase;
7+
use Drupal\islandora_mirador\Form\MiradorConfigForm;
8+
9+
/**
10+
* Tests the MiradorConfigForm validation.
11+
*
12+
* @group islandora_mirador
13+
*/
14+
class MiradorConfigFormTest extends KernelTestBase {
15+
16+
/**
17+
* {@inheritdoc}
18+
*/
19+
protected static $modules = [
20+
'islandora_mirador',
21+
'system',
22+
];
23+
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
protected function setUp(): void {
28+
parent::setUp();
29+
$this->installConfig(['islandora_mirador']);
30+
}
31+
32+
/**
33+
* Returns a form object with valid base values for theme fields.
34+
*/
35+
protected function validValues(): array {
36+
return [
37+
'mirador_selected_theme' => 'light',
38+
'mirador_theme_light_primary' => '#1967d2',
39+
'mirador_theme_light_secondary' => '#1967d2',
40+
'mirador_theme_dark_primary' => '#4db6ac',
41+
'mirador_theme_dark_secondary' => '#4db6ac',
42+
];
43+
}
44+
45+
/**
46+
* Runs validateForm on a MiradorConfigForm with the given values.
47+
*/
48+
protected function validateWithValues(array $values): FormState {
49+
$form_object = MiradorConfigForm::create($this->container);
50+
$form_state = new FormState();
51+
$form_state->setValues($values);
52+
$form = [];
53+
$form_object->validateForm($form, $form_state);
54+
return $form_state;
55+
}
56+
57+
/**
58+
* Tests that valid theme and colors pass validation.
59+
*/
60+
public function testValidLightTheme(): void {
61+
$form_state = $this->validateWithValues($this->validValues());
62+
$this->assertFalse($form_state->hasAnyErrors());
63+
}
64+
65+
/**
66+
* Tests that dark theme is also valid.
67+
*/
68+
public function testValidDarkTheme(): void {
69+
$values = $this->validValues();
70+
$values['mirador_selected_theme'] = 'dark';
71+
$form_state = $this->validateWithValues($values);
72+
$this->assertFalse($form_state->hasAnyErrors());
73+
}
74+
75+
/**
76+
* Tests that the system theme option is valid.
77+
*/
78+
public function testValidSystemTheme(): void {
79+
$values = $this->validValues();
80+
$values['mirador_selected_theme'] = 'system';
81+
$form_state = $this->validateWithValues($values);
82+
$this->assertFalse($form_state->hasAnyErrors());
83+
}
84+
85+
/**
86+
* Tests that a 3-digit hex color is valid.
87+
*/
88+
public function testValidShortHexColor(): void {
89+
$values = $this->validValues();
90+
$values['mirador_theme_light_primary'] = '#fff';
91+
$form_state = $this->validateWithValues($values);
92+
$this->assertFalse($form_state->hasAnyErrors());
93+
}
94+
95+
/**
96+
* Tests that an invalid theme value fails validation.
97+
*/
98+
public function testInvalidTheme(): void {
99+
$values = $this->validValues();
100+
$values['mirador_selected_theme'] = 'blue';
101+
$form_state = $this->validateWithValues($values);
102+
$this->assertTrue($form_state->hasAnyErrors());
103+
$errors = $form_state->getErrors();
104+
$this->assertArrayHasKey('mirador_selected_theme', $errors);
105+
}
106+
107+
/**
108+
* Tests that a color without a leading # fails validation.
109+
*/
110+
public function testInvalidColorMissingHash(): void {
111+
$values = $this->validValues();
112+
$values['mirador_theme_light_primary'] = '1967d2';
113+
$form_state = $this->validateWithValues($values);
114+
$this->assertTrue($form_state->hasAnyErrors());
115+
$errors = $form_state->getErrors();
116+
$this->assertArrayHasKey('mirador_theme_light_primary', $errors);
117+
}
118+
119+
/**
120+
* Tests that a color with invalid hex characters fails validation.
121+
*/
122+
public function testInvalidColorBadCharacters(): void {
123+
$values = $this->validValues();
124+
$values['mirador_theme_dark_secondary'] = '#zzzzzz';
125+
$form_state = $this->validateWithValues($values);
126+
$this->assertTrue($form_state->hasAnyErrors());
127+
$errors = $form_state->getErrors();
128+
$this->assertArrayHasKey('mirador_theme_dark_secondary', $errors);
129+
}
130+
131+
/**
132+
* Tests that a color with wrong length fails validation.
133+
*/
134+
public function testInvalidColorWrongLength(): void {
135+
$values = $this->validValues();
136+
$values['mirador_theme_dark_primary'] = '#12345';
137+
$form_state = $this->validateWithValues($values);
138+
$this->assertTrue($form_state->hasAnyErrors());
139+
$errors = $form_state->getErrors();
140+
$this->assertArrayHasKey('mirador_theme_dark_primary', $errors);
141+
}
142+
143+
/**
144+
* Tests that multiple invalid fields each produce their own error.
145+
*/
146+
public function testMultipleInvalidFields(): void {
147+
$values = $this->validValues();
148+
$values['mirador_selected_theme'] = 'invalid';
149+
$values['mirador_theme_light_secondary'] = 'notacolor';
150+
$form_state = $this->validateWithValues($values);
151+
$errors = $form_state->getErrors();
152+
$this->assertArrayHasKey('mirador_selected_theme', $errors);
153+
$this->assertArrayHasKey('mirador_theme_light_secondary', $errors);
154+
}
155+
156+
/**
157+
* Tests that config defaults are set correctly on fresh install.
158+
*/
159+
public function testConfigDefaults(): void {
160+
$config = $this->config('islandora_mirador.settings');
161+
$this->assertEquals('light', $config->get('mirador_selected_theme'));
162+
$this->assertEquals('#1967d2', $config->get('mirador_theme_light_primary'));
163+
$this->assertEquals('#1967d2', $config->get('mirador_theme_light_secondary'));
164+
$this->assertEquals('#4db6ac', $config->get('mirador_theme_dark_primary'));
165+
$this->assertEquals('#4db6ac', $config->get('mirador_theme_dark_secondary'));
166+
}
167+
168+
}

0 commit comments

Comments
 (0)