Merge branch 'dev' into ghcr-registry
This commit is contained in:
commit
a6d5bfe967
10 changed files with 181 additions and 46 deletions
|
@ -1,12 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<path d="M14 3V5H17.8L12.9 9.9L14.3 11.3L19 6.6V10.2H21V3H14Z" fill="black"/>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<path d="M5 5H10V3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V14.2H19V19H5V5Z" fill="black"/>
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
|
||||||
<g>
|
|
||||||
<polygon points="14,3 14,5 17.8,5 12.9,9.9 14.3,11.3 19,6.6 19,10.2 21,10.2 21,3 "/>
|
|
||||||
<path d="M3,10.2h2V5h5V3H5C3.9,3,3,3.9,3,5V10.2z"/>
|
|
||||||
<path d="M5,14.2H3V19c0,1.1,0.9,2,2,2h5v-2H5V14.2z"/>
|
|
||||||
<path d="M19,19h-5v2h5c1.1,0,2-0.9,2-2v-4.8h-2V19z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 297 B |
|
@ -42,7 +42,6 @@ function RawModal({
|
||||||
shouldCloseOnEsc={closeFromOutside}
|
shouldCloseOnEsc={closeFromOutside}
|
||||||
shouldCloseOnOverlayClick={closeFromOutside}
|
shouldCloseOnOverlayClick={closeFromOutside}
|
||||||
shouldReturnFocusAfterClose={false}
|
shouldReturnFocusAfterClose={false}
|
||||||
closeTimeoutMS={300}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,33 +1,9 @@
|
||||||
.ReactModal__Overlay {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 200ms var(--fluid-slide-up);
|
|
||||||
}
|
|
||||||
.ReactModal__Overlay--after-open{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.ReactModal__Overlay--before-close{
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ReactModal__Content {
|
|
||||||
transform: translateY(100%);
|
|
||||||
transition: transform 200ms var(--fluid-slide-up);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ReactModal__Content--after-open{
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ReactModal__Content--before-close{
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.raw-modal {
|
.raw-modal {
|
||||||
--small-modal-width: 525px;
|
--small-modal-width: 525px;
|
||||||
--medium-modal-width: 712px;
|
--medium-modal-width: 712px;
|
||||||
--large-modal-width: 1024px;
|
--large-modal-width: 1024px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
border-radius: var(--bo-radius);
|
border-radius: var(--bo-radius);
|
||||||
|
@ -61,3 +37,30 @@
|
||||||
background-color: var(--bg-overlay);
|
background-color: var(--bg-overlay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ReactModal__Overlay {
|
||||||
|
animation: raw-modal--overlay 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReactModal__Content {
|
||||||
|
animation: raw-modal--content 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes raw-modal--content {
|
||||||
|
0% {
|
||||||
|
transform: translateY(100px);
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes raw-modal--overlay {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
47
src/app/molecules/image-lightbox/ImageLightbox.jsx
Normal file
47
src/app/molecules/image-lightbox/ImageLightbox.jsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import './ImageLightbox.scss';
|
||||||
|
import FileSaver from 'file-saver';
|
||||||
|
|
||||||
|
import Text from '../../atoms/text/Text';
|
||||||
|
import RawModal from '../../atoms/modal/RawModal';
|
||||||
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
|
|
||||||
|
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
|
||||||
|
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
|
||||||
|
|
||||||
|
function ImageLightbox({
|
||||||
|
url, alt, isOpen, onRequestClose,
|
||||||
|
}) {
|
||||||
|
const handleDownload = () => {
|
||||||
|
FileSaver.saveAs(url, alt);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RawModal
|
||||||
|
className="image-lightbox__modal"
|
||||||
|
overlayClassName="image-lightbox__overlay"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onRequestClose={onRequestClose}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<div className="image-lightbox__header">
|
||||||
|
<Text variant="b2" weight="medium">{alt}</Text>
|
||||||
|
<IconButton onClick={() => window.open(url)} size="small" src={ExternalSVG} />
|
||||||
|
<IconButton onClick={handleDownload} size="small" src={DownloadSVG} />
|
||||||
|
</div>
|
||||||
|
<div className="image-lightbox__content">
|
||||||
|
<img src={url} alt={alt} />
|
||||||
|
</div>
|
||||||
|
</RawModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageLightbox.propTypes = {
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
alt: PropTypes.string.isRequired,
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
onRequestClose: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageLightbox;
|
50
src/app/molecules/image-lightbox/ImageLightbox.scss
Normal file
50
src/app/molecules/image-lightbox/ImageLightbox.scss
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
@use '../../partials/flex';
|
||||||
|
@use '../../partials/text';
|
||||||
|
|
||||||
|
.image-lightbox__modal {
|
||||||
|
box-shadow: none;
|
||||||
|
width: unset;
|
||||||
|
gap: var(--sp-normal);
|
||||||
|
|
||||||
|
border-radius: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
& .text {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
& .ic-raw {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-lightbox__overlay {
|
||||||
|
background-color: var(--bg-overlay-low);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.image-lightbox__header > *,
|
||||||
|
.image-lightbox__content > * {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
.image-lightbox__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& > .text {
|
||||||
|
@extend .cp-fx__item-one;
|
||||||
|
@extend .cp-txt__ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.image-lightbox__content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
max-height: 90vh;
|
||||||
|
|
||||||
|
& img {
|
||||||
|
background-color: var(--bg-surface-low);
|
||||||
|
object-fit: contain;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
border-radius: var(--bo-radius);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import { BlurhashCanvas } from 'react-blurhash';
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
import IconButton from '../../atoms/button/IconButton';
|
import IconButton from '../../atoms/button/IconButton';
|
||||||
import Spinner from '../../atoms/spinner/Spinner';
|
import Spinner from '../../atoms/spinner/Spinner';
|
||||||
|
import ImageLightbox from '../image-lightbox/ImageLightbox';
|
||||||
|
|
||||||
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
|
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
|
||||||
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
|
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
|
||||||
|
@ -124,6 +125,7 @@ function Image({
|
||||||
}) {
|
}) {
|
||||||
const [url, setUrl] = useState(null);
|
const [url, setUrl] = useState(null);
|
||||||
const [blur, setBlur] = useState(true);
|
const [blur, setBlur] = useState(true);
|
||||||
|
const [lightbox, setLightbox] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let unmounted = false;
|
let unmounted = false;
|
||||||
|
@ -138,14 +140,42 @@ function Image({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const toggleLightbox = () => {
|
||||||
|
if (!url) return;
|
||||||
|
setLightbox(!lightbox);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="file-container">
|
<div className="file-container">
|
||||||
<FileHeader name={name} link={url || link} type={type} external />
|
<div
|
||||||
<div style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }} className="image-container">
|
style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }}
|
||||||
|
className="image-container"
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
onClick={toggleLightbox}
|
||||||
|
onKeyDown={toggleLightbox}
|
||||||
|
>
|
||||||
{ blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />}
|
{ blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />}
|
||||||
{ url !== null && <img style={{ display: blur ? 'none' : 'unset' }} onLoad={() => setBlur(false)} src={url || link} alt={name} />}
|
{ url !== null && (
|
||||||
|
<img
|
||||||
|
style={{ display: blur ? 'none' : 'unset' }}
|
||||||
|
onLoad={() => setBlur(false)}
|
||||||
|
src={url || link}
|
||||||
|
alt={name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{url && (
|
||||||
|
<ImageLightbox
|
||||||
|
url={url}
|
||||||
|
alt={name}
|
||||||
|
isOpen={lightbox}
|
||||||
|
onRequestClose={toggleLightbox}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Image.defaultProps = {
|
Image.defaultProps = {
|
||||||
|
|
|
@ -62,6 +62,13 @@
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.image-container {
|
||||||
|
max-height: 460px;
|
||||||
|
img {
|
||||||
|
cursor: pointer;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.video-container {
|
.video-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -290,6 +290,11 @@ function MessageEdit({ body, onSave, onCancel }) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === 'Enter' && e.shiftKey === false) {
|
if (e.key === 'Enter' && e.shiftKey === false) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSave(editInputRef.current.value);
|
onSave(editInputRef.current.value);
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
&__preview > img {
|
&__preview > img {
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
border-radius: var(--bo-radius);
|
border-radius: var(--bo-radius);
|
||||||
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
&__icon {
|
&__icon {
|
||||||
padding: var(--sp-extra-tight);
|
padding: var(--sp-extra-tight);
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
|
|
||||||
/* shadow and overlay */
|
/* shadow and overlay */
|
||||||
--bg-overlay: rgba(0, 0, 0, 20%);
|
--bg-overlay: rgba(0, 0, 0, 20%);
|
||||||
--bg-overlay-low: rgba(0, 0, 0, 85%);
|
--bg-overlay-low: rgba(0, 0, 0, 50%);
|
||||||
|
|
||||||
--bs-popup: 0 0 16px rgba(0, 0, 0, 10%);
|
--bs-popup: 0 0 16px rgba(0, 0, 0, 10%);
|
||||||
|
|
||||||
|
@ -264,7 +264,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* shadow and overlay */
|
/* shadow and overlay */
|
||||||
--bg-overlay: rgba(0, 0, 0, 50%);
|
--bg-overlay: rgba(0, 0, 0, 60%);
|
||||||
|
--bg-overlay-low: rgba(0, 0, 0, 80%);
|
||||||
|
|
||||||
--bs-popup: 0 0 16px rgba(0, 0, 0, 25%);
|
--bs-popup: 0 0 16px rgba(0, 0, 0, 25%);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue