]> git.localhorst.tv Git - alttp.git/commitdiff
clickable APNGs
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 30 Jul 2023 15:56:18 +0000 (17:56 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 30 Jul 2023 15:56:18 +0000 (17:56 +0200)
resources/js/components/common/PngDialog.js [new file with mode: 0644]
resources/js/components/common/PngPlayer.js
resources/js/components/common/RawHTML.js
resources/sass/common.scss

diff --git a/resources/js/components/common/PngDialog.js b/resources/js/components/common/PngDialog.js
new file mode 100644 (file)
index 0000000..dbd030f
--- /dev/null
@@ -0,0 +1,27 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+
+import PngPlayer from './PngPlayer';
+
+const PngDialog = ({ onHide, show, src, title }) => <Modal onHide={onHide} show={show} size="lg">
+       {title ?
+               <Modal.Header closeButton>
+                       <Modal.Title>
+                               {title}
+                       </Modal.Title>
+               </Modal.Header>
+       : null}
+       <Modal.Body>
+               <PngPlayer src={src} />
+       </Modal.Body>
+</Modal>;
+
+PngDialog.propTypes = {
+       onHide: PropTypes.func,
+       show: PropTypes.bool,
+       src: PropTypes.string,
+       title: PropTypes.string,
+};
+
+export default PngDialog;
index 4bdb41adc5653aab9588820d19b0542a7f977911..d1d5da3bca3bae77b0b6e257395878bc526ef510 100644 (file)
@@ -24,6 +24,7 @@ const PngPlayer = ({ src }) => {
        const [player, setPlayer] = React.useState(null);
 
        React.useEffect(() => {
+               if (!src) return;
                setError(null);
                setLoading(true);
                const ctrl = new AbortController();
index fb0b51ef2b43ec3b5672768818bc2e8a78e056cb..dde6d530fed21f58dd5a736d64d7f6b1b22d01fd 100644 (file)
@@ -2,8 +2,23 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import { useNavigate } from 'react-router-dom';
 
+import PngDialog from './PngDialog';
+
+const isApng = el => el.nodeName === 'IMG' && el.getAttribute('type') === 'image/apng';
+
+const isLink = el => el.nodeName === 'A';
+
+const canClick = el => {
+       if (isLink(el)) return true;
+       if (isApng(el)) return true;
+       return false;
+};
+
 const RawHTML = ({ html }) => {
        const navigate = useNavigate();
+       const [apng, setApng] = React.useState(null);
+       const [show, setShow] = React.useState(false);
+       const [title, setTitle] = React.useState(null);
 
        const onClick = e => {
                if (e.defaultPrevented) return;
@@ -11,34 +26,46 @@ const RawHTML = ({ html }) => {
                if (e.button !== 0) return;
 
                let el = e.target;
-               while (el && el.nodeName !== 'A') {
+               while (el && !canClick(el)) {
                        el = el.parentNode;
                }
                if (!el) return;
 
-               if (el.target && el.target !== '_self') return;
-               if (el.attributes.download) return;
-               if (el.rel && /(?:^|\s+)external(?:\s+|$)/.test(el.rel)) return;
+               if (isLink(el)) {
+                       if (el.target && el.target !== '_self') return;
+                       if (el.attributes.download) return;
+                       if (el.rel && /(?:^|\s+)external(?:\s+|$)/.test(el.rel)) return;
+
+                       const href = el.getAttribute('href');
 
-               const href = el.getAttribute('href');
+                       if (href.startsWith('#')) return;
+                       if (href.startsWith('http')) return;
+                       if (href.startsWith('mailto')) return;
+                       if (href.startsWith('tel')) return;
 
-               if (href.startsWith('#')) return;
-               if (href.startsWith('http')) return;
-               if (href.startsWith('mailto')) return;
-               if (href.startsWith('tel')) return;
+                       el.blur();
+                       e.preventDefault();
 
-               el.blur();
-               e.preventDefault();
+                       setTimeout(() => {
+                               // scroll to top on location change
+                               scrollTo({ top: 0, behavior: 'smooth' });
+                       }, 50);
 
-               setTimeout(() => {
-                       // scroll to top on location change
-                       scrollTo({ top: 0, behavior: 'smooth' });
-               }, 50);
+                       navigate(href);
+                       return;
+               }
 
-               navigate(href);
+               if (isApng(el)) {
+                       setApng(el.getAttribute('src'));
+                       setShow(true);
+                       setTitle(el.getAttribute('alt'));
+               }
        };
 
-       return <div onClick={onClick} dangerouslySetInnerHTML={{ __html: html }} />;
+       return <>
+               <div className="raw-html" onClick={onClick} dangerouslySetInnerHTML={{ __html: html }} />
+               <PngDialog onHide={() => setShow(false)} show={show} src={apng} title={title} />
+       </>;
 };
 
 RawHTML.propTypes = {
index 3e88649417c1dcc9b8ced28749acd14768bc205e..4d603f06a3b4926d0a8706e4e1ffdd0cf35477fa 100644 (file)
@@ -23,6 +23,18 @@ h1 {
        max-width: none !important;
 }
 
+.pic-end {
+       float: right;
+       margin-left: 1rem;
+       margin-bottom: 1rem;
+}
+
+.pic-start {
+       float: left;
+       margin-right: 1rem;
+       margin-bottom: 1rem;
+}
+
 .png-player {
        display: flex;
        flex-direction: column;
@@ -60,6 +72,12 @@ h1 {
        }
 }
 
+.raw-html {
+       img[type="image/apng"] {
+               cursor: pointer;
+       }
+}
+
 .snes-button-a,
 .snes-button-b,
 .snes-button-x,