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);
}
#compose-container textarea{
#compose-container textarea {
width: 100%;
max-width: 100%;
height: 3em;
@ -114,14 +114,16 @@
#compose-container .toolbar-button:has([disabled]) > * {
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;
position: absolute;
left: 0;
height: 100%;
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 */
left: -100vw !important;
}
@ -201,7 +203,7 @@
#compose-container .media-preview {
flex-shrink: 1;
}
#compose-container .media-preview > *{
#compose-container .media-preview > * {
min-width: 80px;
width: 80px !important;
height: 80px;
@ -215,6 +217,22 @@
flex-grow: 1;
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 {
display: flex;
flex-direction: column;
@ -226,6 +244,9 @@
align-self: flex-start;
color: var(--text-insignificant-color);
}
#compose-container .media-aside .close-button:hover {
color: var(--text-color);
}
#compose-container .media-aside .uploaded {
color: var(--green-color);
margin-bottom: 4px;

View file

@ -263,26 +263,33 @@ export default ({ onClose, replyToStatus }) => {
if (mediaAttachments.length > 0) {
// Upload media attachments first
const mediaPromises = mediaAttachments.map((attachment) => {
const params = {
file: attachment.file,
description: attachment.description || undefined,
};
return masto.mediaAttachments.create(params).then((res) => {
// Update media attachment with ID
if (res.id) {
attachment.id = res.id;
}
return res;
});
const { file, description, sourceDescription, id } =
attachment;
console.log('UPLOADING', attachment);
if (id) {
// If already uploaded
return attachment;
} else {
const params = {
file,
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);
// If any failed, return
if (
results.some(
(result) =>
result.status === 'rejected' || !result.value.id,
)
results.some((result) => {
return result.status === 'rejected' || !result.value?.id;
})
) {
setUIState('error');
// Alert all the reasons
@ -314,7 +321,8 @@ export default ({ onClose, replyToStatus }) => {
newStatus,
});
} catch (e) {
alert(e);
console.error(e);
alert(e?.reason || e);
setUIState('error');
}
})();
@ -410,63 +418,25 @@ export default ({ onClose, replyToStatus }) => {
{mediaAttachments.length > 0 && (
<div class="media-attachments">
{mediaAttachments.map((attachment, i) => {
const { url, type, id } = attachment;
const suffixType = type.split('/')[0];
const { id } = attachment;
return (
<div class="media-attachment" key={i + id}>
<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>
<textarea
placeholder={
{
image: 'Image description',
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>
<MediaAttachment
key={i + id}
attachment={attachment}
disabled={uiState === 'loading'}
onDescriptionChange={(value) => {
setMediaAttachments((attachments) => {
const newAttachments = [...attachments];
newAttachments[i].description = value;
return newAttachments;
});
}}
onRemove={() => {
setMediaAttachments((attachments) => {
return attachments.filter((_, j) => j !== i);
});
}}
/>
);
})}
</div>
@ -529,3 +499,65 @@ export default ({ onClose, replyToStatus }) => {
</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 */
.status-reblog {
background: linear-gradient(to bottom right, var(
--reblog-faded-color
), transparent 160px);
background: linear-gradient(
to bottom right,
var(--reblog-faded-color),
transparent 160px
);
}
.status-reply-to {
background: linear-gradient(to bottom right, var(
--reply-to-faded-color
), transparent 160px);
background: linear-gradient(
to bottom right,
var(--reply-to-faded-color),
transparent 160px
);
}
.status-reblog .status-reply-to {
background: linear-gradient(to top left, var(
--reply-to-faded-color
), transparent 160px);
background: linear-gradient(
to top left,
var(--reply-to-faded-color),
transparent 160px
);
}
.visibility-direct {
/* diagonal stripes of yellow */
background-image: repeating-linear-gradient(
--yellow-stripes: repeating-linear-gradient(
-45deg,
var(--reply-to-faded-color),
var(--reply-to-faded-color) 10px,
@ -25,6 +30,8 @@
transparent 10px,
transparent 20px
);
/* diagonal stripes of yellow */
background-image: var(--yellow-stripes);
}
/* STATUS PRE META */
@ -51,7 +58,18 @@
align-items: flex-start;
}
.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;
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 {
padding-top: 8px;
@ -87,11 +105,11 @@
min-height: 50px;
justify-content: space-between;
}
.status > .container > .meta .arrow {
.status > .container > .meta .arrow {
color: var(--reply-to-color);
vertical-align: middle;
}
.status > .container > .meta :is(.time, .edited) {
.status > .container > .meta :is(.time, .edited) {
color: inherit;
text-align: end;
opacity: 0.5;
@ -100,17 +118,16 @@
margin-left: 4px;
white-space: nowrap;
}
.status > .container > .meta a.time:hover {
.status > .container > .meta a.time:hover {
text-decoration: underline;
}
.status > .container > .meta .reply-to {
.status > .container > .meta .reply-to {
opacity: 0.5;
font-size: smaller;
}
.status.large .content-container {
margin-left: calc(-50px - 16px);
background-image: linear-gradient(to bottom, transparent, var(--bg-color) 10px, var(--bg-color));
padding-top: 10px;
padding-bottom: 10px;
}
@ -124,13 +141,13 @@
align-items: center;
}
.status .content-container.has-spoiler .spoiler ~ * {
filter: blur(6px) invert(.5);
filter: blur(6px) invert(0.5);
pointer-events: none;
transition: filter .5s;
transition: filter 0.5s;
user-select: none;
}
.status .content-container.has-spoiler .spoiler ~ .content ~ * {
opacity: .5;
opacity: 0.5;
}
.status .content-container.show-spoiler .spoiler {
border-style: dotted;
@ -148,7 +165,7 @@
margin-top: 8px;
}
.status .content p {
margin-block: .75em;
margin-block: 0.75em;
}
.status .content p:first-child {
margin-block-start: 0;
@ -236,7 +253,7 @@
height: 70px;
border-radius: 50%;
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;
}
.status .media-video:after {
@ -249,8 +266,9 @@
width: 0;
height: 0;
border-style: solid;
border-width: 15px 0 15px 26.0px;
border-color: transparent transparent transparent var(--text-insignificant-color);
border-width: 15px 0 15px 26px;
border-color: transparent transparent transparent
var(--text-insignificant-color);
pointer-events: none;
opacity: 0.75;
z-index: 2;
@ -351,9 +369,15 @@ a.card:hover {
display: flex;
gap: 8px;
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: 1px solid rgba(128, 128, 128, .1);
border: 1px solid rgba(128, 128, 128, 0.1);
align-items: center;
}
.poll-label {
@ -391,8 +415,7 @@ a.card:hover {
}
.status.large .extra-meta {
padding-top: 0;
margin-left: calc(-50px - 16px);
background-color: var(--bg-color);
margin-left: calc(-50px - 4px);
}
/* ACTIONS */
@ -405,14 +428,12 @@ a.card:hover {
justify-content: space-between;
}
.status.large .actions {
/* margin-left: -12px; */
padding-top: 8px;
padding-bottom: 16px;
margin-left: calc(-50px - 16px);
background-image: linear-gradient(to bottom, var(--bg-color), var(--bg-color) calc(100% - 10px), transparent);
}
.status .actions > * {
opacity: .5;
opacity: 0.5;
transition: opacity 0.2s ease-in-out;
}
.status:hover .actions > * {
@ -459,9 +480,11 @@ a.card:hover {
width: 100%;
font-size: 90%;
border: 1px solid var(--outline-color);
background: linear-gradient(to bottom right, var(
--bg-faded-color
), transparent 160px);
background: linear-gradient(
to bottom right,
var(--bg-faded-color),
transparent 160px
);
}
/* MISC */
@ -485,7 +508,7 @@ a.card:hover {
min-height: 50dvh;
}
#edit-history :is(ol, ol li){
#edit-history :is(ol, ol li) {
list-style: none;
margin: 0;
padding: 0;