summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.museum.md42
-rw-r--r--data_store/content/thelastmuseum/content.html73
-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
10 files changed, 738 insertions, 508 deletions
diff --git a/README.museum.md b/README.museum.md
index 6419890..2222a16 100644
--- a/README.museum.md
+++ b/README.museum.md
@@ -2,18 +2,38 @@
Project for KW Berlin and other places.
-## Linking to assets
+## Updating the site
+
+### Linking to assets
- Local: ./data_store/media/last-museum/[artist]/[filename]
- Embed: /static/media/last-museum/[artist]/[filename]
-## Pages
+### Videos
+
+- size: cover
+- units: pixels
+
+For underlying videos:
+
+- units: video
-- Custom components: frontend/site/museum/views/
+### Pages
-These should all have some documentation.
+- Custom components: `frontend/site/museum/views/`
+- Include these with routes: `frontend/site/museum/app/index.js`
-## Building the site
+### Adding a new artist
+
+- Update `constants.js` with the artist name, bio, etc
+- Font size might change for CSS: `.page-artists .artist-big-name`
+- Upload their cursors
+- Update `frontend/site/projects/museum/views/home.js`
+ - the line `history.push("/thelastmuseum/...")` should point at the new start page
+
+## Deployment
+
+### Building the site
```
# Building the Last Museum site (development)
@@ -24,10 +44,10 @@ yarn build:museum:production
rsync -rlptuvz ./data_store/exports/thelastmuseum/ lens@garden:swimmer/data_store/exports/thelastmuseum/
```
-## Adding a new artist
+### Packaging a build
-- Update constants.js with the artist name, bio, etc
-- Font size might change for CSS: `.page-artists .artist-big-name`
-- Upload their cursors
-- Update frontend/site/projects/museum/views/home.js
- - the line `history.push("/thelastmuseum/...")` should point at the new start page
+```
+# Zipping the site on the server
+cd /home/lens/swimmer/data_store/exports
+zip -r ./thelastmuseum-YYYYMMDD.zip ./thelastmuseum
+```
diff --git a/data_store/content/thelastmuseum/content.html b/data_store/content/thelastmuseum/content.html
index dd2085f..c3dc1ee 100644
--- a/data_store/content/thelastmuseum/content.html
+++ b/data_store/content/thelastmuseum/content.html
@@ -6,6 +6,44 @@
<h2>Artists</h2>
+<h3>Petros Moris</h3>
+
+<h4>Biography (English)</h4>
+
+<p>Petros Moris (b. 1986) is an artist based in Athens. His work develops through sculpture, writing and digital media, contemplating the dynamics between manifestations of memory and the anthropogenic concept of the future. He has been nominated for the Deste Prize 2015 and has been awarded the SNF Artists Fellowship, the Onassis Foundation Scholarship, and the Delfina Foundation Residency. He has presented solo exhibitions in galleries and art spaces including Radio Athènes (Athens), Galeria Duarte Sequiera (Braga), Project Native Informant (London), Point Centre for Contemporary Art (Nicosia), Union Pacific (London), LilyRobert Gallery (Paris), ROOM E-10 27 (Berlin), SPACE (London), The Cyprus Embassy (Athens), and Onassis Culture (Athens). He has participated in group exhibitions including the <i>2019 Singapore Biennale</i> (Singapore), the <i>New Museum Triennial - Songs for Sabotage</i> (New York), <i>The Tides of the Century</i> (Ocean Flower Island Museum, Hainan), the <i>7th Thessaloniki Biennale</i> (Thessaloniki), <i>Still Here Tomorrow</i> (Stavros Niarchos Foundation, Athens), <i>Digital Gothic</i> (Centre d'art Contemporain, Delme), <i>Tomorrows</i> (Le Lieu Unique, Nantes), <i>Geometries</i> (Onassis Culture, Athens), <i>The Same River Twice & The Equilibrists</i> (Deste X New Museum, Benaki Museum Athens), the <i>2021 Athens Biennale - Eclipse</i> (Athens). He has curated exhibitions and events at the Chisenhale Gallery, the Whitechapel Gallery, and Circuits and Currents as part of the curatorial collectives SIM and Radical Reading. He has been part of the art collective KERNEL and co-runs the publication project AM.</p>
+
+<p>
+ Oracle is set in a derelict textile factory in Laurium, a seaside town in Attica that used to be a significant silver mine territory since antiquity, the main source of ancient Athenian wealth and naval power. Those mines were reworked from the late 19th century to the 1980’s for ore extraction, an activity that left its grave environmental impact on the area’s soil to this day. Once part of the local industrial economy that collapsed before the turn of the millennium, this industrial ruin was recently leaked to be the future site of a data center complex to be built by one of the so-called “Big Five” multinational information technology corporations, for the purpose of running dedicated Cloud, IoT and AI services.
+ </p>
+ <p>
+ Sculptural appearances inhabit the entropic spaces of this industrial ghost site, hybrids between human-resembling and non-human facial forms, seeding from chimeric compositions between archaeological photogrammetric scans and online-found threedimensional models referencing contemporary technocultures. Their metallic materiality reflects the local geological heritage and the mineral composition of the electronic hardware that enables the uncanny machinic intelligence of computational operations. The interface of Oracle is permeated by a haunting soundtrack composed by Bill Kouligas, and becomes infiltrated by formations of visual coding and synthetic language generated by predictive models of simulated mutation and automated apophenia.
+ </p>
+ <p>
+ The physical and psychic underground space is still the origin of such forms of contemporary oracular production, where the ancient practices of hallucination deriving from subterranean chemical fumes gave their place to the extractivism of minerals and psychosocial data that feeds the deep-dreaming of the algorithmic Cloud.
+ </p>
+ <p>
+ As a place of memory and a place to become, an assemblage of mythological, historical, and technological entities, a generative process, or a resulting outcome left for interpretation, Oracle becomes a mediative entanglement of collapsing timelines, of imaginaries and anxieties bound to technological, socioeconomic and environmental transformation.
+ </p>
+
+<h4>Biografie (Deutsch)</h4>
+
+<p>
+ Petros Moris (*1986) ist ein in Athen lebender Künstler. Sein Werk umfasst Skulptur, Schriften und digitale Medien und beschäftigt sich mit der Dynamik zwischen den Erscheinungsformen der Erinnerung und dem anthropogenen Konzept der Zukunft. Er wurde 2015 für den Deste Prize nominiert und erhielt das SNF-Künstlerstipendium, das Stipendium der Onassis-Stiftung und die Delfina Foundation Residenz. Seine Arbeiten waren in Einzelausstellungen in Galerien und Kunsträumen zu sehen, unter anderem bei Radio Athènes (Athen), in der Galeria Duarte Sequiera (Braga), bei Project Native Informant (London), im Point Centre for Contemporary Art (Nikosia), bei Union Pacific (London), in der LilyRobert Gallery (Paris), bei ROOM E-10 27 (Berlin), bei SPACE (London), sowie The Cyprus Embassy (Athen) und bei Onassis Culture (Athen). Er war in verschiedenen Gruppenausstellungen vertreten, darunter bei der <i>2019 Singapore Biennale</i> (Singapur), der <i>New Museum Triennial - Songs for Sabotage</i> (New York), bei <i>The Tides of the Century</i> (Ocean Flower Island Museum, Hainan), bei der <i>7th Thessaloniki Biennale</i> (Thessaloniki), bei <i>Still Here Tomorrow</i> (Stavros Niarchos Foundation, Athen), <i>Digital Gothic</i> (Centre d'art Contemporain, Delme), <i>Tomorrows</i> (Le Lieu Unique, Nantes), <i>Geometries</i> (Onassis Culture, Athen), sowie bei <i>The Same River Twice & The Equilibrists</i> (Deste X New Museum, Benaki Museum Athen) und der <i>2021 Athens Biennale - Eclipse</i> (Athen). Moris hat Ausstellungen und Veranstaltungen in der Chisenhale Gallery, der Whitechapel Gallery und Circuits and Currents als Teil der Kurator*innenkollektive SIM und Radical Reading kuratiert. Er war Teil des Kunstkollektivs KERNEL und ist Mitherausgeber des Publikationsprojekts AM.
+ </p>
+
+<p>
+ <i>Oracle</i> befindet sich in einer verfallenen Textilfabrik in der attikanischen Küstenstadt Lavrio, in der seit der Antike Silber abgebaut wurde. Lange Zeit Quelle des Reichtums und der Seemacht Athens, wurden die Minen im späten 19. Jahrhundert zur Erzgewinnung umgewidmet - mit gravierenden Folgen für die Umwelt. Um die Jahrtausendwende brach diese Industrie zusammen. Die Zukunft der verbliebenen riesigen Ruinen ist ungewiss. Angeblich will hier demnächst ein multinationales IT-Unternehmen der "Big Five" ein Rechenzentrum für spezialisierte Cloud-, IoT- und KI-Dienste bauen. <i>Oracle</i> zeigt eine andere Option für den Standort auf. Konzeptionell zwischen der Vergangenheit und der Zukunft von Lavrio angesiedelt, interagiert die Arbeit mit dem Kontext und thematisiert den Mythos des Orakels in der griechischen Kultur.
+ </p>
+ <p>
+ Die <i>Oracle</i>-Skulpturen füllen die entropischen Räume der industriellen Geisterstadt Lavrio. Maskenähnliche Objekte erinnern an Hybride aus menschlichen und nicht-menschlichen Gesichtern. Sie sind chimärische Kompositionen aus archäologischen fotogrammetrischen Scans und im Internet "gefundenen" dreidimensionalen Modellen. Ihr metallischer Glanz spiegelt das lokale geologische Erbe wider. Ihre mineralische Zusammensetzung verweist auf die elektronische Hardware, die die unheimliche Intelligenz der Maschinen und die gespenstische Abstraktion von Rechenoperationen möglich macht.
+ </p>
+ <p>
+ Visuelle Kodierungen und in simulierten Prozessen erzeugte Klanglandschaften durchdringen den Schauplatz und die digitale Schnittstelle von <i>Oracle</i>. Der physische und psychische unterirdische Raum von Lavrio ist in dieser Arbeit noch immer Ursprung der zeitgenössischen orakelhaften Produktion - in der uralte Praktiken den durch chemische Dämpfe hervorgerufenen Halluzinationen, der Extraktion von Mineralien und psychosozialen Daten weichen, die die intensiven Träume der algorithmischen Cloud treiben.
+ </p>
+ <p>
+ Als Ort der Erinnerung und Ort des Werdens, als Sammlung von Mythen, Geschichte und Technologie vermittelt <i>Oracle</i> eine Vorstellung von den verschlungenen Zeitlinien, Fantasien und Ängsten, die den gesellschaftlichen, ökonomischen und ökologischen Wandel begleiten.
+ </p>
+
<h3>Nora Al-Badri</h3>
<h4>Biography (English)</h4>
@@ -363,6 +401,9 @@
<b>Jakrawal Nilthamrong</b>’s contribution is set in Thailand’s northern mountainous region near Chiang Mai, a site recently devastated by annual wildfires which envelop the area in a choking haze. Set among burning fields, his interventions reconstruct the type of time-release release burners built by arsonists, whose purposes in setting the blazes are heavily contested and politicized. Scenes of fire creeping up the mountainside transition automatically in step with a numeric real-time count of world population shown at the edge of the video. Speculatively linking the fires to global population growth, Nilthamrong indicts the ecological and economic pressures impinging on local actors—farmers, foragers, and residents. Mesmerizing images of fire onscreen offer a smokeless encounter with the incendiary contradictions of a contemporary social-economic order that pits basic human subsistence against environmental sustainability on a planetary scale. What is emphasized here through the modest figure of a clothes peg clipped to a tangle of wires is the technological mediation of these tensions.
</p>
<p>
+ <b>Petros Moris</b>’ <i>Oracle</i>, is set in a derelict textile factory in Laurium—a seaside town in Attica, associated with silver mining since antiquity. In the late 19th Century its mines were re-worked for ore extraction, with grave environmental consequences. Later, at the turn of the millennium, this industry collapsed and its massive industrial facilities laid in ruin, awaiting an uncertain future. Moris’ new work responds to that future: According to leaked information, the site will soon house a data-center complex to be built by a major technology corporation (for the purpose of running Cloud, IoT and AI services). Conceptually locating his work between Larium’s past and its future, the artist’s new commission is a powerful response to the context – and to the mythos of the oracle in Greek culture. <i>Oracle’s</i> sculptures inhabit the entropic spaces of Larium’s industrial ghost site. Resembling hybrids between human and non-human faces, these objects borrow from archaeological photogrametric scans and ‘found’ three-dimensional models gathered online. Their metallic sheen reflects the local geological heritage, while interactive icons deliver computer generated text oracles.
+ </p>
+ <p>
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>
@@ -462,7 +503,30 @@
<h4>Deutsch</h4>
- <p>Eine deutsche Übersetzung kommt bald.</p>
+ <p>
+ <b>Anmerkungen des Entwicklers</b>
+ </p>
+ <p>
+ Die Software, die <i>The Last Museum</i> zugrunde liegt, geht auf die Hypertext Kunst-Webseiten der späten 1990er zurück. Der plötzliche Aufstieg des Internet brachte eine künstlerische Landschaft mit sich, die mehr war als eine bloße Konstellation vernetzter Texte. Online-Künstler*innen schufen neue digitale Räume, in denen jeder Link in eine andere Welt mit ihrem eigenen Raumgefühl führen konnte. Das ging über die unendliche, von Borges imaginierte Bibliothek hinaus. Hier konnte eine einzelne Seite nicht einfach nur Worte enthalten, sondern alle möglichen Medien, selbst grundlegende interaktive Inhalte, die damals aufgrund ihrer Einfachheit umso abstrakter wirkten. Bewohnt wurde dieser Raum von virtuellen Autor*innen, die sich nach Belieben auflösen oder verstellen konnten und sich mittels typischer Navigationselemente mit einem postmodernen Flair äußerten, der es mit den ausufernden Fußnoten und dem elegischen Kolophon eines experimentellen Romans des 20. Jahrhunderts aufnehmen konnte.
+ </p>
+ <p>
+ Der fatale Fehler des Internets der 1990er bestand darin, dass es extrem manuell und daher schwer zu pflegen war. Viele der ersten Kunst-Webseiten schafften es nicht bis ins neue Jahrhundert, und das Internet der 2000er, der Blog-Ära, war entschieden weniger experimentierfreudig. Sobald es mit einer Datenbank verlinkt war, wurde das Web immer formelhafter, da sich die Informationen, die man anbieten konnte, auf ein starres Set von Kästchen beschränkten, deren Input gereinigt und von der struppigen Formatierung und dem selbstkreierten Organisationsstil der Vergangenheit befreit war. Diese Periode ging auch mit einem Verlust von Vertrauen zu den Benutzer*innen einher, die, so scheint es, vormals zu viel Macht hatten. Ein einziges nicht richtig funktionierender HTML „Tag“ konnte eine ganze Webseite ruinieren und neue Möglichkeiten der Skripterstellung bedeuteten, dass überall Softwarewürmer lauern konnten, die nur darauf warteten, zum Leben zu erwachen. Für Programmierer*innen, die mit alternden Versionen nicht unterstützter Browser kämpften, war das Entwicklungsumfeld rau und oft feindselig.
+ </p>
+ <p>
+ Im letzten Jahrzehnt wiederum kam es sowohl zur Synthese als auch zur Zerstörung des alten Internets, und die heutige Online-Welt wurde zum Leben erweckt. Es ist erst eine Dekade her, dass der imagistische Diskurs Online im selben Tempo stattzufinden begann wie Unterhaltungen. Es schien, dass alles, was in der Software ging, nun auch im Browser möglich war im Kontext des eigenen Gartens und wenn man aufpasste, nicht gegen seine unsichtbaren Mauern zu prallen. Doch die Welt der Datenbanken war immer noch stärker als je zuvor. Wir pflegen Social Media-Konten statt Webringen, und das alte <a href="https://www.heise.de/tp/features/Erste-Siedler-und-Barbaren-3439021.html" target="_blank">Vernakular-Web</a> ist mittlerweile in weite Ferne gerückt. Wenn man heute online eine gewissen Freiheit erlangen will, muss man sie selbst programmieren, und dasselbe gilt, wenn man auch seinen Freund*innen dieses Vergnügen bereiten will.
+ </p>
+ <p>
+ Diese Software ist also von zwei Dingen inspiriert. Zunächst haben wir da diese idealisierte Vision des Internet der 1990er dank Kunst-Webseiten wie der irgendwie immer noch existierenden, mit einem „Webby“ ausgezeichneten <a href="http://www.superbad.com/" target="_blank">superbad.com</a>, eine Website, die durch ihr abstraktes Mäandern, das sich ihrem vagen, unaufgelösten Narrativ anzuschmiegen scheint, immer noch inspirierend wirkt. Man kreuze dies mit dem imagistischen Potenzial der 2010er, für das beispielhalft Tim Bakers <a href="https://asdf.us/tile/" target="_blank">image layering tool</a> steht. Hier werden die Benutzer*innen ermutigt, die Regeln des digitalen Eigentums zu brechen und »Bandbreitendiebstahl« zu begehen und sämtliche Bilderlinks, die man griffbereit vorfindet, anzuordnen und zu collagieren.
+ </p>
+ <p>
+ Im Sommer 2020 hatte ich endlich Zeit, diese beiden Ideen zum Swimmer zu verbinden, einem Werkzeug, mit dem man mehrere Collagen machen und miteinander verlinken kann, sodass auf diese Weise ein Netz entsteht, das durch imagistischen Hypertext Geschichten aus einer anderen Welt erzählen kann. Das erste Projekt, das davon Gebrauch machte, stammt von meinem Freund Ernst Markus Stein für seine Ausstellung <a href="https://km-galerie.com/swimmer/frontside/" target="_blank">geschichte von jemand, wo nachts das hobby geklaut wird</a> im KM in Kreuzberg. Seine physischen Werke nahmen einen skulpturalen Austausch zwischen Realem und Virtuellen vor und verwandelten digitale Gemälde in Tapisserien und Stickereien. Bei unserer parallelen Online-Vernissage lief das Band in umgekehrter Reihenfolge, so dass unsere bei sich daheim festsitzenden Freund*innen uns auch während des Lockdowns besuchen konnten.
+ </p>
+ <p>
+ Noch immer können wir ein Fenster in irgendeinen neuen Raum öffnen, und dieser Rahmen ist es, in den sich <i>The Last Museum</i> einfügt, mit sechs Künstler*innen, die ihre Skulpturen draußen in der realen Welt platzieren, wo wir uns noch sicher sein müssen, dass die Zeit nach wie vor verrinnt und die Luft ständig in Bewegung ist. Diese virtuelle Wiederherstellung des physischen Raums ist ein Zeichen der Hoffnung und das Versprechen einer Wiederbegegnung in der Zukunft.
+ </p>
+ <p>
+ Übersetzung aus dem Englischen: Nikolaus G. Schneider
+ </p>
<h2>Credits</h2>
@@ -506,6 +570,13 @@
<h3>Artwork Credits</h3>
+ <b>Petros Moris</b><br/>
+ <i>Oracle</i><br/>
+ Location: Abandoned textile factory, Laurium, Greece<br/>
+ Soundtrack: Bill Kouligas<br/>
+ With thanks to: Lito Kattou, Giagkos Papadopoulos, Foivos Tsimpourlas<br/>
+ <br/>
+ <br/>
<b>Charles Stankievech</b><br/>
<i>The Glass Key</i><br/>
Cinematography, Soundtrack, LIDAR and Ionospheric Recordings, Hypercard Programming: Charles Stankievech<br/>
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);