Fixes and refactor for compose UI with media uploads

Somehow prettier (for CSS) start running properly
This commit is contained in:
Lim Chee Aun 2022-12-12 16:27:44 +08:00
parent f7571f6df1
commit b988b10c3d
3 changed files with 189 additions and 113 deletions

View file

@ -24,7 +24,7 @@
color: var(--text-insignificant-color); color: var(--text-insignificant-color);
} }
#compose-container textarea{ #compose-container textarea {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
height: 3em; height: 3em;
@ -114,14 +114,16 @@
#compose-container .toolbar-button:has([disabled]) > * { #compose-container .toolbar-button:has([disabled]) > * {
filter: opacity(0.3); filter: opacity(0.3);
} }
#compose-container .toolbar-button:not(.show-field) :is(input[type="checkbox"], select, input[type="file"]) { #compose-container
.toolbar-button:not(.show-field)
:is(input[type='checkbox'], select, input[type='file']) {
opacity: 0; opacity: 0;
position: absolute; position: absolute;
left: 0; left: 0;
height: 100%; height: 100%;
margin: 0; margin: 0;
} }
#compose-container .toolbar-button input[type="file"] { #compose-container .toolbar-button input[type='file'] {
/* Move this out of the way, to fix cursor: pointer bug */ /* Move this out of the way, to fix cursor: pointer bug */
left: -100vw !important; left: -100vw !important;
} }
@ -201,7 +203,7 @@
#compose-container .media-preview { #compose-container .media-preview {
flex-shrink: 1; flex-shrink: 1;
} }
#compose-container .media-preview > *{ #compose-container .media-preview > * {
min-width: 80px; min-width: 80px;
width: 80px !important; width: 80px !important;
height: 80px; height: 80px;
@ -215,6 +217,22 @@
flex-grow: 1; flex-grow: 1;
resize: none; resize: none;
} }
#compose-container .media-attachments .media-desc {
flex-grow: 1;
}
#compose-container .media-attachments .media-desc p {
font-size: 90%;
margin: 0;
padding: 0;
/* clamp 2 lines */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
#compose-container .media-attachments .media-desc p i {
color: var(--text-insignificant-color);
}
#compose-container .media-aside { #compose-container .media-aside {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -226,7 +244,10 @@
align-self: flex-start; align-self: flex-start;
color: var(--text-insignificant-color); color: var(--text-insignificant-color);
} }
#compose-container .media-aside .close-button:hover {
color: var(--text-color);
}
#compose-container .media-aside .uploaded { #compose-container .media-aside .uploaded {
color: var(--green-color); color: var(--green-color);
margin-bottom: 4px; margin-bottom: 4px;
} }

View file

