Compare commits

...

5 commits

Author SHA1 Message Date
Natsu Kagami 42f1f45767
Merge branch 'main' of github.com:cheeaun/phanpy 2023-07-19 09:33:39 +02:00
Lim Chee Aun 7286a4e03b Attempt to fix menu confirm not opening 2023-07-19 15:19:03 +08:00
Lim Chee Aun 1f0d2eebe6 Having fun with multi-stacking modals 2023-07-18 20:40:10 +08:00
Lim Chee Aun 38a13b07c5 Fix boost menu bug 2023-07-18 18:45:38 +08:00
Lim Chee Aun 92a4f502a0 Experimental Auto Inline Translation (AIT)
For short posts for now and throttled API calls
2023-07-18 13:31:26 +08:00
4 changed files with 138 additions and 72 deletions

View file

@ -12,6 +12,13 @@
backdrop-filter: blur(24px); backdrop-filter: blur(24px);
animation: appear 0.5s var(--timing-function) both; animation: appear 0.5s var(--timing-function) both;
} }
#modal-container > div .sheet {
transition: transform 0.3s var(--timing-function);
transform-origin: center bottom;
}
#modal-container > div:has(~ div) .sheet {
transform: scale(0.975);
}
#modal-container > .light { #modal-container > .light {
backdrop-filter: saturate(0.75); backdrop-filter: saturate(0.75);

View file

