summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/app/api/crud.actions.js90
-rw-r--r--frontend/app/utils/index.js628
-rw-r--r--frontend/app/views/tile/handles/tile.image.js81
-rw-r--r--frontend/site/projects/museum/app/index.js94
-rw-r--r--frontend/site/projects/museum/constants.js114
-rw-r--r--frontend/site/projects/museum/export.js2
-rw-r--r--frontend/site/projects/museum/views/artists.css31
-rw-r--r--frontend/site/projects/museum/views/home.js91
8 files changed, 635 insertions, 496 deletions
diff --git a/frontend/app/api/crud.actions.js b/frontend/app/api/crud.actions.js
index 86c2948..54fc96d 100644
--- a/frontend/app/api/crud.actions.js
+++ b/frontend/app/api/crud.actions.js
@@ -1,51 +1,55 @@
-import { crud_fetch } from 'app/api/crud.fetch'
-import { as_type } from 'app/api/crud.types'
-import { upload_action } from 'app/api/crud.upload'
-import { store } from 'app/store'
+import { crud_fetch } from "app/api/crud.fetch";
+import { as_type } from "app/api/crud.types";
+import { upload_action } from "app/api/crud.upload";
+import { store } from "app/store";
export function crud_actions(type) {
- const fetch_type = crud_fetch(type)
- return [
- 'index',
- 'show',
- 'create',
- 'update',
- 'destroy',
- ].reduce((lookup, param) => {
- lookup[param] = crud_action(type, param, (q) => fetch_type[param](q))
- return lookup
- }, {
- action: (method, fn) => crud_action(type, method, fn),
- upload: (fd) => upload_action(type, fd),
- updateOption: (key, value) => dispatch => {
- dispatch({ type: as_type(type, 'update_option'), key, value })
+ const fetch_type = crud_fetch(type);
+ return ["index", "show", "create", "update", "destroy"].reduce(
+ (lookup, param) => {
+ lookup[param] = crud_action(type, param, (q) => fetch_type[param](q));
+ return lookup;
},
- updateOptions: opt => dispatch => {
- dispatch({ type: as_type(type, 'update_options'), opt })
- },
- })
+ {
+ action: (method, fn) => crud_action(type, method, fn),
+ upload: (fd) => upload_action(type, fd),
+ updateOption: (key, value) => (dispatch) => {
+ dispatch({ type: as_type(type, "update_option"), key, value });
+ },
+ updateOptions: (opt) => (dispatch) => {
+ dispatch({ type: as_type(type, "update_options"), opt });
+ },
+ }
+ );
}
-export const crud_action = (type, method, fn) => (q, load_more) => dispatch => {
- return new Promise ((resolve, reject) => {
- if (method === 'index') {
+export const crud_action = (type, method, fn) => (q, load_more) => (
+ dispatch
+) => {
+ return new Promise((resolve, reject) => {
+ if (method === "index") {
if (store.getState()[type].index.loading) {
- return resolve({})
+ return resolve({});
}
}
- dispatch({ type: as_type(type, method + '_loading'), load_more })
- fn(q).then(data => {
- if (data.status === 'ok') {
- dispatch({ type: as_type(type, method), data, load_more })
- resolve(data)
- } else {
- dispatch({ type: as_type(type, method + '_error'), error: data.error })
- reject(data)
- }
- }).catch(e => {
- console.log(e)
- dispatch({ type: as_type(type, method + '_error') })
- reject(e)
- })
- })
-}
+ dispatch({ type: as_type(type, method + "_loading"), load_more });
+ fn(q)
+ .then((data) => {
+ if (data?.status === "ok") {
+ dispatch({ type: as_type(type, method), data, load_more });
+ resolve(data);
+ } else {
+ dispatch({
+ type: as_type(type, method + "_error"),
+ error: data.error,
+ });
+ reject(data);
+ }
+ })
+ .catch((e) => {
+ console.log(e);
+ dispatch({ type: as_type(type, method + "_error") });
+ reject(e);
+ });
+ });
+};
diff --git a/frontend/app/utils/index.js b/frontend/app/utils/index.js
index fc12ee3..5ae979d 100644
--- a/frontend/app/utils/index.js
+++ b/frontend/app/utils/index.js
@@ -1,25 +1,30 @@
-import { api as api_type } from 'app/types'
+import { api as api_type } from "app/types";
// import { format, formatDistance } from 'date-fns'
-import format from 'date-fns/format'
-import formatDistance from 'date-fns/formatDistance'
+import format from "date-fns/format";
+import formatDistance from "date-fns/formatDistance";
-export const formatDateTime = dateStr => format(new Date(dateStr), 'd MMM yyyy H:mm')
-export const formatDate = dateStr => format(new Date(dateStr), 'd MMM yyyy')
-export const formatTime = dateStr => format(new Date(dateStr), 'H:mm')
-export const formatAge = dateStr => formatDistance(new Date(), new Date(dateStr)) + ' ago.'
-export const unslugify = fn => fn.replace(/-/g, ' ').replace(/_/g, ' ').replace('.mp3', '')
+export const formatDateTime = (dateStr) =>
+ format(new Date(dateStr), "d MMM yyyy H:mm");
+export const formatDate = (dateStr) => format(new Date(dateStr), "d MMM yyyy");
+export const formatTime = (dateStr) => format(new Date(dateStr), "H:mm");
+export const formatAge = (dateStr) =>
+ formatDistance(new Date(), new Date(dateStr)) + " ago.";
+export const unslugify = (fn) =>
+ fn.replace(/-/g, " ").replace(/_/g, " ").replace(".mp3", "");
/* Mobile check */
-export const isiPhone = !!((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)))
-export const isiPad = !!(navigator.userAgent.match(/iPad/i))
-export const isAndroid = !!(navigator.userAgent.match(/Android/i))
-export const isMobile = isiPhone || isiPad || isAndroid
-export const isDesktop = !isMobile
+export const isiPhone = !!(
+ navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i)
+);
+export const isiPad = !!navigator.userAgent.match(/iPad/i);
+export const isAndroid = !!navigator.userAgent.match(/Android/i);
+export const isMobile = isiPhone || isiPad || isAndroid;
+export const isDesktop = !isMobile;
-const htmlClassList = document.body.parentNode.classList
-htmlClassList.add(isDesktop ? 'desktop' : 'mobile')
+const htmlClassList = document.body.parentNode.classList;
+htmlClassList.add(isDesktop ? "desktop" : "mobile");
/* Default image dimensions */
@@ -28,399 +33,440 @@ export const widths = {
sm: 320,
md: 640,
lg: 1280,
-}
+};
/* Formatting functions */
-const acronyms = 'id url cc sa fp md5 sha256'.split(' ').map(s => '_' + s)
-const acronymsUpperCase = acronyms.map(s => s.toUpperCase())
+const acronyms = "id url cc sa fp md5 sha256".split(" ").map((s) => "_" + s);
+const acronymsUpperCase = acronyms.map((s) => s.toUpperCase());
-export const formatName = s => {
- acronyms.forEach((acronym, i) => s = s.replace(acronym, acronymsUpperCase[i]))
- return s.replace(/_/g, ' ')
-}
+export const formatName = (s) => {
+ acronyms.forEach(
+ (acronym, i) => (s = s.replace(acronym, acronymsUpperCase[i]))
+ );
+ return s.replace(/_/g, " ");
+};
// Use to pad frame numbers with zeroes
export const pad = (n, m) => {
- let s = String(n || 0)
+ let s = String(n || 0);
while (s.length < m) {
- s = '0' + s
+ s = "0" + s;
}
- return s
-}
+ return s;
+};
-export const courtesyS = (n, s) => n + ' ' + (n === 1 ? s : s + 's')
-export const capitalize = s => s.split(' ').map(capitalizeWord).join(' ')
-export const capitalizeWord = s => s.substr(0, 1).toUpperCase() + s.substr(1)
-export const padSeconds = n => n < 10 ? '0' + n : n
+export const courtesyS = (n, s) => n + " " + (n === 1 ? s : s + "s");
+export const capitalize = (s) => s.split(" ").map(capitalizeWord).join(" ");
+export const capitalizeWord = (s) => s.substr(0, 1).toUpperCase() + s.substr(1);
+export const padSeconds = (n) => (n < 10 ? "0" + n : n);
export const commatize = (n, radix) => {
- radix = radix || -1
- var nums = [], i, counter = 0, r = Math.floor
+ radix = radix || -1;
+ var nums = [],
+ i,
+ counter = 0,
+ r = Math.floor;
if (radix !== -1 && n > radix) {
- n /= radix
- nums.unshift(r((n * 10) % 10))
- nums.unshift(".")
+ n /= radix;
+ nums.unshift(r((n * 10) % 10));
+ nums.unshift(".");
}
do {
- i = r(n % 10)
- n = r(n / 10)
- counter += 1
- if (n && ! (counter % 3))
- { i = ',' + r(i) }
- nums.unshift(i)
- }
- while (n)
- return nums.join("")
-}
+ i = r(n % 10);
+ n = r(n / 10);
+ counter += 1;
+ if (n && !(counter % 3)) {
+ i = "," + r(i);
+ }
+ nums.unshift(i);
+ } while (n);
+ return nums.join("");
+};
export const timestamp = (n = 0, fps = 25) => {
- n /= fps
- let s = padSeconds(Math.round(n) % 60)
- n = Math.floor(n / 60)
+ n /= fps;
+ let s = padSeconds(Math.round(n) % 60);
+ n = Math.floor(n / 60);
if (n > 60) {
- return Math.floor(n / 60) + ':' + padSeconds(n % 60) + ':' + s
+ return Math.floor(n / 60) + ":" + padSeconds(n % 60) + ":" + s;
}
- return (n % 60) + ':' + s
-}
-export const timestampToSeconds = time_str => {
- const time_str_parts = (time_str || "").trim().split(":").map(s => parseFloat(s))
+ return (n % 60) + ":" + s;
+};
+export const timestampToSeconds = (time_str) => {
+ const time_str_parts = (time_str || "")
+ .trim()
+ .split(":")
+ .map((s) => parseFloat(s));
if (time_str_parts.length === 3) {
- return (time_str_parts[0] * 60 + time_str_parts[1]) * 60 + time_str_parts[2]
+ return (
+ (time_str_parts[0] * 60 + time_str_parts[1]) * 60 + time_str_parts[2]
+ );
}
if (time_str_parts.length === 2) {
- return time_str_parts[0] * 60 + time_str_parts[1]
+ return time_str_parts[0] * 60 + time_str_parts[1];
}
- return time_str_parts[0]
-}
+ return time_str_parts[0];
+};
-export const percent = n => (n * 100).toFixed(1) + '%'
+export const percent = (n) => (n * 100).toFixed(1) + "%";
-export const px = (n, w) => Math.round(n * w) + 'px'
+export const px = (n, w) => Math.round(n * w) + "px";
-export const clamp = (n, a=0, b=1) => n < a ? a : n < b ? n : b
-export const dist = (x1, y1, x2, y2) => Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
-export const mod = (n, m) => n - (m * Math.floor(n / m))
-export const angle = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1)
+export const clamp = (n, a = 0, b = 1) => (n < a ? a : n < b ? n : b);
+export const dist = (x1, y1, x2, y2) =>
+ Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
+export const mod = (n, m) => n - m * Math.floor(n / m);
+export const angle = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);
-export const randint = n => Math.floor(Math.random() * n)
+export const randint = (n) => Math.floor(Math.random() * n);
/* URLs */
-export const sha256_tree = (sha256, branch_size=2, tree_depth=2) => {
- const tree_size = tree_depth * branch_size
- let tree = ""
+export const sha256_tree = (sha256, branch_size = 2, tree_depth = 2) => {
+ const tree_size = tree_depth * branch_size;
+ let tree = "";
for (var i = 0; i < tree_size; i += branch_size) {
- tree += '/' + sha256.substr(i, branch_size)
+ tree += "/" + sha256.substr(i, branch_size);
}
- return tree
-}
+ return tree;
+};
-export const imageUrl = (sha256, frame, size = 'th') => [
- 'https://' + process.env.S3_HOST + '/v1/media/keyframes',
- sha256_tree(sha256),
- pad(frame, 6),
- size,
- 'index.jpg'
-].filter(s => !!s).join('/')
+export const imageUrl = (sha256, frame, size = "th") =>
+ [
+ "https://" + process.env.S3_HOST + "/v1/media/keyframes",
+ sha256_tree(sha256),
+ pad(frame, 6),
+ size,
+ "index.jpg",
+ ]
+ .filter((s) => !!s)
+ .join("/");
-export const uploadUri = ({ sha256, ext }) => '/static/data/uploads' + sha256_tree(sha256) + '/' + sha256 + ext
-export const metadataUri = (sha256, tag) => '/metadata/' + sha256 + '/' + tag + '/'
-export const keyframeUri = (sha256, frame) => '/metadata/' + sha256 + '/keyframe/' + pad(frame, 6) + '/'
+export const uploadUri = ({ sha256, ext }) =>
+ "/static/data/uploads" + sha256_tree(sha256) + "/" + sha256 + ext;
+export const metadataUri = (sha256, tag) =>
+ "/metadata/" + sha256 + "/" + tag + "/";
+export const keyframeUri = (sha256, frame) =>
+ "/metadata/" + sha256 + "/keyframe/" + pad(frame, 6) + "/";
-export const preloadImage = url => (
+export const preloadImage = (url) =>
new Promise((resolve, reject) => {
- const image = new Image()
- let loaded = false
+ const image = new Image();
+ let loaded = false;
image.onload = () => {
- if (loaded) return
- loaded = true
- image.onload = null
- image.onerror = null
- resolve(image)
- }
+ if (loaded) return;
+ loaded = true;
+ image.onload = null;
+ image.onerror = null;
+ console.log(image);
+ resolve(image);
+ };
image.onerror = () => {
- if (loaded) return
- image.onload = null
- image.onerror = null
- resolve(image)
- }
+ if (loaded) return;
+ image.onload = null;
+ image.onerror = null;
+ resolve(image);
+ };
// console.log(img.src)
// image.crossOrigin = 'anonymous'
- image.src = url
+ image.src = url;
if (image.complete) {
- image.onload()
+ image.onload();
}
- })
-)
+ });
-export const preloadVideo = (url, options = {}) => (
+export const preloadVideo = (url, options = {}) =>
new Promise((resolve, reject) => {
- const { canplaythrough } = options
- const video = document.createElement('video')
- let loaded = false
+ const { canplaythrough } = options;
+ const video = document.createElement("video");
+ let loaded = false;
const bind = () => {
- video.addEventListener(canplaythrough ? 'canplaythrough' : 'loadedmetadata', onload)
- video.addEventListener('error', onerror)
- }
+ video.addEventListener(
+ canplaythrough ? "canplaythrough" : "loadedmetadata",
+ onload
+ );
+ video.addEventListener("error", onerror);
+ };
const unbind = () => {
- video.removeEventListener(canplaythrough ? 'canplaythrough' : 'loadedmetadata', onload)
- video.removeEventListener('error', onerror)
- }
+ video.removeEventListener(
+ canplaythrough ? "canplaythrough" : "loadedmetadata",
+ onload
+ );
+ video.removeEventListener("error", onerror);
+ };
const onload = () => {
- if (loaded) return
- loaded = true
- unbind()
- resolve(video)
- }
+ if (loaded) return;
+ loaded = true;
+ unbind();
+ resolve(video);
+ };
const onerror = (error) => {
- if (loaded) return
- loaded = true
- unbind()
- reject(error)
- }
- bind()
- video.preload = 'auto'
- video.src = url
- })
-)
+ if (loaded) return;
+ loaded = true;
+ unbind();
+ reject(error);
+ };
+ bind();
+ video.preload = "auto";
+ video.src = url;
+ });
-export const preloadAudio = url => (
+export const preloadAudio = (url) =>
new Promise((resolve, reject) => {
- const audio = document.createElement('audio')
- let loaded = false
+ const audio = document.createElement("audio");
+ let loaded = false;
const bind = () => {
- audio.addEventListener('loadedmetadata', onload)
- audio.addEventListener('error', onerror)
- }
+ audio.addEventListener("loadedmetadata", onload);
+ audio.addEventListener("error", onerror);
+ };
const unbind = () => {
- audio.removeEventListener('loadedmetadata', onload)
- audio.removeEventListener('error', onerror)
- }
+ audio.removeEventListener("loadedmetadata", onload);
+ audio.removeEventListener("error", onerror);
+ };
const onload = () => {
- if (loaded) return
- loaded = true
- unbind()
- resolve(audio)
- }
+ if (loaded) return;
+ loaded = true;
+ unbind();
+ resolve(audio);
+ };
const onerror = (error) => {
- if (loaded) return
- loaded = true
- unbind()
- reject(error)
- }
- bind()
- audio.preload = 'auto'
- audio.src = url
- })
-)
+ if (loaded) return;
+ loaded = true;
+ unbind();
+ reject(error);
+ };
+ bind();
+ audio.preload = "auto";
+ audio.src = url;
+ });
export const cropImage = (url, crop) => {
return new Promise((resolve, reject) => {
- let { x, y, w, h } = crop
- const image = new Image()
- let loaded = false
- x = parseFloat(x)
- y = parseFloat(y)
- w = parseFloat(w)
- h = parseFloat(h)
+ let { x, y, w, h } = crop;
+ const image = new Image();
+ let loaded = false;
+ x = parseFloat(x);
+ y = parseFloat(y);
+ w = parseFloat(w);
+ h = parseFloat(h);
image.onload = () => {
- if (loaded) return
- loaded = true
- image.onload = null
- const canvas = document.createElement('canvas')
- const ctx = canvas.getContext('2d')
- const width = image.naturalWidth
- const height = image.naturalHeight
- canvas.width = w * width
- canvas.height = h * height
+ if (loaded) return;
+ loaded = true;
+ image.onload = null;
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+ const width = image.naturalWidth;
+ const height = image.naturalHeight;
+ canvas.width = w * width;
+ canvas.height = h * height;
ctx.drawImage(
image,
Math.round(x * width),
Math.round(y * height),
Math.round(w * width),
Math.round(h * height),
- 0, 0, canvas.width, canvas.height
- )
- resolve(canvas)
- }
+ 0,
+ 0,
+ canvas.width,
+ canvas.height
+ );
+ resolve(canvas);
+ };
image.onerror = () => {
- console.log('image error')
- reject()
- }
+ console.log("image error");
+ reject();
+ };
// console.log(img.src)
- image.crossOrigin = 'anonymous'
- image.src = url
+ image.crossOrigin = "anonymous";
+ image.src = url;
if (image.complete) {
- image.onload()
+ image.onload();
}
- })
-}
-export const urlSearchParamsToDict = search => {
- const params = new URLSearchParams(search)
- const dict = {}
- params.forEach((value, key) => { // ???
- dict[key] = value
- })
- return dict
-}
+ });
+};
+export const urlSearchParamsToDict = (search) => {
+ const params = new URLSearchParams(search);
+ const dict = {};
+ params.forEach((value, key) => {
+ // ???
+ dict[key] = value;
+ });
+ return dict;
+};
/* AJAX */
-let cachedAuth = null
-let token = ''
-let username = ''
+let cachedAuth = null;
+let token = "";
+let username = "";
-export const post = (dispatch, type=api_type, tag, url, data) => {
- let headers
+export const post = (dispatch, type = api_type, tag, url, data) => {
+ let headers;
if (data instanceof FormData) {
headers = {
- Accept: 'application/json',
- }
+ Accept: "application/json",
+ };
} else if (data) {
headers = {
- Accept: 'application/json',
- 'Content-Type': 'application/json; charset=utf-8',
- }
- data = JSON.stringify(data)
+ Accept: "application/json",
+ "Content-Type": "application/json; charset=utf-8",
+ };
+ data = JSON.stringify(data);
}
dispatch({
type: type.loading,
tag,
- })
+ });
return fetch(url, {
- method: 'POST',
+ method: "POST",
body: data,
headers,
})
- .then(res => res.json())
- .then(res => dispatch({
- type: type.loaded,
- tag,
- data: res,
- }))
- .catch(err => dispatch({
- type: type.error,
- tag,
- err,
- }))
-}
+ .then((res) => res.json())
+ .then((res) =>
+ dispatch({
+ type: type.loaded,
+ tag,
+ data: res,
+ })
+ )
+ .catch((err) =>
+ dispatch({
+ type: type.error,
+ tag,
+ err,
+ })
+ );
+};
-export const api = (dispatch, type=api_type, tag, url, data) => {
+export const api = (dispatch, type = api_type, tag, url, data) => {
dispatch({
type: type.loading,
tag,
- })
- if (url.indexOf('http') !== 0) {
- url = window.location.origin + url
+ });
+ if (url.indexOf("http") !== 0) {
+ url = window.location.origin + url;
}
- url = new URL(url)
+ url = new URL(url);
if (data) {
- url.search = new URLSearchParams(data).toString()
+ url.search = new URLSearchParams(data).toString();
}
return fetch(url, {
- method: 'GET',
+ method: "GET",
// mode: 'cors',
})
- .then(res => res.json())
- .then(res => dispatch({
- type: type.loaded,
- tag,
- data: res,
- }))
- .catch(err => dispatch({
- type: type.error,
- tag,
- err,
- }))
-}
+ .then((res) => res.json())
+ .then((res) =>
+ dispatch({
+ type: type.loaded,
+ tag,
+ data: res,
+ })
+ )
+ .catch((err) =>
+ dispatch({
+ type: type.error,
+ tag,
+ err,
+ })
+ );
+};
/* sorting */
export const numericSort = {
- asc: (a,b) => a[0] - b[0],
- desc: (a,b) => b[0] - a[0],
-}
+ asc: (a, b) => a[0] - b[0],
+ desc: (a, b) => b[0] - a[0],
+};
export const stringSort = {
- asc: (a,b) => a[0].localeCompare(b[0]),
- desc: (a,b) => b[0].localeCompare(a[0]),
-}
-export const orderByFn = (s='name asc') => {
- const [field='name', direction='asc'] = s.split(' ')
- let mapFn, sortFn
+ asc: (a, b) => a[0].localeCompare(b[0]),
+ desc: (a, b) => b[0].localeCompare(a[0]),
+};
+export const orderByFn = (s = "name asc") => {
+ const [field = "name", direction = "asc"] = s.split(" ");
+ let mapFn, sortFn;
switch (field) {
- case 'id':
- mapFn = a => [parseInt(a.id) || 0, a]
- sortFn = numericSort[direction]
- break
- case 'epoch':
- mapFn = a => [parseInt(a.epoch || a.epochs) || 0, a]
- sortFn = numericSort[direction]
- break
- case 'size':
- mapFn = a => [parseInt(a.size) || 0, a]
- sortFn = numericSort[direction]
- break
- case 'date':
- mapFn = a => [+new Date(a.date || a.created_at), a]
- sortFn = numericSort[direction]
- break
- case 'updated_at':
- mapFn = a => [+new Date(a.updated_at), a]
- sortFn = numericSort[direction]
- break
- case 'priority':
- mapFn = a => [parseInt(a.priority) || parseInt(a.id) || 1000, a]
- sortFn = numericSort[direction]
- case 'name':
+ case "id":
+ mapFn = (a) => [parseInt(a.id) || 0, a];
+ sortFn = numericSort[direction];
+ break;
+ case "epoch":
+ mapFn = (a) => [parseInt(a.epoch || a.epochs) || 0, a];
+ sortFn = numericSort[direction];
+ break;
+ case "size":
+ mapFn = (a) => [parseInt(a.size) || 0, a];
+ sortFn = numericSort[direction];
+ break;
+ case "date":
+ mapFn = (a) => [+new Date(a.date || a.created_at), a];
+ sortFn = numericSort[direction];
+ break;
+ case "updated_at":
+ mapFn = (a) => [+new Date(a.updated_at), a];
+ sortFn = numericSort[direction];
+ break;
+ case "priority":
+ mapFn = (a) => [parseInt(a.priority) || parseInt(a.id) || 1000, a];
+ sortFn = numericSort[direction];
+ case "name":
default:
- mapFn = a => [a.name || "", a]
- sortFn = stringSort[direction]
- break
+ mapFn = (a) => [a.name || "", a];
+ sortFn = stringSort[direction];
+ break;
}
- return { mapFn, sortFn }
-}
-export const getOrderedIds = (objects, sort, prepend=[]) => {
- const { mapFn, sortFn } = orderByFn(sort)
- return prepend.concat(objects.map(mapFn).sort(sortFn).map(a => a[1].id))
-}
+ return { mapFn, sortFn };
+};
+export const getOrderedIds = (objects, sort, prepend = []) => {
+ const { mapFn, sortFn } = orderByFn(sort);
+ return prepend.concat(
+ objects
+ .map(mapFn)
+ .sort(sortFn)
+ .map((a) => a[1].id)
+ );
+};
export const getOrderedIdsFromLookup = (lookup, sort) => {
- return getOrderedIds(Object.keys(lookup).map(key => lookup[key]), sort)
-}
+ return getOrderedIds(
+ Object.keys(lookup).map((key) => lookup[key]),
+ sort
+ );
+};
/* parallel promises */
export const allProgress = (promises, progress_cb) => {
- let d = 0
- progress_cb(0, 0, promises.length)
+ let d = 0;
+ progress_cb(0, 0, promises.length);
promises.forEach((p) => {
p.then((s) => {
- d += 1
- progress_cb(Math.floor((d * 100) / promises.length), d, promises.length)
- return s
- })
- })
- return Promise.all(promises)
-}
+ d += 1;
+ progress_cb(Math.floor((d * 100) / promises.length), d, promises.length);
+ return s;
+ });
+ });
+ return Promise.all(promises);
+};
/* Clipboard */
export function writeToClipboard(str) {
return new Promise((resolve, reject) => {
- let success = false
+ let success = false;
function listener(e) {
- e.clipboardData.setData("text/plain", str)
- e.preventDefault()
- success = true
+ e.clipboardData.setData("text/plain", str);
+ e.preventDefault();
+ success = true;
}
- document.addEventListener("copy", listener)
- document.execCommand("copy")
- document.removeEventListener("copy", listener)
+ document.addEventListener("copy", listener);
+ document.execCommand("copy");
+ document.removeEventListener("copy", listener);
if (success) {
- resolve()
+ resolve();
} else {
- reject()
+ reject();
}
- })
-} \ No newline at end of file
+ });
+}
diff --git a/frontend/app/views/tile/handles/tile.image.js b/frontend/app/views/tile/handles/tile.image.js
index ea34bbe..9c9e311 100644
--- a/frontend/app/views/tile/handles/tile.image.js
+++ b/frontend/app/views/tile/handles/tile.image.js
@@ -1,68 +1,79 @@
-import React, { useState, useEffect } from 'react'
-import { generateTransform, pickCursor } from 'app/views/tile/tile.utils'
+import React, { useState, useEffect } from "react";
+import { generateTransform, pickCursor } from "app/views/tile/tile.utils";
-export default function TileImage({ tile, box, bounds, cursors, videoBounds, viewing, onMouseDown, onDoubleClick, onMouseEnter, onMouseLeave }) {
+export default function TileImage({
+ tile,
+ box,
+ bounds,
+ cursors,
+ videoBounds,
+ viewing,
+ onMouseDown,
+ onDoubleClick,
+ onMouseEnter,
+ onMouseLeave,
+}) {
const [style, setStyle] = useState({
transition: tile.settings.is_popup ? "opacity 0.2s" : "",
opacity: tile.settings.is_popup ? 0.0 : tile.settings.opacity,
- })
- const [className, setClassName] = useState("tile")
+ });
+ const [className, setClassName] = useState("tile");
useEffect(() => {
// console.log(tile)
const style = {
transform: generateTransform(tile, box, bounds, videoBounds),
opacity: tile.settings.is_popup ? 0.0 : tile.settings.opacity,
- transition: tile.settings.is_popup ? "opacity 0.2s" : ""
- }
+ transition: tile.settings.is_popup ? "opacity 0.2s" : "",
+ };
// console.log(generateTransform(tile))
- let content
- let className = ['tile', tile.type].join(' ')
+ let content;
+ let className = ["tile", tile.type].join(" ");
- let [cursorClass, cursorStyle] = pickCursor(tile, cursors, viewing)
+ let [cursorClass, cursorStyle] = pickCursor(tile, cursors, viewing);
if (cursorClass) {
- className += " " + cursorClass
+ className += " " + cursorClass;
}
if (cursorStyle) {
- style.cursor = cursorStyle
+ style.cursor = cursorStyle;
}
if (!tile.settings.url) {
- return null
+ return;
}
if (tile.settings.is_tiled) {
- style.backgroundImage = 'url(' + tile.settings.url + ')'
- style.backgroundPosition = tile.settings.align.replace('_', ' ')
+ style.backgroundImage = "url(" + tile.settings.url + ")";
+ style.backgroundPosition = tile.settings.align.replace("_", " ");
switch (tile.settings.tile_style) {
default:
- case 'tile':
- break
- case 'cover':
- style.backgroundSize = 'cover'
- break
- case 'contain':
- style.backgroundSize = 'contain'
- break
- case 'contain no-repeat':
- style.backgroundSize = 'contain'
- style.backgroundRepeat = 'no-repeat'
- break
+ case "tile":
+ break;
+ case "cover":
+ style.backgroundSize = "cover";
+ break;
+ case "contain":
+ style.backgroundSize = "contain";
+ break;
+ case "contain no-repeat":
+ style.backgroundSize = "contain";
+ style.backgroundRepeat = "no-repeat";
+ break;
}
- className += ' is_tiled'
+ className += " is_tiled";
} else {
- className += ' ' + tile.settings.align
+ className += " " + tile.settings.align;
}
- setStyle(style)
- setClassName(className)
+ setStyle(style);
+ setClassName(className);
if (tile.settings.is_popup) {
setTimeout(() => {
setStyle({
...style,
opacity: tile.settings.opacity,
- })
- }, 10)
+ });
+ }, 10);
}
- }, [])
+ }, []);
return (
<div
@@ -75,5 +86,5 @@ export default function TileImage({ tile, box, bounds, cursors, videoBounds, vie
>
{!tile.settings.is_tiled && <img src={tile.settings.url} />}
</div>
- )
+ );
}
diff --git a/frontend/site/projects/museum/app/index.js b/frontend/site/projects/museum/app/index.js
index 22a9ba5..bb11360 100644
--- a/frontend/site/projects/museum/app/index.js
+++ b/frontend/site/projects/museum/app/index.js
@@ -2,54 +2,84 @@
* Site router and custom pages
*/
-import React, { Component } from 'react'
-import { ConnectedRouter } from 'connected-react-router'
-import { Route } from 'react-router'
-import { connect } from 'react-redux'
+import React, { Component } from "react";
+import { ConnectedRouter } from "connected-react-router";
+import { Route } from "react-router";
+import { connect } from "react-redux";
-import ViewerContainer from 'site/viewer/viewer.container'
-import Home from 'site/projects/museum/views/home'
-import Essay from 'site/projects/museum/views/essay'
-import Artists from 'site/projects/museum/views/artists'
-import Credits from 'site/projects/museum/views/credits'
-import NavOverlay from 'site/projects/museum/views/nav.overlay'
-import StlOverlay from 'site/projects/museum/views/stl.overlay'
+import ViewerContainer from "site/viewer/viewer.container";
+import Home from "site/projects/museum/views/home";
+import Essay from "site/projects/museum/views/essay";
+import Artists from "site/projects/museum/views/artists";
+import Credits from "site/projects/museum/views/credits";
+import NavOverlay from "site/projects/museum/views/nav.overlay";
+import StlOverlay from "site/projects/museum/views/stl.overlay";
-import "site/projects/museum/views/mobile.css"
+import "site/projects/museum/views/mobile.css";
-import { loadMuseum } from 'site/projects/museum/museum.actions'
+import { loadMuseum } from "site/projects/museum/museum.actions";
class App extends Component {
componentDidMount() {
- loadMuseum()
+ loadMuseum();
}
-
+
render() {
if (!this.props.ready) {
- return <div />
+ return <div />;
}
return (
<ConnectedRouter history={this.props.history}>
- <div className='app'>
- <Route path={'/thelastmuseum/:page_name'} component={ViewerContainer} exact />
- <Route path={'/thelastmuseum/start'} component={Home} exact />
- <Route path={'/thelastmuseum/essay'} component={Essay} exact />
- <Route path={'/thelastmuseum/artists'} component={Artists} exact />
- <Route path={'/thelastmuseum/credits'} component={Credits} exact />
- <Route path={'/thelastmuseum/:page_name'} component={StlOverlay} exact />
- <Route path={'/thelastmuseum/:page_name'} component={NavOverlay} exact />
- <Route path='/thelastmuseum/' exact render={() => {
- setTimeout(() => this.props.history.push('/thelastmuseum/start'), 10)
- return null
- }} />
+ <div className="app">
+ <Route
+ path={"/thelastmuseum/:page_name"}
+ component={ViewerContainer}
+ exact
+ />
+ <Route path={"/thelastmuseum/start"} component={Home} exact />
+ <Route path={"/thelastmuseum/essay"} component={Essay} exact />
+ <Route path={"/thelastmuseum/artists"} component={Artists} exact />
+ <Route path={"/thelastmuseum/credits"} component={Credits} exact />
+ <Route
+ path={"/thelastmuseum/:page_name"}
+ component={StlOverlay}
+ exact
+ />
+ <Route
+ path={"/thelastmuseum/:page_name"}
+ component={NavOverlay}
+ exact
+ />
+ <Route
+ path="/thelastmuseum/"
+ exact
+ render={() => {
+ setTimeout(
+ () => this.props.history.push("/thelastmuseum/start"),
+ 10
+ );
+ return null;
+ }}
+ />
+ <Route
+ path="/"
+ exact
+ render={() => {
+ setTimeout(
+ () => this.props.history.push("/thelastmuseum/start"),
+ 10
+ );
+ return null;
+ }}
+ />
</div>
</ConnectedRouter>
- )
+ );
}
}
-const mapStateToProps = state => ({
+const mapStateToProps = (state) => ({
ready: state.site.ready,
-})
+});
-export default connect(mapStateToProps)(App)
+export default connect(mapStateToProps)(App);
diff --git a/frontend/site/projects/museum/constants.js b/frontend/site/projects/museum/constants.js
index 1af94c7..a2c1fe9 100644
--- a/frontend/site/projects/museum/constants.js
+++ b/frontend/site/projects/museum/constants.js
@@ -3,10 +3,46 @@
*/
export const ARTIST_ORDER = [
- "petros", "stankievech", "nora", "leite", "opoku", "foreshew", "nilthamrong",
-]
+ "stankievech",
+ "nora",
+ "leite",
+ "opoku",
+ "foreshew",
+ "nilthamrong",
+ "petros",
+ "egill",
+];
export const ARTISTS = {
+ egill: {
+ name: "Egill Sæbjörnsson",
+ location: {
+ en: "The Living Art Museum, Reykjavík, Iceland",
+ de: "The Living Art Museum, Reykjavík, Iceland",
+ },
+ start: "egill-intro",
+ homepage: "http://egills.de/",
+ bio: {
+ en: `<p>
+ Egill Sæbjörnsson, born 1973 in Iceland, has been making artworks that bring together 3D environments, digital projections, technology and sound for over 20 years. These range from small intimate installations in museum and gallery settings to large scale permanent architectural installations. Including his pioneering public art commission for the Robert Koch Institute in Berlin, entitled <i>Steinkugel</i>, which was the first permanent, self-generative video installation in an outdoor space in Germany. Sæbjörnsson conceives his work as a technological continuation of painting and sculpture, exploring the space between the virtual and real, mental and physical. His work is playful and humorous but always probing deeper philosophical questions. He gives regular performance lectures in which he explores the theoretical underpinnings of his practice.
+ </p>
+ <p>
+ Egill Sæbjörnsson’s works have been exhibited in The Hamburger Bahnhof, Berlin, Moderna Museet, Stockholm, Frankfurter Kunstverein, National Gallery of Prague, The 57th Biennale Arte in Venice and in 2019 he was nominated for the Ars Fennica Art Prize in Finland.
+ </p>`,
+ de: `<p></p>`,
+ },
+ statement: {
+ en: `<p>
+ </p>`,
+ de: `<p>
+ </p>`,
+ },
+ image: "/thelastmuseum/static/media/last-museum/artist-bio/egill.jpg",
+ globePosition: {
+ top: "8.7%",
+ left: "40.9%",
+ },
+ },
petros: {
name: "Petros Moris",
location: {
@@ -391,40 +427,38 @@ export const ARTISTS = {
top: "44%",
left: "44.4%",
},
- }
-}
+ },
+};
-export const PROJECT_PAGE_SET = new Set(["essay", "artists", "credits"])
+export const PROJECT_PAGE_SET = new Set(["essay", "artists", "credits"]);
/**
* Essays
*/
export const ESSAYS = {
- nadim: { title: "Curator's Essay", author: "Nadim Samman", },
- statements: { title: "Artist Statements", author: "", },
- developer: { title: "Developer Notes", author: "Jules LaPlace", },
-}
+ nadim: { title: "Curator's Essay", author: "Nadim Samman" },
+ statements: { title: "Artist Statements", author: "" },
+ developer: { title: "Developer Notes", author: "Jules LaPlace" },
+};
-export const ESSAY_ORDER = [
- "nadim", "statements", "developer",
-]
+export const ESSAY_ORDER = ["nadim", "statements", "developer"];
export const ESSAY_TEXTS = {
nadim_intro: {
- "en": `
+ en: `
<p>
- <i>The Last Museum</i> is an exhibition that explores tensions between the imagined ‘anywhere’ of digital space and its relation to concrete places and objects. Deploying a hybrid offline-online format, the project invites an international group of artists to reimagine site-specificity, through a sequence of interventions that cut across both real and virtual domains. The artists are <b>Nora Al-Badri</b> (Germany/Iraq), <b>Juliana Cerqueira Leite</b> (Brazil), <b>Nicole Foreshew</b> (Wiradjuri Nation/Australia), <b>Jakrawal Nilthamrong</b> (Thailand), <b>Zohra Opoku</b> (Ghana), and <b>Charles Stankievech</b> (Canada).
+ <i>The Last Museum</i> is an exhibition that explores tensions between the imagined ‘anywhere’ of digital space and its relation to concrete places and objects. Deploying a hybrid offline-online format, the project invites an international group of artists to reimagine site-specificity, through a sequence of interventions that cut across both real and virtual domains. The artists are <b>Nora Al-Badri</b> (Germany/Iraq), <b>Juliana Cerqueira Leite</b> (Brazil), <b>Nicole Foreshew</b> (Wiradjuri Nation/Australia), <b>Jakrawal Nilthamrong</b> (Thailand), <b>Zohra Opoku</b> (Ghana), <b>Charles Stankievech</b> (Canada), <b>Petros Moris</b> (Greece), and <b>Egill Sæbjörnsson</b> (Iceland).
</p>
`,
- "de": `
+ de: `
<p>
- <i>The Last Museum</i> ist eine Ausstellung, die Spannungen zwischen dem vermeintlichen "Überall" des Digitalen und seiner Beziehung zu konkreten Orten und Objekten erforscht. Das hybride Offline-Online-Format des Projektes lädt eine internationale Gruppe von Künstler*innen dazu ein, durch eine Abfolge von Interventionen, die sowohl reale als auch virtuelle Bereiche durchdringen, das Ortsspezifische neu zu denken. Die Künstler sind <b>Nora Al-Badri</b> (Deutschland/Irak), <b>Juliana Cerqueira Leite</b> (Brasilien), <b>Nicole Foreshew</b> (Wiradjuri-Nation/Australien), <b>Jakrawal Nilthamrong</b> (Thailand), <b>Zohra Opoku</b> (Ghana) und <b>Charles Stankievech</b> (Kanada).
+ <i>The Last Museum</i> ist eine Ausstellung, die Spannungen zwischen dem vermeintlichen "Überall" des Digitalen und seiner Beziehung zu konkreten Orten und Objekten erforscht. Das hybride Offline-Online-Format des Projektes lädt eine internationale Gruppe von Künstler*innen dazu ein, durch eine Abfolge von Interventionen, die sowohl reale als auch virtuelle Bereiche durchdringen, das Ortsspezifische neu zu denken. Die Künstler sind <b>Nora Al-Badri</b> (Deutschland/Irak), <b>Juliana Cerqueira Leite</b> (Brasilien), <b>Nicole Foreshew</b> (Wiradjuri-Nation/Australien), <b>Jakrawal Nilthamrong</b> (Thailand), <b>Zohra Opoku</b> (Ghana), <b>Charles Stankievech</b> (Kanada), <b>Petros Moris</b> (Griechenland), and <b>Egill Sæbjörnsson</b> (Island).
</p>
`,
},
nadim_essay: {
- "en": `
+ en: `
<p>
<i>The Last Museum</i> connects disparate sites, spanning six continents and the virtual sphere. It is an experiment that deploys a unique exhibition design—embracing the overlapping <i>analog</i> and <i>digital</i> dimensions of a given location while, additionally, exploiting the unique potentials of each for dramatic effect. Each artist was commissioned to author a sculptural group, to be installed at a location of their own choosing. The choice was only limited by a request that it be associated with communication and connectivity. Final settings ended up highlighting both technical and more esoteric forms of transmission—and included a notorious hacker hangout, ancestral land in rural Australia, an electronics mall in downtown Sao Paolo, a Cosmic Ray observatory in the Rocky Mountains, and more.
</p>
@@ -483,7 +517,7 @@ export const ESSAY_TEXTS = {
This project was conceived during the first wave of COVID-19, amid heightened tensions between the conditions of physical lockdown and globe-spanning telecommunication. Although utterly international, its production required no travel for persons or artworks. Through the artistic positions and interactive staging, <i>The Last Museum</i> explores the drama of site-specificity in light of <i>the digitization of place and its re-presentation online.</i> Rather than being a one-off exhibition, <i>The Last Museum</i> will ‘tour’ as a pop-up window on the start pages of partner institutions for fixed periods. In line with the project’s rejection of an ‘anywhere, anytime’ web imaginary, each touring iteration will acquire a new chapter—with an additional artist/site from the host institution’s country added to the navigable chain. As long as our colleagues’ are interested, it is possible that <i>The Last Museum</i> may tour and grow indefinitely—like the content of the web itself.
</p>
`,
- "de": `
+ de: `
<p>
<i>The Last Museum</i> verbindet unterschiedlichste Orte, die sich über sechs Kontinente und die virtuelle Sphäre erstrecken. Es ist ein Experiment, bei dem ein spezielles Ausstellungsdesign zum Einsatz kommt, dass die sich überschneidenden <i>analogen</i> und <i>digitalen</i> Dimensionen eines bestimmten Ortes mit einbezieht und dessen Potenziale hervorhebt. Alle beteiligten Künstler*innen wurden mit der Schaffung einer Skulpturengruppe beauftragt, die an einem frei gewählten, physischen Ort installiert wurde. Einzige Bedingung war, dass der Ort mit Kommunikationsinfrastrukturen verbunden ist. Die endgültigen Standorte beleuchten sowohl technische als auch irrationale Ressourcen von Konnektivität, darunter ein berüchtigter Hackerspace in Berlin, Indigenes Land in einem entlegenen Teil Australiens, ein beliebtes Elektronik-Einkaufszentrum in der Innenstadt von São Paulo, eine Forschungsstation für kosmische Strahlung in den Rocky Mountains, eine halb fertige Leichenhalle in Accra/Ghana und brennende Felder in Chiang Mai, Thailand.
</p>
@@ -541,7 +575,7 @@ export const ESSAY_TEXTS = {
`,
},
jules_essay: {
- "en": `
+ en: `
<p>
The software underlying <i>The Last Museum</i> has its roots in the hypertext art websites of the late 1990s. The sudden rise of the web gave way to an artistic landscape that was more than a mere constellation of networked texts. Online artists built new digital spaces, where each link might lead to another world with its own sense of place. This was something beyond the infinite library imagined by Borges. Here a single page could contain not just words, but any sort of media - even basic interactive content that, at the time, felt all the more abstract through its simplicity. Inhabiting this space was a virtual author who could dissolve and dissemble at will, speaking through typical navigation elements with a postmodern flair that rivaled the sprawling footnotes and elegiac colophon of an experimental twentieth-century novel.
</p>
@@ -561,7 +595,7 @@ export const ESSAY_TEXTS = {
We can still open a window into some new space, and it is in this frame that <i>The Last Museum</i> inserts itself, with six artists placing their sculptures out in the real world, where we must be assured that time is still passing and the air always moves. This virtual recreation of physical space is an offering of hope, and a promise of future reunion.</i>
</p>
`,
- "de": `
+ de: `
<p>
<b>Anmerkungen des Entwicklers</b>
</p>
@@ -586,24 +620,28 @@ export const ESSAY_TEXTS = {
<p>
Übersetzung aus dem Englischen: Nikolaus G. Schneider
</p>
- `
- }
-}
+ `,
+ },
+};
/**
* Marquees (i.e. Zohra)
*/
export const MARQUEES = {
- 'opoku-1-hail-to-you': {
- en: 'Hail to you, lords of truth, free from wrongdoing, who exist forever and forever. I have reached you because I am an akh with my forms. I am powerful through my magic. Going forth by day / Papyrus of Sobekmose / Book of the Dead ...',
- de: 'Seid gegrüßt, ihr Herren der Wahrheit, frei von Unrecht, die ihr für immer und ewig existiert. Ich habe euch erreicht, weil ich mit meinen Formen ein akh bin. Ich bin mächtig durch meine Magie. Weitergehen bei Tag / Papyrus des Sobekmose / Totenbuch ...',
+ "opoku-1-hail-to-you": {
+ en:
+ "Hail to you, lords of truth, free from wrongdoing, who exist forever and forever. I have reached you because I am an akh with my forms. I am powerful through my magic. Going forth by day / Papyrus of Sobekmose / Book of the Dead ...",
+ de:
+ "Seid gegrüßt, ihr Herren der Wahrheit, frei von Unrecht, die ihr für immer und ewig existiert. Ich habe euch erreicht, weil ich mit meinen Formen ein akh bin. Ich bin mächtig durch meine Magie. Weitergehen bei Tag / Papyrus des Sobekmose / Totenbuch ...",
+ },
+ "opoku-9-to-me-belongs-yesterday": {
+ en:
+ "To me belongs yesterday which is pregnant with tomorrow. Its births will rise on another occasion of his. (I am) the one whose ba is secret, who made the gods, who gives offerings to the protector of the west (side) of the sky, the steering rudder of the east, lord of two faces whose rays are seen, lord of the elevations who goes forth at twilight, manifestations … / Solar Text. Coming forth by day and not preventing the BA / Papyrus of Sobekmose / Book of the Dead",
+ de:
+ "To me belongs yesterday which is pregnant with tomorrow. Its births will rise on another occasion of his. (I am) the one whose ba is secret, who made the gods, who gives offerings to the protector of the west (side) of the sky, the steering rudder of the east, lord of two faces whose rays are seen, lord of the elevations who goes forth at twilight, manifestations … / Solar Text. Coming forth by day and not preventing the BA / Papyrus of Sobekmose / Book of the Dead",
},
- 'opoku-9-to-me-belongs-yesterday': {
- en: 'To me belongs yesterday which is pregnant with tomorrow. Its births will rise on another occasion of his. (I am) the one whose ba is secret, who made the gods, who gives offerings to the protector of the west (side) of the sky, the steering rudder of the east, lord of two faces whose rays are seen, lord of the elevations who goes forth at twilight, manifestations … / Solar Text. Coming forth by day and not preventing the BA / Papyrus of Sobekmose / Book of the Dead',
- de: 'To me belongs yesterday which is pregnant with tomorrow. Its births will rise on another occasion of his. (I am) the one whose ba is secret, who made the gods, who gives offerings to the protector of the west (side) of the sky, the steering rudder of the east, lord of two faces whose rays are seen, lord of the elevations who goes forth at twilight, manifestations … / Solar Text. Coming forth by day and not preventing the BA / Papyrus of Sobekmose / Book of the Dead',
- }
-}
+};
export const HEADPHONES = {
first: {
@@ -612,14 +650,14 @@ export const HEADPHONES = {
},
second: {
en: "(HTRF spatialization)",
- de: "(HTRF Verräumlichung)"
- }
-}
+ de: "(HTRF Verräumlichung)",
+ },
+};
export const BACK_TO_KW = {
en: "back to KW",
de: "zu KW",
-}
+};
export const CREDITS_STRINGS = {
artwork_credits_head: {
@@ -693,7 +731,7 @@ export const CREDITS_STRINGS = {
Press Releases and Image Material:<br/>
<a href="https://kw-berlin.de/en/press" target="_blank">kw-berlin.de/en/press</a>
</div>
- `
+ `,
},
artist_credits_1: {
@@ -817,5 +855,5 @@ export const CREDITS_STRINGS = {
Videografie & Sound Design: Jakrawal Nilthamrong <br/>
Bildhauerin: Julladit Sittibanjerd
`,
- }
-}
+ },
+};
diff --git a/frontend/site/projects/museum/export.js b/frontend/site/projects/museum/export.js
index ab25eda..7532d40 100644
--- a/frontend/site/projects/museum/export.js
+++ b/frontend/site/projects/museum/export.js
@@ -2,7 +2,7 @@
* Export the text content of the site to the index.html
*/
-import { ARTISTS, ESSAY_TEXTS, CREDITS_STRINGS } from "./constants"
+import { ARTISTS, ESSAY_TEXTS, CREDITS_STRINGS } from "./constants.js"
import fs from 'fs'
const outputFile = "./data_store/content/thelastmuseum/content.html"
diff --git a/frontend/site/projects/museum/views/artists.css b/frontend/site/projects/museum/views/artists.css
index c4546b5..bd9e921 100644
--- a/frontend/site/projects/museum/views/artists.css
+++ b/frontend/site/projects/museum/views/artists.css
@@ -6,7 +6,7 @@
.page-artists .artists-heading {
text-align: center;
- font-family: 'Druk Wide', sans-serif;
+ font-family: "Druk Wide", sans-serif;
text-transform: uppercase;
font-style: italic;
font-size: 2.5vh;
@@ -16,7 +16,7 @@
}
.page-artists .artist-detail-name,
.page-artists .artist-big-name {
- font-family: 'Druk';
+ font-family: "Druk";
font-style: italic;
text-transform: uppercase;
text-align: center;
@@ -28,12 +28,12 @@
}
.page-artists .artist-big-name {
/* font-size: 17.4vh; */
- font-size: 15vh;
- text-shadow: 0 0 10px #FF790D;
+ font-size: 13vh;
+ text-shadow: 0 0 10px #ff790d;
transition: text-shadow 0.2s;
}
.page-artists .artist-big-name:hover {
- text-shadow: 0 0 0px #FF790D;
+ text-shadow: 0 0 0px #ff790d;
}
.page-artists .artist-list {
display: flex;
@@ -48,7 +48,8 @@
.page-artists .artist-gallery {
position: absolute;
- top: 0; left: 0;
+ top: 0;
+ left: 0;
width: 100vw;
height: 100vh;
opacity: 0;
@@ -62,7 +63,8 @@
}
.page-artists .artist-detail {
position: absolute;
- top: 0; left: 0;
+ top: 0;
+ left: 0;
width: 100vw;
height: 100vh;
background: #0f0f0f;
@@ -83,7 +85,7 @@
top: 2vh;
left: 0;
width: 100%;
- text-shadow: 0 0 5px #FF790D;
+ text-shadow: 0 0 5px #ff790d;
cursor: pointer;
}
.page-artists .artist-detail-name a,
@@ -94,8 +96,8 @@
transform: translateZ(0);
}
.page-artists .nav-arrow path {
- stroke: #FF790D;
- fill: #FF790D;
+ stroke: #ff790d;
+ fill: #ff790d;
}
.artist-close {
@@ -112,7 +114,8 @@
.page-artists .artist-content {
position: absolute;
- top: 16vh; left: 0;
+ top: 16vh;
+ left: 0;
width: 100vw;
height: 78vh;
overflow-y: auto;
@@ -157,12 +160,12 @@
left: 0;
width: 100%;
text-align: center;
- font-family: 'Druk Wide', sans-serif;
+ font-family: "Druk Wide", sans-serif;
text-transform: uppercase;
font-style: italic;
font-size: 2.5vh;
- text-shadow: 0 0 3px #FF790D;
+ text-shadow: 0 0 3px #ff790d;
margin: 0 0 1vh;
cursor: default;
pointer-events: none;
-} \ No newline at end of file
+}
diff --git a/frontend/site/projects/museum/views/home.js b/frontend/site/projects/museum/views/home.js
index 877e0c7..8ad8d49 100644
--- a/frontend/site/projects/museum/views/home.js
+++ b/frontend/site/projects/museum/views/home.js
@@ -2,43 +2,43 @@
* Start page interaction
*/
-import React, { Component } from 'react'
-import { connect } from 'react-redux'
+import React, { Component } from "react";
+import { connect } from "react-redux";
-import { history } from 'site/store'
-import actions from 'site/actions'
+import { history } from "site/store";
+import actions from "site/actions";
-import "./home.css"
+import "./home.css";
-let clicked = false
-let started = false
+let clicked = false;
+let started = false;
class Home extends Component {
constructor(props) {
- super(props)
- this.ref = React.createRef()
- this.handleClick = this.handleClick.bind(this)
+ super(props);
+ this.ref = React.createRef();
+ this.handleClick = this.handleClick.bind(this);
this.state = {
open: false,
hidden: this.props.interactive,
showCurtain: true,
- }
+ };
}
componentDidMount() {
- const roadblock = document.querySelector('.roadblock')
- if (roadblock) roadblock.style.display = "none"
+ const roadblock = document.querySelector(".roadblock");
+ if (roadblock) roadblock.style.display = "none";
setTimeout(() => {
- this.setState({ showCurtain: false })
- }, 100)
+ this.setState({ showCurtain: false });
+ }, 100);
}
handleClick(e) {
- e && e.preventDefault()
- actions.site.interact()
+ e && e.preventDefault();
+ actions.site.interact();
- this.index = 0
- const FLASH_TIME = 150
+ this.index = 0;
+ const FLASH_TIME = 150;
const order = [
["home orange-text intro", FLASH_TIME],
["home white-text orange-bg", FLASH_TIME],
@@ -46,33 +46,33 @@ class Home extends Component {
["home white-text orange-bg", FLASH_TIME],
["home orange-text white-bg", FLASH_TIME],
["home white-text orange-bg", FLASH_TIME],
- ]
+ ];
const go = () => {
- clearTimeout(this.timeout)
- const item = order[this.index]
+ clearTimeout(this.timeout);
+ const item = order[this.index];
if (item) {
- this.ref.current.className = item[0]
- this.index += 1
- this.timeout = setTimeout(go, item[1])
+ this.ref.current.className = item[0];
+ this.index += 1;
+ this.timeout = setTimeout(go, item[1]);
} else {
- this.ref.current.className = "home orange-text intro hidden"
- clicked = true
+ this.ref.current.className = "home orange-text intro hidden";
+ clicked = true;
}
- }
+ };
if (!clicked) {
- go()
+ go();
} else if (!started) {
- this.ref.current.className = "home orange-text black-bg open"
+ this.ref.current.className = "home orange-text black-bg open";
setTimeout(() => {
- started = true
- }, 200)
+ started = true;
+ }, 200);
} else {
- this.ref.current.className = "home orange-text orange-bg open"
+ this.ref.current.className = "home orange-text orange-bg open";
setTimeout(() => {
- history.push("/thelastmuseum/stankievech-1")
- }, FLASH_TIME)
+ history.push("/thelastmuseum/egill-intro");
+ }, FLASH_TIME);
}
}
@@ -84,7 +84,9 @@ class Home extends Component {
onClick={this.handleClick}
>
<div className="home-byline byline-top">KW PRESENTS</div>
- <div className="home-title title-1">THE L<span>AST</span></div>
+ <div className="home-title title-1">
+ THE L<span>AST</span>
+ </div>
<div className="home-title title-2">MUSEUM</div>
<div className="home-artists">
<div>CHARLES STANKIEVECH</div>
@@ -97,17 +99,22 @@ class Home extends Component {
</div>
<div className="home-byline byline-bottom">CURATED BY NADIM SAMMAN</div>
<div className="home-message">
- The Last Museum hovers somewhere between life and death, lockdown and escape. Spanning six continents and too many screens, it is home to new muses - born of encounters between digital space and facts on the ground. It is web-site-specific. You are already here.
+ The Last Museum hovers somewhere between life and death, lockdown and
+ escape. Spanning six continents and too many screens, it is home to
+ new muses - born of encounters between digital space and facts on the
+ ground. It is web-site-specific. You are already here.
</div>
- <div className={this.state.showCurtain ? "curtain" : "curtain hidden"} />
+ <div
+ className={this.state.showCurtain ? "curtain" : "curtain hidden"}
+ />
</div>
- )
+ );
}
}
-const mapStateToProps = state => ({
+const mapStateToProps = (state) => ({
interactive: state.site.interactive,
language: state.site.language,
-})
+});
-export default connect(mapStateToProps)(Home)
+export default connect(mapStateToProps)(Home);