Adds time stickers to MediaEditor

This commit is contained in:
Josh Perez 2023-03-01 14:00:50 -05:00 committed by GitHub
parent 4549291b7b
commit 4d357f6f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 828 additions and 48 deletions

View File

@ -45,4 +45,5 @@
hasCustomTitleBar: () => false,
},
};
window.getPreferredSystemLocales = () => ['en'];
</script>

View File

@ -11,6 +11,7 @@ import { ClassyProvider } from '../ts/components/PopperRootContext';
import { I18n } from '../sticker-creator/util/i18n';
import { StorybookThemeContext } from './StorybookThemeContext';
import { ThemeType } from '../ts/types/Util';
import { setupI18n } from '../ts/util/setupI18n';
export const globalTypes = {
mode: {
@ -37,6 +38,8 @@ export const globalTypes = {
},
};
window.i18n = setupI18n('en', messages);
const withModeAndThemeProvider = (Story, context) => {
const theme =
context.globals.theme === 'light' ? ThemeType.light : ThemeType.dark;

View File

@ -2579,6 +2579,18 @@
"message": "Recently used stickers will appear here.",
"description": "Shown in the sticker picker when there are no recent stickers to show."
},
"icu:stickers__StickerPicker__recent": {
"messageformat": "Recents",
"description": "Title for all of the recent stickers"
},
"icu:stickers__StickerPicker__featured": {
"messageformat": "Featured",
"description": "Title for featured stickers"
},
"icu:stickers__StickerPicker__analog-time": {
"messageformat": "Analog time",
"description": "aria-label for the analog time sticker"
},
"stickers--StickerPreview--Title": {
"message": "Sticker Pack",
"description": "The title that appears in the sticker pack preview modal."
@ -5619,6 +5631,10 @@
"message": "There was an error when saving your settings. Please try again.",
"description": "Shown if there is an error when saving your preferred reaction settings. Should be very rare to see this message."
},
"icu:MediaEditor__clock-more-styles": {
"messageformat": "More styles",
"description": "Action button for switching up the clock styles"
},
"MediaEditor__control--draw": {
"message": "Draw",
"description": "Label for the draw button in the media editor"

Binary file not shown.

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="42" height="42" fill="none"><circle cx="21" cy="21" r="21" fill="#fff"/><circle cx="21" cy="21" r="21" fill="#fff"/><circle cx="21" cy="21" r="21" fill="#D1FFC1"/></svg>

After

Width:  |  Height:  |  Size: 216 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="200" fill="none"><rect width="14" height="200" fill="#FFBE20" rx="7"/></svg>

After

Width:  |  Height:  |  Size: 135 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="280" fill="none"><path fill="#FFBE20" d="M5 280c-2.761 0-5-1.99-5-4.444V4.444C0 1.99 2.239 0 5 0s5 1.99 5 4.444v271.112C10 278.01 7.761 280 5 280Z"/></svg>

After

Width:  |  Height:  |  Size: 214 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none"><path fill="#000" d="M600 300c0 165.685-134.315 300-300 300S0 465.685 0 300 134.315 0 300 0s300 134.315 300 300Z" opacity=".8"/><path fill="red" d="M310.002 295.313c0 5.523-4.477 10-10 10s-10-4.477-10-10 4.477-10 10-10 10 4.477 10 10Z"/><path fill="#FFBE20" d="M320 300c0 11.046-8.954 20-20 20s-20-8.954-20-20 8.954-20 20-20 20 8.954 20 20Z"/><path fill="#fff" d="M283.959 83.409v-54.47h-5.486c-1.175 5.565-5.407 8.23-9.953 8.23h-1.332v6.034h2.116c2.586 0 6.426-.94 8.15-3.057V83.41h6.505ZM326.026 83.409v-5.957h-23.277c5.878-5.721 10.502-10.424 14.421-15.361 5.565-6.662 8.229-13.167 8.229-19.593 0-9.64-5.407-14.735-14.969-14.735-9.718 0-15.596 5.487-15.596 14.656 0 1.568.156 3.057.47 4.468l7.054.862a24.213 24.213 0 0 1-.549-5.173c0-5.8 3.057-9.091 8.308-9.091 5.172 0 8.072 3.213 8.072 9.326 0 5.565-2.038 10.894-6.818 16.85-4.938 5.957-10.738 11.756-17.086 18.183v5.565h31.741ZM432.311 117.635V62.723h-5.531c-1.185 5.61-5.452 8.296-10.034 8.296h-1.344v6.084h2.134c2.607 0 6.478-.949 8.217-3.082v43.614h6.558ZM528.691 206.367v-6.004h-23.466c5.925-5.768 10.587-10.509 14.537-15.486 5.61-6.716 8.297-13.274 8.297-19.753 0-9.718-5.452-14.854-15.091-14.854-9.798 0-15.724 5.531-15.724 14.775 0 1.58.158 3.081.475 4.504l7.11.869a24.43 24.43 0 0 1-.553-5.215c0-5.847 3.082-9.165 8.375-9.165 5.215 0 8.139 3.239 8.139 9.402 0 5.61-2.055 10.982-6.874 16.987-4.978 6.005-10.825 11.852-17.225 18.331v5.609h32ZM525.901 427.653h-7.506v-36.187h-7.743l-20.148 36.74v4.74h21.491v13.432h6.4v-13.432h7.506v-5.293Zm-13.906 0h-15.249c1.422-1.739 2.528-3.714 3.95-6.242l7.98-14.933c1.501-2.924 2.45-5.057 3.319-7.822h.158c-.158 2.923-.158 4.977-.158 8.612v20.385ZM416.189 531.196c10.35 0 16.355-7.347 16.355-19.515 0-11.931-4.978-18.647-13.827-18.647-5.215 0-9.086 2.292-11.298 6.637l1.501-18.646h21.254v-5.926h-26.706l-2.291 31.051 6.479.711c.948-5.056 4.187-8.217 9.007-8.217 5.847 0 8.849 4.741 8.849 13.432 0 8.77-3.239 13.432-9.165 13.432-5.294 0-8.296-4.109-8.928-10.114l-7.032.791c.711 9.086 6.558 15.011 15.802 15.011ZM299.388 525.508c-5.531 0-9.561 2.607-11.773 7.032.158-14.064 3.556-20.938 10.351-20.938 4.898 0 7.427 3.082 8.059 9.481l7.032-1.185c-.949-9.244-6.163-13.826-15.012-13.826-11.062 0-17.225 8.138-17.225 28.838 0 20.464 5.531 28.444 16.988 28.444 9.876 0 15.881-7.19 15.881-19.278 0-11.694-5.294-18.568-14.301-18.568Zm-1.58 32.236c-5.926 0-9.64-4.74-10.114-17.066 1.501-6.005 4.741-9.639 9.956-9.639 5.688 0 9.007 4.819 9.007 13.116 0 9.007-3.319 13.589-8.849 13.589ZM174.488 527.7v-4.346c.079-15.96 5.135-31.999 15.802-45.589v-4.978h-30.577v6.163h23.466c-10.192 14.617-15.723 29.866-15.881 46.458v2.292h7.19ZM96.071 417.677c6.088-2.134 8.935-6.088 8.935-12.571 0-9.172-6.01-14.153-15.576-14.153-9.409 0-15.655 5.218-15.655 14.232 0 7.274 3.162 10.99 8.934 13.52-6.72 2.135-10.2 6.721-10.2 14.074 0 9.725 6.326 15.497 17.158 15.497 10.358 0 16.841-5.377 16.841-15.813 0-8.144-3.637-12.097-10.437-14.786Zm-6.72-21.664c5.613 0 8.934 2.926 8.934 9.093 0 6.246-1.897 8.618-7.59 10.753l-2.056-.712c-5.218-1.818-8.064-4.032-8.064-10.12 0-6.088 3.32-9.014 8.776-9.014Zm.316 46.886c-6.483 0-10.279-3.795-10.279-10.278 0-6.721 2.61-9.884 8.777-12.018l2.767.949c5.693 1.976 8.697 4.664 8.697 11.148 0 7.195-3.637 10.199-9.962 10.199ZM71.09 211.055v-54.912h-5.53c-1.186 5.609-5.453 8.296-10.035 8.296h-1.343v6.084h2.133c2.607 0 6.479-.949 8.217-3.082v43.614h6.558ZM100.778 212.24c11.456 0 17.935-7.269 17.935-28.681s-6.479-28.601-17.935-28.601c-11.457 0-17.935 7.189-17.935 28.601s6.478 28.681 17.935 28.681Zm0-5.61c-7.19 0-10.982-5.372-10.982-23.071 0-17.698 3.792-22.992 10.982-22.992 7.19 0 10.982 5.294 10.982 22.992 0 17.699-3.792 23.071-10.982 23.071ZM159.346 119.947V65.034h-5.531c-1.185 5.61-5.452 8.296-10.034 8.296h-1.343v6.084h2.133c2.607 0 6.479-.948 8.217-3.081v43.614h6.558ZM186.506 119.947V65.034h-5.531c-1.185 5.61-5.452 8.296-10.035 8.296h-1.343v6.084h2.134c2.607 0 6.478-.948 8.217-3.081v43.614h6.558ZM544.269 327.821c10.034 0 16.039-5.61 16.039-15.96 0-7.822-3.555-12.958-10.113-14.064 5.61-1.422 9.007-6.558 9.007-13.274 0-9.244-5.531-13.985-14.933-13.985-10.429 0-15.881 6.479-15.96 17.067l6.795 1.027c.079-8.612 2.844-12.405 9.007-12.405 5.057 0 8.059 2.844 8.059 8.77 0 6.558-3.081 10.272-8.849 10.272h-2.528v5.688h2.765c6.321 0 9.718 3.793 9.718 10.983 0 6.874-3.16 10.192-9.007 10.192-5.926 0-9.481-4.108-9.56-12.326l-6.953.791c.158 11.377 6.163 17.224 16.513 17.224ZM50.575 270.538c-10.034 0-15.96 7.032-15.96 18.015 0 11.535 5.689 17.777 14.696 17.777 5.136 0 9.402-2.37 11.615-6.953 0 18.014-3.24 22.913-10.193 22.913-5.214 0-7.98-3.555-8.612-9.876l-6.953 1.343c1.107 9.165 6.4 14.064 15.407 14.064 10.825 0 16.671-6.716 16.671-29.313 0-21.965-5.925-27.97-16.67-27.97Zm.158 30.261c-5.688 0-9.165-4.503-9.165-12.404s3.319-12.326 9.007-12.326c6.637 0 9.56 3.635 10.193 14.143-1.107 6.716-4.978 10.587-10.035 10.587Z"/></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="210" fill="none"><path fill="#fff" fill-rule="evenodd" d="M5 0 0 210h40L35 0H5Zm15 176.001c8.837 0 16-7.164 16-16 0-8.837-7.163-16-16-16s-16 7.163-16 16c0 8.836 7.163 16 16 16Z" clip-rule="evenodd" opacity=".98"/></svg>

After

Width:  |  Height:  |  Size: 278 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="308" fill="none"><path fill="#fff" fill-rule="evenodd" d="M5.76 0 0 308h40L34.24 0H5.76ZM20 273.944c8.837 0 16-7.162 16-15.996 0-8.835-7.163-15.997-16-15.997s-16 7.162-16 15.997c0 8.834 7.163 15.996 16 15.996Z" clip-rule="evenodd" opacity=".98"/></svg>

After

Width:  |  Height:  |  Size: 311 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none"><circle cx="300" cy="300" r="300" fill="#000" opacity=".5"/><path fill="#fff" d="M291 42.001h20v64h-20zM291 493.999h20v64h-20zM421.334 71.567l17.32 10-32 55.426-17.32-10zM195.336 463.01l17.32 10-32 55.426-17.32-10zM519.434 162.34l10 17.32-55.426 32-10-17.32zM127.99 388.34l10 17.32-55.426 32-10-17.32zM529.439 420.34l-10 17.32-55.426-32 10-17.32zM137.988 194.341l-10 17.32-55.426-32 10-17.32zM438.656 518.436l-17.32 10-32-55.426 17.32-10zM212.66 126.99l-17.32 10-32-55.426 17.32-10zM42.004 309.998v-20h64v20zM494.531 309.998v-20h64v20z"/></svg>

After

Width:  |  Height:  |  Size: 621 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="138" fill="none"><path fill="#fff" d="M27.648 18.84A2 2 0 0 1 28 19.975V138H0V19.974a2 2 0 0 1 .352-1.133l12-17.445a2 2 0 0 1 3.296 0l12 17.445Z"/></svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="267" fill="none"><path fill="#fff" d="M27.665 19.52A2 2 0 0 1 28 20.63V267H0V20.63a2 2 0 0 1 .335-1.11l12-18.02a2 2 0 0 1 3.33 0l12 18.02Z"/></svg>

After

Width:  |  Height:  |  Size: 206 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none"><circle cx="300" cy="300" r="300" fill="#000" opacity=".8"/><rect width="28" height="80" x="286" y="488" fill="#fff" rx="2"/><rect width="28" height="80" x="286" y="488" fill="#fff" rx="2"/><rect width="28" height="80" x="286" y="488" fill="#D1FFC1" rx="2"/><rect width="28" height="80" x="32" y="314" fill="#fff" rx="2" transform="rotate(-90 32 314)"/><rect width="28" height="80" x="32" y="314" fill="#fff" rx="2" transform="rotate(-90 32 314)"/><rect width="28" height="80" x="32" y="314" fill="#D1FFC1" rx="2" transform="rotate(-90 32 314)"/><rect width="28" height="80" x="488" y="314" fill="#fff" rx="2" transform="rotate(-90 488 314)"/><rect width="28" height="80" x="488" y="314" fill="#fff" rx="2" transform="rotate(-90 488 314)"/><rect width="28" height="80" x="488" y="314" fill="#D1FFC1" rx="2" transform="rotate(-90 488 314)"/><circle cx="424.049" cy="86.941" r="23.875" fill="#fff" transform="rotate(30 424.049 86.94)"/><circle cx="424.049" cy="86.941" r="23.875" fill="#fff" transform="rotate(30 424.049 86.94)"/><circle cx="424.049" cy="86.941" r="23.875" fill="#D1FFC1" transform="rotate(30 424.049 86.94)"/><circle cx="178.028" cy="513.062" r="23.875" fill="#fff" transform="rotate(30 178.028 513.062)"/><circle cx="178.028" cy="513.062" r="23.875" fill="#fff" transform="rotate(30 178.028 513.062)"/><circle cx="178.028" cy="513.062" r="23.875" fill="#D1FFC1" transform="rotate(30 178.028 513.062)"/><circle cx="514.099" cy="176.989" r="23.875" fill="#fff" transform="rotate(60 514.099 176.989)"/><circle cx="514.099" cy="176.989" r="23.875" fill="#fff" transform="rotate(60 514.099 176.989)"/><circle cx="514.099" cy="176.989" r="23.875" fill="#D1FFC1" transform="rotate(60 514.099 176.989)"/><circle cx="87.978" cy="423.011" r="23.875" fill="#fff" transform="rotate(60 87.978 423.011)"/><circle cx="87.978" cy="423.011" r="23.875" fill="#fff" transform="rotate(60 87.978 423.011)"/><circle cx="87.978" cy="423.011" r="23.875" fill="#D1FFC1" transform="rotate(60 87.978 423.011)"/><circle cx="514.096" cy="423.01" r="23.875" fill="#fff" transform="rotate(120 514.096 423.01)"/><circle cx="514.096" cy="423.01" r="23.875" fill="#fff" transform="rotate(120 514.096 423.01)"/><circle cx="514.096" cy="423.01" r="23.875" fill="#D1FFC1" transform="rotate(120 514.096 423.01)"/><circle cx="87.976" cy="176.989" r="23.875" fill="#fff" transform="rotate(120 87.976 176.989)"/><circle cx="87.976" cy="176.989" r="23.875" fill="#fff" transform="rotate(120 87.976 176.989)"/><circle cx="87.976" cy="176.989" r="23.875" fill="#D1FFC1" transform="rotate(120 87.976 176.989)"/><circle cx="424.049" cy="513.06" r="23.875" fill="#fff" transform="rotate(150 424.049 513.06)"/><circle cx="424.049" cy="513.06" r="23.875" fill="#fff" transform="rotate(150 424.049 513.06)"/><circle cx="424.049" cy="513.06" r="23.875" fill="#D1FFC1" transform="rotate(150 424.049 513.06)"/><circle cx="178.028" cy="86.939" r="23.875" fill="#fff" transform="rotate(150 178.028 86.939)"/><circle cx="178.028" cy="86.939" r="23.875" fill="#fff" transform="rotate(150 178.028 86.939)"/><circle cx="178.028" cy="86.939" r="23.875" fill="#D1FFC1" transform="rotate(150 178.028 86.939)"/><path fill="#fff" d="M301.829 115.866c-.703 1.588-2.955 1.588-3.658 0l-32.435-73.307a2 2 0 0 1 1.829-2.809h64.87a2 2 0 0 1 1.829 2.81l-32.435 73.306Z"/><path fill="#fff" d="M301.829 115.866c-.703 1.588-2.955 1.588-3.658 0l-32.435-73.307a2 2 0 0 1 1.829-2.809h64.87a2 2 0 0 1 1.829 2.81l-32.435 73.306Z"/><path fill="#D1FFC1" d="M301.829 115.866c-.703 1.588-2.955 1.588-3.658 0l-32.435-73.307a2 2 0 0 1 1.829-2.809h64.87a2 2 0 0 1 1.829 2.81l-32.435 73.306Z"/></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="149" fill="none"><path fill="#fff" d="M10 149c-5.523 0-10-4.477-10-10V10C0 4.477 4.477 0 10 0s10 4.477 10 10v129c0 5.523-4.477 10-10 10Z"/></svg>

After

Width:  |  Height:  |  Size: 204 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="229" fill="none"><path fill="#fff" d="M10 229c-5.523 0-10-4.477-10-10V10C0 4.477 4.477 0 10 0s10 4.477 10 10v209c0 5.523-4.477 10-10 10Z"/></svg>

After

Width:  |  Height:  |  Size: 204 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none"><circle cx="300" cy="300" r="300" fill="#fff" opacity=".3"/><rect width="20" height="40" x="519.434" y="162.34" fill="#fff" rx="10" transform="rotate(60 519.434 162.34)"/><rect width="20" height="40" x="107.209" y="400.339" fill="#fff" rx="10" transform="rotate(60 107.209 400.339)"/><rect width="20" height="40" x="421.344" y="71.566" fill="#fff" rx="10" transform="rotate(30 421.344 71.566)"/><rect width="20" height="40" x="183.344" y="483.794" fill="#fff" rx="10" transform="rotate(30 183.344 483.794)"/><rect width="20" height="40" x="163.34" y="81.565" fill="#fff" rx="10" transform="rotate(-30 163.34 81.565)"/><rect width="20" height="40" x="401.34" y="493.794" fill="#fff" rx="10" transform="rotate(-30 401.34 493.794)"/><rect width="20" height="40" x="72.566" y="179.66" fill="#fff" rx="10" transform="rotate(-60 72.566 179.66)"/><rect width="20" height="40" x="484.789" y="417.66" fill="#fff" rx="10" transform="rotate(-60 484.789 417.66)"/><circle cx="300.002" cy="300" r="10" fill="red"/><circle cx="300" cy="300" r="22" fill="#fff"/><path fill="#fff" d="M285.472 80.644v-54.47h-7.288c-1.489 5.486-5.486 8.151-10.424 8.151h-1.332v8.15h1.959c2.9 0 6.505-.861 8.229-2.82v40.989h8.856ZM328.605 80.644V73.12h-22.336c5.956-5.408 10.345-9.64 13.95-14.107 5.33-6.27 7.916-12.383 7.916-18.732 0-10.267-5.799-15.282-16.223-15.282s-16.772 5.72-16.772 15.126c0 1.489.157 2.9.47 4.31l9.248 1.019a28.518 28.518 0 0 1-.47-5.016c0-5.172 2.586-8.15 7.132-8.15 4.467 0 6.975 2.82 6.975 8.307 0 5.408-1.881 10.267-6.426 15.753-4.468 5.33-10.111 10.737-17.243 17.32v6.976h33.779ZM300.276 536.569c-5.452 0-9.244 2.291-11.456 6.241.158-12.878 3.16-18.409 9.086-18.409 4.266 0 6.479 2.765 7.111 8.454l9.086-1.422c-.948-9.402-6.4-14.064-16.039-14.064-11.615 0-18.252 8.138-18.252 28.76 0 20.306 6.005 28.523 18.094 28.523 10.429 0 16.829-7.427 16.829-19.674 0-11.93-5.61-18.409-14.459-18.409Zm-2.528 30.972c-5.294 0-8.533-4.425-8.928-16.039 1.501-4.978 4.424-7.901 8.849-7.901 4.977 0 7.901 4.108 7.901 11.693 0 8.217-2.924 12.247-7.822 12.247ZM553.663 329.743c10.746 0 17.146-5.846 17.146-16.197 0-7.585-3.556-12.562-10.351-13.668 5.847-1.423 9.402-6.558 9.402-13.116 0-9.402-5.925-14.301-16.118-14.301-10.982 0-17.066 6.321-17.145 17.224l9.086 1.264c.079-7.98 2.529-11.298 7.822-11.298 4.425 0 6.953 2.449 6.953 7.901 0 5.847-2.765 9.244-7.98 9.244h-2.449v6.953h2.607c5.768 0 8.77 3.239 8.77 9.718 0 6.005-2.765 9.007-7.743 9.007-5.135 0-8.217-3.713-8.217-11.298l-9.323 1.027c.158 11.615 6.558 17.54 17.54 17.54ZM45.991 272.461c-10.745 0-16.987 7.19-16.987 18.33 0 11.378 5.768 17.778 15.012 17.778 4.82 0 8.928-1.976 11.14-6.005-.158 15.881-3.002 20.069-8.928 20.069-4.503 0-7.032-3.24-7.585-8.85l-9.007 1.581c.948 9.244 6.637 14.379 16.276 14.379 11.694 0 17.857-6.715 17.857-29.154 0-22.044-6.163-28.128-17.778-28.128Zm.158 29.155c-4.977 0-8.059-4.109-8.059-11.062 0-7.269 2.923-11.14 7.98-11.14 5.926 0 8.533 3.318 9.007 13.748-1.264 5.451-4.661 8.454-8.928 8.454Z"/></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -54,3 +54,8 @@
font-family: 'EB Garamond';
src: url('../fonts/stories/EBGaramond-Regular.ttf');
}
@font-face {
font-family: 'Hatsuishi';
src: url('../fonts/stories/Hatsuishi-Regular.woff2');
}

View File

@ -3,8 +3,7 @@
// Fonts
@mixin font-family {
font-family: $inter;
@mixin localized-fonts {
/* Japanese */
&:lang(ja) {
font-family: 'SF Pro JP', 'Hiragino Kaku Gothic Pro', 'ヒラギノ角ゴ Pro W3',
@ -18,6 +17,16 @@
}
}
@mixin font-family {
font-family: $inter;
@include localized-fonts;
}
@mixin time-fonts {
font-family: Hatsuishi, $inter;
@include localized-fonts;
}
@mixin font-title-1 {
@include font-family;
font-weight: 600;

View File

@ -5433,6 +5433,10 @@ button.module-image__border-overlay:focus {
}
}
.module-sticker-picker__recents--title {
color: $color-gray-05;
}
.module-sticker-picker__header__button {
width: 28px;
height: 28px;
@ -5616,15 +5620,18 @@ button.module-image__border-overlay:focus {
.module-sticker-picker__body {
position: relative;
&__grid {
display: grid;
grid-gap: 8px;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 68px;
}
&__content {
width: 332px;
height: 356px;
padding: 8px 13px 16px 13px;
overflow-y: auto;
display: grid;
grid-gap: 8px;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 68px;
&--under-text {
height: 320px;
@ -5721,6 +5728,45 @@ button.module-image__border-overlay:focus {
}
}
.module-sticker-picker__time--digital {
@include time-fonts;
color: $color-white;
font-size: 28px;
line-height: 0px;
}
.module-sticker-picker__time--analog {
background: url(../images/analog-time/Arabic.svg) center no-repeat;
background-size: contain;
height: 64px;
position: relative;
width: 64px;
}
.module-sticker-picker__time--analog__hour {
background: url(../images/analog-time/Arabic-hour.svg) center no-repeat;
height: 14px;
left: 50%;
margin-left: -1px;
margin-top: -14px;
position: absolute;
top: 50%;
transform-origin: 50% 100%;
width: 2px;
}
.module-sticker-picker__time--analog__minute {
background: url(../images/analog-time/Arabic-minute.svg) center no-repeat;
height: 22px;
left: 50%;
margin-left: -1px;
margin-top: -22px;
position: absolute;
top: 50%;
transform-origin: 50% 100%;
width: 2px;
}
// Module: Sticker button (launches the sticker picker)
.sticker-button-wrapper {

View File

@ -27,9 +27,11 @@ import { useFabricHistory } from '../mediaEditor/useFabricHistory';
import { usePortal } from '../hooks/usePortal';
import { useUniqueId } from '../hooks/useUniqueId';
import { MediaEditorFabricPencilBrush } from '../mediaEditor/MediaEditorFabricPencilBrush';
import { MediaEditorFabricAnalogTimeSticker } from '../mediaEditor/MediaEditorFabricAnalogTimeSticker';
import { MediaEditorFabricCropRect } from '../mediaEditor/MediaEditorFabricCropRect';
import { MediaEditorFabricDigitalTimeSticker } from '../mediaEditor/MediaEditorFabricDigitalTimeSticker';
import { MediaEditorFabricIText } from '../mediaEditor/MediaEditorFabricIText';
import { MediaEditorFabricPencilBrush } from '../mediaEditor/MediaEditorFabricPencilBrush';
import { MediaEditorFabricSticker } from '../mediaEditor/MediaEditorFabricSticker';
import { fabricEffectListener } from '../mediaEditor/fabricEffectListener';
import { getRGBA, getHSL } from '../mediaEditor/util/color';
@ -1062,6 +1064,53 @@ export function MediaEditor({
fabricCanvas.setActiveObject(sticker);
setEditMode(undefined);
}}
onPickTimeSticker={(style: 'analog' | 'digital') => {
if (!fabricCanvas) {
return;
}
if (style === 'digital') {
const sticker = new MediaEditorFabricDigitalTimeSticker(
Date.now()
);
sticker.setPositionByOrigin(
new fabric.Point(
imageState.width / 2,
imageState.height / 2
),
'center',
'center'
);
sticker.setCoords();
fabricCanvas.add(sticker);
fabricCanvas.setActiveObject(sticker);
}
if (style === 'analog') {
const sticker = new MediaEditorFabricAnalogTimeSticker();
const STICKER_SIZE_RELATIVE_TO_CANVAS = 4;
const size =
Math.min(imageState.width, imageState.height) /
STICKER_SIZE_RELATIVE_TO_CANVAS;
sticker.scaleToHeight(size);
sticker.setPositionByOrigin(
new fabric.Point(
imageState.width / 2,
imageState.height / 2
),
'center',
'center'
);
sticker.setCoords();
fabricCanvas.add(sticker);
fabricCanvas.setActiveObject(sticker);
}
setEditMode(undefined);
}}
receivedPacks={[]}
recentStickers={recentStickers}
showPickerHint={false}
@ -1247,7 +1296,7 @@ function getNewImageStateFromCrop(
function cloneFabricCanvas(original: fabric.Canvas): Promise<fabric.Canvas> {
return new Promise(resolve => {
original.clone(resolve);
original.clone(resolve, ['data']);
});
}

View File

@ -35,6 +35,7 @@ export type OwnProps = {
stickerId: number,
url: string
) => unknown;
readonly onPickTimeSticker?: (style: 'analog' | 'digital') => unknown;
readonly showIntroduction?: boolean;
readonly clearShowIntroduction: () => unknown;
readonly showPickerHint: boolean;
@ -51,6 +52,7 @@ export const StickerButton = React.memo(function StickerButtonInner({
clearInstalledStickerPack,
onClickAddPack,
onPickSticker,
onPickTimeSticker,
recentStickers,
onOpenStateChanged,
receivedPacks,
@ -111,6 +113,14 @@ export const StickerButton = React.memo(function StickerButtonInner({
[setOpen, onPickSticker]
);
const handlePickTimeSticker = React.useCallback(
(style: 'analog' | 'digital') => {
setOpen(false);
onPickTimeSticker?.(style);
},
[setOpen, onPickTimeSticker]
);
const handleClose = React.useCallback(() => {
setOpen(false);
}, [setOpen]);
@ -355,6 +365,9 @@ export const StickerButton = React.memo(function StickerButtonInner({
onClickAddPack ? handleClickAddPack : undefined
}
onPickSticker={handlePickSticker}
onPickTimeSticker={
onPickTimeSticker ? handlePickTimeSticker : undefined
}
recentStickers={recentStickers}
showPickerHint={showPickerHint}
/>

View File

@ -8,6 +8,7 @@ import FocusTrap from 'focus-trap-react';
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
import type { StickerPackType, StickerType } from '../../state/ducks/stickers';
import type { LocalizerType } from '../../types/Util';
import { getAnalogTime } from '../../util/getAnalogTime';
export type OwnProps = {
readonly i18n: LocalizerType;
@ -18,6 +19,7 @@ export type OwnProps = {
stickerId: number,
url: string
) => unknown;
readonly onPickTimeSticker?: (style: 'analog' | 'digital') => unknown;
readonly packs: ReadonlyArray<StickerPackType>;
readonly recentStickers: ReadonlyArray<StickerType>;
readonly showPickerHint?: boolean;
@ -71,6 +73,7 @@ export const StickerPicker = React.memo(
onClose,
onClickAddPack,
onPickSticker,
onPickTimeSticker,
showPickerHint,
style,
}: Props,
@ -131,7 +134,10 @@ export const StickerPicker = React.memo(
// Focus popup on after initial render, restore focus on teardown
const [focusRef] = useRestoreFocus();
const isEmpty = stickers.length === 0;
const hasPacks = packs.length > 0;
const isRecents = hasPacks && currentTab === 'recents';
const hasTimeStickers = isRecents && onPickTimeSticker;
const isEmpty = stickers.length === 0 && !hasTimeStickers;
const addPackRef = isEmpty ? focusRef : undefined;
const downloadError =
selectedPack &&
@ -142,14 +148,13 @@ export const StickerPicker = React.memo(
? selectedPack.stickerCount - stickers.length
: 0;
const hasPacks = packs.length > 0;
const isRecents = hasPacks && currentTab === 'recents';
const showPendingText = pendingCount > 0;
const showDownlaodErrorText = downloadError;
const showDownloadErrorText = downloadError;
const showEmptyText = !downloadError && isEmpty;
const showText =
showPendingText || showDownlaodErrorText || showEmptyText;
showPendingText || showDownloadErrorText || showEmptyText;
const showLongText = showPickerHint;
const analogTime = getAnalogTime();
return (
<FocusTrap
@ -303,46 +308,97 @@ export const StickerPicker = React.memo(
</div>
) : null}
{!isEmpty ? (
<div
className={classNames(
'module-sticker-picker__body__content',
{
<div className="module-sticker-picker__body__content">
{isRecents && onPickTimeSticker && (
<div className="module-sticker-picker__recents">
<strong className="module-sticker-picker__recents__title">
{i18n('icu:stickers__StickerPicker__featured')}
</strong>
<div className="module-sticker-picker__body__grid">
<button
type="button"
className="module-sticker-picker__body__cell module-sticker-picker__time--digital"
onClick={() => onPickTimeSticker('digital')}
>
{new Intl.DateTimeFormat(
window.getPreferredSystemLocales(),
{
hour: 'numeric',
minute: 'numeric',
}
)
.formatToParts(Date.now())
.filter(x => x.type !== 'dayPeriod')
.reduce((acc, { value }) => `${acc}${value}`, '')}
</button>
<button
aria-label={i18n(
'icu:stickers__StickerPicker__analog-time'
)}
className="module-sticker-picker__body__cell module-sticker-picker__time--analog"
onClick={() => onPickTimeSticker('analog')}
type="button"
>
<span
className="module-sticker-picker__time--analog__hour"
style={{
transform: `rotate(${analogTime.hour}deg)`,
}}
/>
<span
className="module-sticker-picker__time--analog__minute"
style={{
transform: `rotate(${analogTime.minute}deg)`,
}}
/>
</button>
</div>
{stickers.length > 0 && (
<strong className="module-sticker-picker__recents__title">
{i18n('icu:stickers__StickerPicker__recent')}
</strong>
)}
</div>
)}
<div
className={classNames('module-sticker-picker__body__grid', {
'module-sticker-picker__body__content--under-text':
showText,
'module-sticker-picker__body__content--under-long-text':
showLongText,
}
)}
>
{stickers.map(({ packId, id, url }, index: number) => {
const maybeFocusRef = index === 0 ? focusRef : undefined;
})}
>
{stickers.map(({ packId, id, url }, index: number) => {
const maybeFocusRef = index === 0 ? focusRef : undefined;
return (
<button
type="button"
ref={maybeFocusRef}
key={`${packId}-${id}`}
className="module-sticker-picker__body__cell"
onClick={() => onPickSticker(packId, id, url)}
>
<img
className="module-sticker-picker__body__cell__image"
src={url}
alt={packTitle}
return (
<button
type="button"
ref={maybeFocusRef}
key={`${packId}-${id}`}
className="module-sticker-picker__body__cell"
onClick={() => onPickSticker(packId, id, url)}
>
<img
className="module-sticker-picker__body__cell__image"
src={url}
alt={packTitle}
/>
</button>
);
})}
{Array(pendingCount)
.fill(0)
.map((_, i) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={i}
className="module-sticker-picker__body__cell__placeholder"
role="presentation"
/>
</button>
);
})}
{Array(pendingCount)
.fill(0)
.map((_, i) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={i}
className="module-sticker-picker__body__cell__placeholder"
role="presentation"
/>
))}
))}
</div>
</div>
) : null}
</div>

View File

@ -0,0 +1,289 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { get } from 'lodash';
import { fabric } from 'fabric';
import { customFabricObjectControls } from './util/customFabricObjectControls';
import { getAnalogTime } from '../util/getAnalogTime';
import { strictAssert } from '../util/assert';
import { moreStyles } from './util/moreStyles';
export enum AnalogClockStickerStyle {
Arabic = 'Arabic',
Baton = 'Baton',
Explorer = 'Explorer',
Dive = 'Dive',
}
const HOUR_LENGTH = 0.44;
const MIN_LENGTH = 0.69;
type ClockAsset = {
dial: HTMLImageElement;
hour: HTMLImageElement;
minute: HTMLImageElement;
};
const ASSETS = new Map<AnalogClockStickerStyle, ClockAsset>();
function hydrateAssets(): void {
if (ASSETS.size) {
return;
}
const path = 'images/analog-time';
const clocks = [
AnalogClockStickerStyle.Arabic,
AnalogClockStickerStyle.Baton,
AnalogClockStickerStyle.Explorer,
AnalogClockStickerStyle.Dive,
];
clocks.forEach(name => {
const dial = new Image();
const hour = new Image();
const minute = new Image();
dial.src = `${path}/${name}.svg`;
hour.src = `${path}/${name}-hour.svg`;
minute.src = `${path}/${name}-minute.svg`;
ASSETS.set(name, {
dial,
hour,
minute,
});
});
}
function degToRad(deg: number): number {
return deg * (Math.PI / 180);
}
type HandDimensions = {
rad: number;
length: number;
width: number;
};
function drawHands(
ctx: CanvasRenderingContext2D,
clock: ClockAsset,
hourDimensions: HandDimensions,
minuteDimensions: HandDimensions,
offset = 0
): void {
ctx.rotate(hourDimensions.rad);
ctx.drawImage(
clock.hour,
0 - hourDimensions.width / 2,
0 - hourDimensions.length + offset,
hourDimensions.width,
hourDimensions.length
);
ctx.rotate(-hourDimensions.rad);
ctx.rotate(minuteDimensions.rad);
ctx.drawImage(
clock.minute,
0 - minuteDimensions.width / 2,
0 - minuteDimensions.length + offset,
minuteDimensions.width,
minuteDimensions.length
);
ctx.rotate(-minuteDimensions.rad);
}
export class MediaEditorFabricAnalogTimeSticker extends fabric.Image {
static getNextStyle(
style?: AnalogClockStickerStyle
): AnalogClockStickerStyle {
if (style === AnalogClockStickerStyle.Dive) {
return AnalogClockStickerStyle.Arabic;
}
if (style === AnalogClockStickerStyle.Explorer) {
return AnalogClockStickerStyle.Dive;
}
if (style === AnalogClockStickerStyle.Baton) {
return AnalogClockStickerStyle.Explorer;
}
return AnalogClockStickerStyle.Baton;
}
constructor(options: fabric.IImageOptions = {}) {
if (!ASSETS.size) {
hydrateAssets();
}
let style: AnalogClockStickerStyle = AnalogClockStickerStyle.Arabic;
ASSETS.forEach((asset, styleName) => {
if (get(options, 'src') === asset.dial.src) {
style = styleName;
}
});
const clock = ASSETS.get(style);
strictAssert(clock, 'expected clock not found');
super(clock.dial, {
...options,
data: { stickerStyle: style, timeDeg: getAnalogTime() },
});
this.on('modified', () => this.canvas?.bringToFront(this));
}
override render(ctx: CanvasRenderingContext2D): void {
super.render(ctx);
const { stickerStyle, timeDeg } = this.data;
const { x, y } = this.getCenterPoint();
const radius = this.getScaledHeight() / 2;
const flip = this.flipX || this.flipY ? 180 : 0;
const rawAngle = (this.angle ?? 0) - flip;
const timeRad = {
hour: degToRad(timeDeg.hour + rawAngle),
minute: degToRad(timeDeg.minute + rawAngle),
};
ctx.save();
ctx.translate(x, y);
const clock = ASSETS.get(stickerStyle);
strictAssert(clock, 'expected clock not found');
if (stickerStyle === AnalogClockStickerStyle.Arabic) {
const offset = radius * 0.106;
drawHands(
ctx,
clock,
{
rad: timeRad.hour,
length: radius * HOUR_LENGTH,
width: radius * 0.049,
},
{
rad: timeRad.minute,
length: radius * MIN_LENGTH,
width: radius * 0.036,
},
offset
);
}
if (stickerStyle === AnalogClockStickerStyle.Baton) {
const offset = radius * 0.106;
drawHands(
ctx,
clock,
{
rad: timeRad.hour,
length: radius * HOUR_LENGTH,
width: radius * 0.09,
},
{
rad: timeRad.minute,
length: radius * MIN_LENGTH,
width: radius * 0.09,
},
offset
);
}
if (stickerStyle === AnalogClockStickerStyle.Explorer) {
drawHands(
ctx,
clock,
{
rad: timeRad.hour,
length: radius * HOUR_LENGTH,
width: radius * 0.07,
},
{
rad: timeRad.minute,
length: radius * MIN_LENGTH,
width: radius * 0.07,
}
);
}
if (stickerStyle === AnalogClockStickerStyle.Dive) {
drawHands(
ctx,
clock,
{
rad: timeRad.hour,
length: radius * 0.47,
width: radius * 0.095,
},
{
rad: timeRad.minute,
length: radius * 0.89,
width: radius * 0.095,
}
);
// Circle
const circleSize = radius * 0.08;
ctx.fillStyle = '#d1ffc1';
ctx.strokeStyle = '#d1ffc1';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(0, 0, circleSize, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
static fromObject(
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
options: any,
callback: (_: MediaEditorFabricAnalogTimeSticker) => unknown
): void {
callback(new MediaEditorFabricAnalogTimeSticker(options));
}
}
const moreStylesControl = new fabric.Control({
...moreStyles,
mouseUpHandler: (_eventData, { target }) => {
const stickerStyle = MediaEditorFabricAnalogTimeSticker.getNextStyle(
target.data.stickerStyle
);
target.setOptions({
data: {
...target.data,
stickerStyle,
},
});
const clock = ASSETS.get(stickerStyle);
strictAssert(clock, 'expected clock not found');
const img = target as fabric.Image;
img.setElement(clock.dial);
target.setCoords();
target.canvas?.requestRenderAll();
return true;
},
});
MediaEditorFabricAnalogTimeSticker.prototype.type =
'MediaEditorFabricAnalogTimeSticker';
MediaEditorFabricAnalogTimeSticker.prototype.borderColor = '#ffffff';
MediaEditorFabricAnalogTimeSticker.prototype.controls = {
...customFabricObjectControls,
mb: moreStylesControl,
};

View File

@ -0,0 +1,214 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { fabric } from 'fabric';
import { customFabricObjectControls } from './util/customFabricObjectControls';
import { moreStyles } from './util/moreStyles';
export enum DigitalClockStickerStyle {
White = 'White',
Black = 'Black',
Light = 'Light',
Dark = 'Dark',
Orange = 'Orange',
}
function getTextStyle(style: DigitalClockStickerStyle): {
fill: string;
textBackgroundColor: string;
} {
if (style === DigitalClockStickerStyle.Black) {
return {
fill: '#000',
textBackgroundColor: '',
};
}
if (style === DigitalClockStickerStyle.Light) {
return {
fill: '#fff',
textBackgroundColor: 'rgba(255, 255, 255, 0.4)',
};
}
if (style === DigitalClockStickerStyle.Dark) {
return {
fill: '#fff',
textBackgroundColor: 'rgba(0, 0, 0, 0.4)',
};
}
if (style === DigitalClockStickerStyle.Orange) {
return {
fill: '#ff7629',
textBackgroundColor: 'rgba(0, 0, 0, 0.6)',
};
}
return {
fill: '#fff',
textBackgroundColor: '',
};
}
const TEXT_PROPS = {
editable: false,
fontWeight: '400',
left: 0,
lockScalingFlip: true,
originX: 'center',
originY: 'center',
textAlign: 'center',
top: 0,
};
export class MediaEditorFabricDigitalTimeSticker extends fabric.Group {
static getNextStyle(
style?: DigitalClockStickerStyle
): DigitalClockStickerStyle {
if (style === DigitalClockStickerStyle.White) {
return DigitalClockStickerStyle.Black;
}
if (style === DigitalClockStickerStyle.Black) {
return DigitalClockStickerStyle.Light;
}
if (style === DigitalClockStickerStyle.Light) {
return DigitalClockStickerStyle.Dark;
}
if (style === DigitalClockStickerStyle.Dark) {
return DigitalClockStickerStyle.Orange;
}
return DigitalClockStickerStyle.White;
}
constructor(
timestamp: number,
style: DigitalClockStickerStyle = DigitalClockStickerStyle.White,
options: fabric.IGroupOptions = {}
) {
const parts = new Intl.DateTimeFormat(window.getPreferredSystemLocales(), {
hour: 'numeric',
minute: 'numeric',
}).formatToParts(timestamp);
const { fill } = getTextStyle(style);
let dayPeriodText = '';
const timeText = parts.reduce((acc, part) => {
if (part.type === 'dayPeriod') {
dayPeriodText = part.value;
return acc;
}
return `${acc}${part.value}`;
}, '');
const timeTextNode = new fabric.IText(timeText.trim(), {
...TEXT_PROPS,
fill,
fontSize: 72,
fontFamily: '"Hatsuishi Large", Hatsuishi, Inter',
});
const dayPeriodTextNode = new fabric.IText(dayPeriodText, {
...TEXT_PROPS,
fill,
fontSize: 11,
fontFamily: 'Inter',
});
const dayPeriodBounds = dayPeriodTextNode.getBoundingRect();
const timeBounds = timeTextNode.getBoundingRect();
const totalWidth = dayPeriodBounds.width + timeBounds.width;
dayPeriodTextNode.set({
left: totalWidth / 2 + dayPeriodBounds.width / 2,
top: timeBounds.height / 2 - dayPeriodBounds.height * 1.66,
});
super([timeTextNode, dayPeriodTextNode], {
...options,
data: { stickerStyle: style, timestamp },
});
this.set('width', totalWidth * 2);
}
static override fromObject(
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
options: any,
callback: (_: MediaEditorFabricDigitalTimeSticker) => unknown
): MediaEditorFabricDigitalTimeSticker {
const timestamp = options?.data.timestamp ?? Date.now();
const result = new MediaEditorFabricDigitalTimeSticker(
timestamp,
options.data?.stickerStyle,
options
);
callback(result);
return result;
}
override render(ctx: CanvasRenderingContext2D): void {
const { textBackgroundColor } = getTextStyle(this.data.stickerStyle);
if (textBackgroundColor) {
const bounds = this.getBoundingRect();
const zoom = this.canvas?.getZoom() || 1;
const height = bounds.height / zoom;
const left = bounds.left / zoom;
const top = bounds.top / zoom;
const width = bounds.width / zoom;
ctx.save();
ctx.fillStyle = textBackgroundColor;
ctx.beginPath();
ctx.roundRect(left, top, width, height, 14);
ctx.closePath();
ctx.fill();
ctx.restore();
}
super.render(ctx);
}
}
const moreStylesControl = new fabric.Control({
...moreStyles,
mouseUpHandler: (_eventData, { target }) => {
const stickerStyle = MediaEditorFabricDigitalTimeSticker.getNextStyle(
target.data.stickerStyle
);
target.setOptions({
data: {
...target.data,
stickerStyle,
},
});
const styleAttrs = getTextStyle(stickerStyle);
const group = target as fabric.Group;
group.getObjects().forEach(textObject => {
textObject.set({ fill: styleAttrs.fill });
});
target.setCoords();
target.canvas?.requestRenderAll();
return true;
},
});
MediaEditorFabricDigitalTimeSticker.prototype.type =
'MediaEditorFabricDigitalTimeSticker';
MediaEditorFabricDigitalTimeSticker.prototype.controls = {
...customFabricObjectControls,
mb: moreStylesControl,
};

View File

@ -7,6 +7,8 @@ import { fabric } from 'fabric';
import * as log from '../logging/log';
import type { ImageStateType } from './ImageStateType';
import { MediaEditorFabricAnalogTimeSticker } from './MediaEditorFabricAnalogTimeSticker';
import { MediaEditorFabricDigitalTimeSticker } from './MediaEditorFabricDigitalTimeSticker';
import { MediaEditorFabricIText } from './MediaEditorFabricIText';
import { MediaEditorFabricPath } from './MediaEditorFabricPath';
import { MediaEditorFabricSticker } from './MediaEditorFabricSticker';
@ -152,6 +154,8 @@ export function useFabricHistory({
// doesn't make it easy to deserialize into a custom class without polluting the
// global namespace. See <http://fabricjs.com/fabric-intro-part-3#subclassing>.
Object.assign(fabric, {
MediaEditorFabricAnalogTimeSticker,
MediaEditorFabricDigitalTimeSticker,
MediaEditorFabricIText,
MediaEditorFabricPath,
MediaEditorFabricSticker,
@ -216,7 +220,7 @@ export function useFabricHistory({
}
function getCanvasState(fabricCanvas: fabric.Canvas): string {
return JSON.stringify(fabricCanvas.toDatalessJSON());
return JSON.stringify(fabricCanvas.toDatalessJSON(['data']));
}
function getIsTimeTraveling({

View File

@ -0,0 +1,46 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
function render(
ctx: CanvasRenderingContext2D,
left: number,
top: number
): void {
ctx.save();
ctx.font = '11px Inter';
const text = window.i18n('icu:MediaEditor__clock-more-styles');
const textMetrics = ctx.measureText(text);
const boxHeight = textMetrics.fontBoundingBoxAscent * 2;
const boxWidth = textMetrics.width * 1.5;
const boxX = left - boxWidth / 2;
const textX = left - textMetrics.width / 2;
const textY = top + boxHeight / 1.5;
// box
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#000000';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.roundRect(boxX, top, boxWidth, boxHeight, 4);
ctx.closePath();
ctx.fill();
// text
ctx.fillStyle = '#fff';
ctx.fillText(text, textX, textY);
ctx.restore();
}
export const moreStyles = {
cursorStyleHandler: (): 'pointer' => 'pointer',
offsetY: 20,
render,
sizeX: 100,
sizeY: 33,
withConnection: true,
x: 0,
y: 0.5,
};

16
ts/util/getAnalogTime.ts Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const HOURS = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330];
const NEXT_HOUR_DEG = 30;
export function getAnalogTime(): { hour: number; minute: number } {
const date = new Date();
const minutesBy60 = 60 / date.getMinutes();
const minute = 360 / minutesBy60;
const hourIndex = date.getHours() % 12;
const currentHour = HOURS[hourIndex] ?? 0;
const hour = currentHour + NEXT_HOUR_DEG / minutesBy60;
return { hour, minute };
}