@ -57,6 +57,7 @@ import MenuLink from './menu-link';
import RelativeTime from './relative-time'; import RelativeTime from './relative-time';
import TranslationBlock from './translation-block'; import TranslationBlock from './translation-block';
const INLINE_TRASNSLATE_LIMIT = 140;
const throttle = pThrottle({ const throttle = pThrottle({
limit: 1, limit: 1,
interval: 1000, interval: 1000,
@ -244,11 +245,23 @@ function Status({
); );
} }
const isSizeLarge = size === 'l';
const [forceTranslate, setForceTranslate] = useState(_forceTranslate); const [forceTranslate, setForceTranslate] = useState(_forceTranslate);
const targetLanguage = getTranslateTargetLanguage(true); const targetLanguage = getTranslateTargetLanguage(true);
const contentTranslationHideLanguages = const contentTranslationHideLanguages =
snapStates.settings.contentTranslationHideLanguages || []; snapStates.settings.contentTranslationHideLanguages || [];
if (!snapStates.settings.contentTranslation) enableTranslate = false; if (!snapStates.settings.contentTranslation) enableTranslate = false;
const inlineTranslate = useMemo(() => {
return (
!isSizeLarge &&
!spoilerText &&
!poll &&
!mediaAttachments?.length &&
content?.length > 0 &&
content?.length <= INLINE_TRASNSLATE_LIMIT
);
}, [isSizeLarge, content, spoilerText, poll, mediaAttachments]);
const [showEdited, setShowEdited] = useState(false); const [showEdited, setShowEdited] = useState(false);
const [showReactions, setShowReactions] = useState(false); const [showReactions, setShowReactions] = useState(false);
@ -306,7 +319,6 @@ function Status({
const createdDateText = niceDateTime(createdAtDate); const createdDateText = niceDateTime(createdAtDate);
const editedDateText = editedAt && niceDateTime(editedAtDate); const editedDateText = editedAt && niceDateTime(editedAtDate);
const isSizeLarge = size === 'l';
// Can boost if: // Can boost if:
// - authenticated AND // - authenticated AND
// - visibility != direct OR // - visibility != direct OR
@ -526,7 +538,7 @@ function Status({
confirmLabel={ confirmLabel={
<> <>
<Icon icon="rocket" /> <Icon icon="rocket" />
<span>Unboost?</span> <span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>
</> </>
} }
menuFooter={ menuFooter={
@ -890,7 +902,8 @@ function Status({
// Higher than the backdrop // Higher than the backdrop
zIndex: 1001, zIndex: 1001,
}, },
onClick: () => { onClick: (e) => {
if (e.target === e.currentTarget)
menuInstanceRef.current?.closeMenu?.(); menuInstanceRef.current?.closeMenu?.();
}, },
}} }}
@ -1091,10 +1104,13 @@ function Status({
}} }}
/> />
)} )}
{((enableTranslate && !!content.trim() && differentLanguage) || {(((enableTranslate || inlineTranslate) &&
!!content.trim() &&
differentLanguage) ||
forceTranslate) && ( forceTranslate) && (
<TranslationBlock <TranslationBlock
forceTranslate={forceTranslate} forceTranslate={forceTranslate || inlineTranslate}
mini={inlineTranslate}
sourceLanguage={language} sourceLanguage={language}
text={ text={
(spoilerText ? `${spoilerText}\n\n` : '') + (spoilerText ? `${spoilerText}\n\n` : '') +
@ -1224,19 +1240,25 @@ function Status({
disabled={!canBoost} disabled={!canBoost}
/> />
</div> */} </div> */}
<Menu <MenuConfirm
portal={{ disabled={!canBoost}
target: onClick={confirmBoostStatus}
document.querySelector('.status-deck') || document.body, confirmLabel={
}} <>
align="start" <Icon icon="rocket" />
gap={4} <span>{reblogged ? 'Unboost?' : 'Boost to everyone?'}</span>
overflow="auto" </>
viewScroll="close" }
boundingBoxPadding="8 8 8 8" menuFooter={
shift={-8} mediaNoDesc &&
menuClassName="menu-emphasized" !reblogged && (
menuButton={({ open }) => ( <div class="footer">
<Icon icon="alert" />
Some media have no descriptions.
</div>
)
}
>
<div class="action has-count"> <div class="action has-count">
<StatusButton <StatusButton
checked={reblogged} checked={reblogged}
@ -1246,22 +1268,10 @@ function Status({
icon="rocket" icon="rocket"
count={reblogsCount} count={reblogsCount}
// onClick={boostStatus} // onClick={boostStatus}
disabled={open || !canBoost} disabled={!canBoost}
/> />
</div> </div>
)} </MenuConfirm>
>
<MenuItem onClick={confirmBoostStatus}>
<Icon icon="rocket" />
<span>Boost to everyone?</span>
</MenuItem>
{mediaNoDesc && (
<div class="footer">
<Icon icon="alert" />
Some media have no descriptions.
</div>
)}
</Menu>
<div class="action has-count"> <div class="action has-count">
<StatusButton <StatusButton
checked={favourited} checked={favourited}

View file

@ -105,3 +105,22 @@
overflow: visible; overflow: visible;
mask-image: none; mask-image: none;
} }
/* MINI */
.status-translation-block-mini {
display: flex;
margin: 8px 0 0;
padding: 8px 0 0;
font-size: 90%;
border-top: var(--hairline-width) solid var(--outline-color);
color: var(--text-insignificant-color);
gap: 8px;
transition: color 0.3s ease-in-out;
}
.status-translation-block-mini .icon {
margin-top: 2px;
}
.status:is(:hover, :active) .status-translation-block-mini {
color: var(--text-color);
}

View file

@ -1,5 +1,6 @@
import './translation-block.css'; import './translation-block.css';
import pThrottle from 'p-throttle';
import { useEffect, useRef, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import sourceLanguages from '../data/lingva-source-languages'; import sourceLanguages from '../data/lingva-source-languages';
@ -9,28 +10,13 @@ import localeCode2Text from '../utils/localeCode2Text';
import Icon from './icon'; import Icon from './icon';
import Loader from './loader'; import Loader from './loader';
function TranslationBlock({ const throttle = pThrottle({
forceTranslate, limit: 1,
sourceLanguage, interval: 2000,
onTranslate, });
text = '',
}) {
const targetLang = getTranslateTargetLanguage(true);
const [uiState, setUIState] = useState('default');
const [pronunciationContent, setPronunciationContent] = useState(null);
const [translatedContent, setTranslatedContent] = useState(null);
const [detectedLang, setDetectedLang] = useState(null);
const detailsRef = useRef();
const sourceLangText = sourceLanguage function lingvaTranslate(text, source, target) {
? localeCode2Text(sourceLanguage) console.log('TRANSLATE', text, source, target);
: null;
const targetLangText = localeCode2Text(targetLang);
const apiSourceLang = useRef('auto');
if (!onTranslate)
onTranslate = (source, target) => {
console.log('TRANSLATE', source, target, text);
// Using another API instance instead of lingva.ml because of this bug (slashes don't work): // Using another API instance instead of lingva.ml because of this bug (slashes don't work):
// https://github.com/thedaviddelta/lingva-translate/issues/68 // https://github.com/thedaviddelta/lingva-translate/issues/68
return fetch( return fetch(
@ -50,13 +36,38 @@ function TranslationBlock({
// return masto.v1.statuses.translate(id, { // return masto.v1.statuses.translate(id, {
// lang: DEFAULT_LANG, // lang: DEFAULT_LANG,
// }); // });
}; }
const throttledLingvaTranslate = throttle(lingvaTranslate);
function TranslationBlock({
forceTranslate,
sourceLanguage,
onTranslate,
text = '',
mini,
}) {
const targetLang = getTranslateTargetLanguage(true);
const [uiState, setUIState] = useState('default');
const [pronunciationContent, setPronunciationContent] = useState(null);
const [translatedContent, setTranslatedContent] = useState(null);
const [detectedLang, setDetectedLang] = useState(null);
const detailsRef = useRef();
const sourceLangText = sourceLanguage
? localeCode2Text(sourceLanguage)
: null;
const targetLangText = localeCode2Text(targetLang);
const apiSourceLang = useRef('auto');
if (!onTranslate) {
onTranslate = mini ? throttledLingvaTranslate : lingvaTranslate;
}
const translate = async () => { const translate = async () => {
setUIState('loading'); setUIState('loading');
try { try {
const { content, detectedSourceLanguage, provider, ...props } = const { content, detectedSourceLanguage, provider, ...props } =
await onTranslate(apiSourceLang.current, targetLang); await onTranslate(text, apiSourceLang.current, targetLang);
if (content) { if (content) {
if (detectedSourceLanguage) { if (detectedSourceLanguage) {
const detectedLangText = localeCode2Text(detectedSourceLanguage); const detectedLangText = localeCode2Text(detectedSourceLanguage);
@ -70,11 +81,13 @@ function TranslationBlock({
} }
setTranslatedContent(content); setTranslatedContent(content);
setUIState('default'); setUIState('default');
if (!mini) {
detailsRef.current.open = true; detailsRef.current.open = true;
detailsRef.current.scrollIntoView({ detailsRef.current.scrollIntoView({
behavior: 'smooth', behavior: 'smooth',
block: 'nearest', block: 'nearest',
}); });
}
} else { } else {
console.error(result); console.error(result);
setUIState('error'); setUIState('error');
@ -91,6 +104,23 @@ function TranslationBlock({
} }
}, [forceTranslate]); }, [forceTranslate]);
if (mini) {
if (!!translatedContent && detectedLang !== targetLangText) {
return (
<div class="status-translation-block-mini">
<Icon
icon="translate"
alt={`Auto-translated from ${sourceLangText}`}
/>
<output lang={targetLang} dir="auto">
{translatedContent}
</output>
</div>
);
}
return null;
}
return ( return (
<div <div
class="status-translation-block" class="status-translation-block"