Skip to content

Commit

Permalink
✨ add dept filter
Browse files Browse the repository at this point in the history
  • Loading branch information
ctcpip committed Jun 25, 2024
1 parent 1db0305 commit 3accbb8
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 70 deletions.
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>New Tab</title>
Expand All @@ -8,7 +8,7 @@
<link rel="stylesheet" type="text/css" href="style.css" />
<script src="openseadragon.min.js"></script>
<script src="log-ga.js" type="module"></script>
<script src="script.js"></script>
<script src="script.js" type="module"></script>
</head>
<body>
<a id="artwork-url">
Expand Down
117 changes: 117 additions & 0 deletions lib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const apiURL = 'https://api.artic.edu/api/v1/search';
const noDepartmentTerm = 'None (No Department Association)';

function getJson(body, callback, forceNew) {
let request = new XMLHttpRequest();
request.open('POST', apiURL, true);
request.setRequestHeader('Content-Type', 'application/json');
request.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
callback(JSON.parse(this.responseText), forceNew);
}
};
request.send(JSON.stringify(body));
}

function getJsonData(body) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('request timed out');
}, 30 * 1000);
getJson(body, (data) => {
resolve(data);
});
});
}

function escape(s) {
const bad1 = /&/g;
const good1 = '&amp;';

const bad2 = /</g;
const good2 = '&lt;';

const bad3 = />/g;
const good3 = '&gt;';

const bad4 = /"/g;
const good4 = '&quot;';

const bad5 = /'/g;
const good5 = '&apos;';

// prettier-ignore
return s
.replace(bad1, good1)
.replace(bad2, good2)
.replace(bad3, good3)
.replace(bad4, good4)
.replace(bad5, good5);
}

function merge(target, source) {
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {};
}
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}

// initialize settings with defaults
const settings = {
dailyMode: false,
departmentOptions: {
options: [],
lastFetched: null,
selected: [],
},
};

// load settings if they exist
const extensionSettingsKey = 'extensionSettings';
merge(settings, JSON.parse(localStorage.getItem(extensionSettingsKey)));
saveSettings(); // save in case there are new default settings or settings are not persisted yet

function getSettings() {
// make sure we are always reading from localStorage so we don't load stale data
return new Proxy(settings, {
get(target, property) {
const s = JSON.parse(localStorage.getItem(extensionSettingsKey));
return s ? s[property] : null;
},
});
}

function saveSettings() {
localStorage.setItem(extensionSettingsKey, JSON.stringify(settings));
}

const filterFields = {
department: 'department_title.keyword',
};

const artworkCacheKeys = {
// LocalStorage keys for reference
savedResponseKey: 'response',
preloadedImagesKey: 'preloaded',
preloadingImagesKey: 'preloading',
lastLoadedDateKey: 'lastLoadedDate',
};

// prettier-ignore
export {
artworkCacheKeys,
escape,
filterFields,
getJson,
getJsonData,
getSettings,
merge,
noDepartmentTerm,
saveSettings,
};
32 changes: 32 additions & 0 deletions options.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
body {
background: #21212c;
color: lightgrey;
font-family: sans-serif;
font-size: 16px;
padding: 0 32px;
}

label,
legend,
select
{
font-size: 24px;
}

select {
margin: 0 24px;
}

.setting {
margin: 24px 0;
padding: 24px 12px;
}

.checkbox {
display: block;
margin: 12px;

input {
margin-right: 12px;
}
}
43 changes: 13 additions & 30 deletions options.html
Original file line number Diff line number Diff line change
@@ -1,39 +1,22 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>Art Institute of Chicago: Art Tab - Options</title>
<style>
body {
background: #21212c;
color: lightgrey;
font-family: sans-serif;
font-size: 16px;
padding: 0 32px;
}
label, select {
font-size: 24px;
}
select {
margin: 0 24px;
}
.setting {
margin: 24px 0;
}
</style>
<link rel="stylesheet" type="text/css" href="options.css" />
</head>
<body>
<h1>Art Institute of Chicago: Art Tab - Options</h1>
<hr>
<div class="setting">
<label for="daily">Daily mode:</label>
<fieldset class="setting">
<legend>Daily mode</legend>
<select id="daily">
<option value="false" selected>No</option>
<option value="true">Yes</option>
</select>
<span>Reload artwork image once per day instead of on every new tab</span>
</div>
<hr>

<script src="options.js"></script>
<option value="false" selected>No</option>
<option value="true">Yes</option>
</select>
<span>Reload artwork image once per day instead of on every new tab</span>
</fieldset>
<fieldset class="setting" id="departments">
<legend>Department Filter</legend>
</fieldset>
<script src="options.js" type="module"></script>
</body>
</html>
89 changes: 84 additions & 5 deletions options.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,89 @@
const extensionSettingsKey = 'extensionSettings';
const settings = JSON.parse(localStorage.getItem(extensionSettingsKey)) || {};
// prettier-ignore
import {
artworkCacheKeys,
escape,
filterFields,
getJsonData,
getSettings,
noDepartmentTerm,
saveSettings,
} from './lib.js';

const contemporaryArt = 'Contemporary Art';

const baseQuery = {
resources: 'artworks',
size: 0,
aggregations: {},
};

const departmentQuery = Object.assign({}, baseQuery);
departmentQuery.aggregations = {
departments: {
terms: {
field: filterFields.department,
},
},
};

const settings = getSettings();

const selectDaily = document.querySelector('#daily');
selectDaily.value = settings.dailyMode;

selectDaily.addEventListener('change', e => {
selectDaily.addEventListener('change', (e) => {
settings.dailyMode = e.target.value === 'true';
localStorage.setItem(extensionSettingsKey, JSON.stringify(settings));
})
save();
});

const oneWeekMs = 7 * 24 * 60 * 60 * 1000;
const lastFetchedMoreThanAWeekAgo = (Date.now() - settings.departmentOptions.lastFetched) > oneWeekMs;

if (settings.departmentOptions.options.length === 0 || lastFetchedMoreThanAWeekAgo) {
const departmentData = await getJsonData(departmentQuery);
// prettier-ignore
const departmentOptions = departmentData.aggregations.departments.buckets
.map((b) => b.key)
.filter(o => o !== contemporaryArt) // for some reason, filtering on contemporary art yields zero results, despite there being many artworks with that department
.sort();
departmentOptions.push(noDepartmentTerm);
settings.departmentOptions.options = departmentOptions;
settings.departmentOptions.lastFetched = Date.now();
save();
}

const divDepartments = document.getElementById('departments');

for (const o of settings.departmentOptions.options) {
const sanitized = escape(o);
const template = document.createElement('div');
template.innerHTML = `<label class="checkbox"><input type="checkbox" value="${sanitized}">${sanitized}</label>`;
divDepartments.append(...template.children);
}

function updateDepartment(e) {
settings.departmentOptions.selected = Array.from(divDepartments.querySelectorAll('input:checked')).map(
(i) => i.value
);
save();
// clear cached artwork data so that preferences are respected immediately
Object.values(artworkCacheKeys).forEach(k => localStorage.removeItem(k));
}

if (settings.departmentOptions.selected.length === 0) {
divDepartments.querySelectorAll('input').forEach((i) => (i.checked = true));
} else {
settings.departmentOptions.selected.forEach((o) => {
const option = divDepartments.querySelector(`[value="${o}"]`);
// guard against options disappearing or being renamed
if(option) {
option.checked = true;
}
});
}

divDepartments.querySelectorAll('input').forEach((i) => i.addEventListener('change', updateDepartment));

function save() {
saveSettings();
}
Loading

0 comments on commit 3accbb8

Please sign in to comment.