@ -263,26 +263,33 @@ export default ({ onClose, replyToStatus }) => {
if (mediaAttachments.length > 0) { if (mediaAttachments.length > 0) {
// Upload media attachments first // Upload media attachments first
const mediaPromises = mediaAttachments.map((attachment) => { const mediaPromises = mediaAttachments.map((attachment) => {
const params = { const { file, description, sourceDescription, id } =
file: attachment.file, attachment;
description: attachment.description || undefined, console.log('UPLOADING', attachment);
}; if (id) {
return masto.mediaAttachments.create(params).then((res) => { // If already uploaded
// Update media attachment with ID return attachment;
if (res.id) { } else {
attachment.id = res.id; const params = {
} file,
return res; description,
}); };
return masto.mediaAttachments.create(params).then((res) => {
// Update media attachment with ID
if (res.id) {
attachment.id = res.id;
}
return res;
});
}
}); });
const results = await Promise.allSettled(mediaPromises); const results = await Promise.allSettled(mediaPromises);
// If any failed, return // If any failed, return
if ( if (
results.some( results.some((result) => {
(result) => return result.status === 'rejected' || !result.value?.id;
result.status === 'rejected' || !result.value.id, })
)
) { ) {
setUIState('error'); setUIState('error');
// Alert all the reasons // Alert all the reasons
@ -314,7 +321,8 @@ export default ({ onClose, replyToStatus }) => {
newStatus, newStatus,
}); });
} catch (e) { } catch (e) {
alert(e); console.error(e);
alert(e?.reason || e);
setUIState('error'); setUIState('error');
} }
})(); })();
@ -410,63 +418,25 @@ export default ({ onClose, replyToStatus }) => {
{mediaAttachments.length > 0 && ( {mediaAttachments.length > 0 && (
<div class="media-attachments"> <div class="media-attachments">
{mediaAttachments.map((attachment, i) => { {mediaAttachments.map((attachment, i) => {
const { url, type, id } = attachment; const { id } = attachment;
const suffixType = type.split('/')[0];
return ( return (
<div class="media-attachment" key={i + id}> <MediaAttachment
<div class="media-preview"> key={i + id}
{suffixType === 'image' ? ( attachment={attachment}
<img src={url} alt="" /> disabled={uiState === 'loading'}
) : suffixType === 'video' ? ( onDescriptionChange={(value) => {
<video src={url} playsinline muted /> setMediaAttachments((attachments) => {
) : suffixType === 'audio' ? ( const newAttachments = [...attachments];
<audio src={url} controls /> newAttachments[i].description = value;
) : null} return newAttachments;
</div> });
<textarea }}
placeholder={ onRemove={() => {
{ setMediaAttachments((attachments) => {
image: 'Image description', return attachments.filter((_, j) => j !== i);
video: 'Video description', });
audio: 'Audio description', }}
}[suffixType] />
}
autoCapitalize="sentences"
autoComplete="on"
autoCorrect="on"
spellCheck="true"
dir="auto"
disabled={uiState === 'loading'}
maxlength="1500"
// TODO: Un-hard-code this maxlength, ref: https://github.com/mastodon/mastodon/blob/b59fb28e90bc21d6fd1a6bafd13cfbd81ab5be54/app/models/media_attachment.rb#L39
onInput={(e) => {
const { value } = e.target;
// Modify `description` in media attachment
setMediaAttachments((attachments) => {
const newAttachments = [...attachments];
newAttachments[i].description = value;
return newAttachments;
});
}}
></textarea>
<div class="media-aside">
<button
type="button"
class="plain close-button"
disabled={uiState === 'loading'}
onClick={() => {
setMediaAttachments((attachments) => {
return attachments.filter((_, j) => j !== i);
});
}}
>
<Icon icon="x" />
</button>
{!!id && (
<Icon icon="upload" title="Uploaded" class="uploaded" />
)}
</div>
</div>
); );
})} })}
</div> </div>
@ -529,3 +499,65 @@ export default ({ onClose, replyToStatus }) => {
</div> </div>
); );
}; };
function MediaAttachment({
attachment,
disabled,
onDescriptionChange = () => {},
onRemove = () => {},
}) {
const { url, type, id } = attachment;
const suffixType = type.split('/')[0];
return (
<div class="media-attachment">
<div class="media-preview">
{suffixType === 'image' ? (
<img src={url} alt="" />
) : suffixType === 'video' ? (
<video src={url} playsinline muted />
) : suffixType === 'audio' ? (
<audio src={url} controls />
) : null}
</div>
{!!id ? (
<div class="media-desc">
<span class="tag">Uploaded</span>
<p>{attachment.description || <i>No description</i>}</p>
</div>
) : (
<textarea
value={attachment.description || ''}
placeholder={
{
image: 'Image description',
video: 'Video description',
audio: 'Audio description',
}[suffixType]
}
autoCapitalize="sentences"
autoComplete="on"
autoCorrect="on"
spellCheck="true"
dir="auto"
disabled={disabled}
maxlength="1500" // Not unicode-aware :(
// TODO: Un-hard-code this maxlength, ref: https://github.com/mastodon/mastodon/blob/b59fb28e90bc21d6fd1a6bafd13cfbd81ab5be54/app/models/media_attachment.rb#L39
onInput={(e) => {
const { value } = e.target;
onDescriptionChange(value);
}}
></textarea>
)}
<div class="media-aside">
<button
type="button"
class="plain close-button"
disabled={disabled}
onClick={onRemove}
>
<Icon icon="x" />
</button>
</div>
</div>
);
}

View file

@ -1,23 +1,28 @@
/* REBLOG + REPLY-TO */ /* REBLOG + REPLY-TO */
.status-reblog { .status-reblog {
background: linear-gradient(to bottom right, var( background: linear-gradient(
--reblog-faded-color to bottom right,
), transparent 160px); var(--reblog-faded-color),
transparent 160px
);
} }
.status-reply-to { .status-reply-to {
background: linear-gradient(to bottom right, var( background: linear-gradient(
--reply-to-faded-color to bottom right,
), transparent 160px); var(--reply-to-faded-color),
transparent 160px
);
} }
.status-reblog .status-reply-to { .status-reblog .status-reply-to {
background: linear-gradient(to top left, var( background: linear-gradient(
--reply-to-faded-color to top left,
), transparent 160px); var(--reply-to-faded-color),
transparent 160px
);
} }
.visibility-direct { .visibility-direct {
/* diagonal stripes of yellow */ --yellow-stripes: repeating-linear-gradient(
background-image: repeating-linear-gradient(
-45deg, -45deg,
var(--reply-to-faded-color), var(--reply-to-faded-color),
var(--reply-to-faded-color) 10px, var(--reply-to-faded-color) 10px,
@ -25,6 +30,8 @@
transparent 10px, transparent 10px,
transparent 20px transparent 20px
); );
/* diagonal stripes of yellow */
background-image: var(--yellow-stripes);
} }
/* STATUS PRE META */ /* STATUS PRE META */
@ -51,7 +58,18 @@
align-items: flex-start; align-items: flex-start;
} }
.status.large { .status.large {
--fade-in-out-bg: linear-gradient(
to bottom,
transparent,
var(--bg-color) 70px,
var(--bg-color) calc(100% - 50px),
transparent
);
padding-bottom: 8px; padding-bottom: 8px;
background-image: var(--fade-in-out-bg);
}
.status.large.visibility-direct {
background-image: var(--fade-in-out-bg), var(--yellow-stripes);
} }
.status-pre-meta + .status { .status-pre-meta + .status {
padding-top: 8px; padding-top: 8px;
@ -87,11 +105,11 @@
min-height: 50px; min-height: 50px;
justify-content: space-between; justify-content: space-between;
} }
.status > .container > .meta .arrow { .status > .container > .meta .arrow {
color: var(--reply-to-color); color: var(--reply-to-color);
vertical-align: middle; vertical-align: middle;
} }
.status > .container > .meta :is(.time, .edited) { .status > .container > .meta :is(.time, .edited) {
color: inherit; color: inherit;
text-align: end; text-align: end;
opacity: 0.5; opacity: 0.5;
@ -100,17 +118,16 @@
margin-left: 4px; margin-left: 4px;
white-space: nowrap; white-space: nowrap;
} }
.status > .container > .meta a.time:hover { .status > .container > .meta a.time:hover {
text-decoration: underline; text-decoration: underline;
} }
.status > .container > .meta .reply-to { .status > .container > .meta .reply-to {
opacity: 0.5; opacity: 0.5;
font-size: smaller; font-size: smaller;
} }
.status.large .content-container { .status.large .content-container {
margin-left: calc(-50px - 16px); margin-left: calc(-50px - 16px);
background-image: linear-gradient(to bottom, transparent, var(--bg-color) 10px, var(--bg-color));
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
} }
@ -124,13 +141,13 @@
align-items: center; align-items: center;
} }
.status .content-container.has-spoiler .spoiler ~ * { .status .content-container.has-spoiler .spoiler ~ * {
filter: blur(6px) invert(.5); filter: blur(6px) invert(0.5);
pointer-events: none; pointer-events: none;
transition: filter .5s; transition: filter 0.5s;
user-select: none; user-select: none;
} }
.status .content-container.has-spoiler .spoiler ~ .content ~ * { .status .content-container.has-spoiler .spoiler ~ .content ~ * {
opacity: .5; opacity: 0.5;
} }
.status .content-container.show-spoiler .spoiler { .status .content-container.show-spoiler .spoiler {
border-style: dotted; border-style: dotted;
@ -148,7 +165,7 @@
margin-top: 8px; margin-top: 8px;
} }
.status .content p { .status .content p {
margin-block: .75em; margin-block: 0.75em;
} }
.status .content p:first-child { .status .content p:first-child {
margin-block-start: 0; margin-block-start: 0;
@ -236,7 +253,7 @@
height: 70px; height: 70px;
border-radius: 50%; border-radius: 50%;
background-color: var(--bg-blur-color); background-color: var(--bg-blur-color);
backdrop-filter: blur(6px) saturate(3) invert(.2); backdrop-filter: blur(6px) saturate(3) invert(0.2);
z-index: 1; z-index: 1;
} }
.status .media-video:after { .status .media-video:after {
@ -249,8 +266,9 @@
width: 0; width: 0;
height: 0; height: 0;
border-style: solid; border-style: solid;
border-width: 15px 0 15px 26.0px; border-width: 15px 0 15px 26px;
border-color: transparent transparent transparent var(--text-insignificant-color); border-color: transparent transparent transparent
var(--text-insignificant-color);
pointer-events: none; pointer-events: none;
opacity: 0.75; opacity: 0.75;
z-index: 2; z-index: 2;
@ -305,7 +323,7 @@
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
display: box; display: box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
box-orient: vertical; box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
line-clamp: 2; line-clamp: 2;
@ -318,7 +336,7 @@
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
display: box; display: box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
box-orient: vertical; box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
line-clamp: 2; line-clamp: 2;
@ -351,9 +369,15 @@ a.card:hover {
display: flex; display: flex;
gap: 8px; gap: 8px;
justify-content: space-between; justify-content: space-between;
background-image: linear-gradient(to right, var(--link-faded-color), var(--link-faded-color) var(--percentage), transparent var(--percentage), transparent); background-image: linear-gradient(
to right,
var(--link-faded-color),
var(--link-faded-color) var(--percentage),
transparent var(--percentage),
transparent
);
border-radius: 8px; border-radius: 8px;
border: 1px solid rgba(128, 128, 128, .1); border: 1px solid rgba(128, 128, 128, 0.1);
align-items: center; align-items: center;
} }
.poll-label { .poll-label {
@ -391,8 +415,7 @@ a.card:hover {
} }
.status.large .extra-meta { .status.large .extra-meta {
padding-top: 0; padding-top: 0;
margin-left: calc(-50px - 16px); margin-left: calc(-50px - 4px);
background-color: var(--bg-color);
} }
/* ACTIONS */ /* ACTIONS */
@ -405,14 +428,12 @@ a.card:hover {
justify-content: space-between; justify-content: space-between;
} }
.status.large .actions { .status.large .actions {
/* margin-left: -12px; */
padding-top: 8px; padding-top: 8px;
padding-bottom: 16px; padding-bottom: 16px;
margin-left: calc(-50px - 16px); margin-left: calc(-50px - 16px);
background-image: linear-gradient(to bottom, var(--bg-color), var(--bg-color) calc(100% - 10px), transparent);
} }
.status .actions > * { .status .actions > * {
opacity: .5; opacity: 0.5;
transition: opacity 0.2s ease-in-out; transition: opacity 0.2s ease-in-out;
} }
.status:hover .actions > * { .status:hover .actions > * {
@ -459,9 +480,11 @@ a.card:hover {
width: 100%; width: 100%;
font-size: 90%; font-size: 90%;
border: 1px solid var(--outline-color); border: 1px solid var(--outline-color);
background: linear-gradient(to bottom right, var( background: linear-gradient(
--bg-faded-color to bottom right,
), transparent 160px); var(--bg-faded-color),
transparent 160px
);
} }
/* MISC */ /* MISC */
@ -485,7 +508,7 @@ a.card:hover {
min-height: 50dvh; min-height: 50dvh;
} }
#edit-history :is(ol, ol li){ #edit-history :is(ol, ol li) {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;