From 8d015133e3bbf35a80d290160c1f26829e965ce6 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 11 Jun 2023 13:26:16 -0500 Subject: [PATCH 001/640] Update translations --- src/addons/settings/translations.json | 8 +++--- .../generated-translations.json | 25 ++++++++++++++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/addons/settings/translations.json b/src/addons/settings/translations.json index 10067a8944c..86b5a2f2eb1 100644 --- a/src/addons/settings/translations.json +++ b/src/addons/settings/translations.json @@ -275,15 +275,15 @@ "addonFeedback": "Addonfeedback", "confirmResetAll": "Weet je zeker dat je alle addonsinstellingen wilt resetten naar hun standaardinstellingen?", "credits": "Dank aan:", - "dirty": "Tabbladen verversen om instellingen toe te passen.", - "dirtyButton": "Verversen", - "enableDangerous": "Deze addon is gevaarlijk en kan opzettelijk functies UITSCHAKELEN. De meeste gebruikers zouden deze addon NIET moeten inschakelen. Weet je zeker dat je het toch wilt doen?", + "dirty": "Ververs tabbladen om instellingen toe te passen.", + "dirtyButton": "Nu verversen", + "enableDangerous": "Deze addon is gevaarlijk en kan opzettelijk functies UITSCHAKELEN. De meeste gebruikers moeten deze addon NIET gebruiken. Weet je zeker dat je het wilt inschakelen?", "export": "Instellingen exporteren", "groupDanger": "Gevaarlijk ({number})", "groupNew": "Nieuw ({number})", "groupOthers": "Anderen ({number})", "import": "Instellingen importeren", - "noCompiler": "Deze addon werkt alleen als de compiler is uitgezet, dit kun je doen in Geavanceerd > Compiler Uitzetten of door de \"Compiler uitzetten in editor\"-addon.", + "noCompiler": "Deze addon werkt alleen als de compiler is uitgeschakeld, dit kun je doen in Geavanceerd > Compiler Uitschakelen of door de \"Compiler uitschakelen in editor\"-addon.", "noResults": "Geen resultaten.", "presets": "Voorinstellingen", "reset": "Resetten", diff --git a/src/lib/tw-translations/generated-translations.json b/src/lib/tw-translations/generated-translations.json index ee85eba9c50..fced6df5794 100644 --- a/src/lib/tw-translations/generated-translations.json +++ b/src/lib/tw-translations/generated-translations.json @@ -13,13 +13,25 @@ "tw.browserModal.desc": "Assegura't que fas servir una versió recent de Google Chrome, Mozilla Firefox, Microsoft Edge o Apple Safari.", "tw.cantUseCloud": "Tot i que pots crear variables al núvol, no funcionaran tret que aquest projecte es carregui a Scratch o es converteixi amb una eina com ara {packager}.", "tw.changeUsername.cannotChangeWhileRunning": "El nom d'usuari no es pot modificar si el projecte s'està executant.", + "tw.clipboard.danger": "Si el vostre porta-retalls conté dades com ara contrasenyes, és possible que el projecte les comparteixi amb altres persones o servidors.", + "tw.clipboard.permission": "L'accés al porta-retalls podria no funcionar en alguns navegadors. Si s'hi permet, es permetran automàticament propers accessos al porta-retalls.", + "tw.clipboard.title": "El projecte vol llegir les dades del vostre porta-retalls.", "tw.cloudVariableBadge": "Aquest projecte utilitza variables al núvol. El TurboWarp utilitza el seu propi servidor variable de núvol independent de Scratch. Vés amb molt de compte amb la suplantació d'identitat, ja que qualsevol pot canviar el seu nom d'usuari per qualsevol.{learnMore}", "tw.code": "Codi font", "tw.confirmIncompatibleExtension": "Aquesta extensió és incompatible amb Scratch. Els projectes fets amb ell no es poden penjar al lloc web del Scratch. Segur que vols activar-lo?", + "tw.customExtension.description": "Carregueu extensions personalitzades des d'una adreça URL, fitxers o el codi font en JavaScript.", "tw.customExtension.name": "Extensió personalitzada", "tw.customExtensionModal.file": "Fitxer", "tw.customExtensionModal.load": "Carregar", + "tw.customExtensionModal.promptFile": "Seleccioneu el fitxer JavaScript de l'extensió:", + "tw.customExtensionModal.promptText": "Enganxeu el codi font en JavaScript de l'extensió:", + "tw.customExtensionModal.promptURL": "Introduïu l'adreça URL de l'extensió:", "tw.customExtensionModal.title": "Carregar una extensió personalitzada", + "tw.customExtensionModal.trusted": "Aquesta extensió es carregarà sense sandbox perquè ve d'una font verificada.", + "tw.customExtensionModal.unsandboxed": "Executar l'extensió sense sandbox", + "tw.customExtensionModal.unsandboxedWarning1": "Carregar les extensions sense el sandbox és perillós i no s'hauria d'activar si no sabeu el què feu.", + "tw.customExtensionModal.unsandboxedWarning2": "Les extensions sense \"sandbox\" poden corrompre el vostre projecte, suprimir les vostres configuracions, obtenir contrasenyes i altres actes perillosos. Els desenvolupadors de {APP_NAME} no es fan responsables dels problemes que se'n poden derivar.", + "tw.customExtensionModal.untrusted": "Les extensions d'URL que no siguin fiables es carregaran sempre amb sandbox per la vostra seguretat.", "tw.extensionGallery.description": "Aquí llistem moltes extensions per la vostra comoditat. Podeu trobar encara més informació a extensions.turbowarp.org.", "tw.extensionGallery.name": "Galeria d'extensions del TurboWarp", "tw.extensions.incompatible": "No compatible amb Scratch", @@ -46,6 +58,11 @@ "tw.invalidParameters.clones": "El paràmetre d'URL \"clone\" no és correcte", "tw.invalidParameters.fps": "El paràmetre d'URL \"fps\" no és correcte", "tw.loadError": "No s'ha pogut carregar el projecte: {error}", + "tw.loadExtension.embedded": "El projecte vol carregar una extensió personalitzada amb aquest codi:", + "tw.loadExtension.sandboxed": "Tot i que el codi s'emmagatzemarà al sandbox, encara podrà tenir accés a informació sobre el vostre dispositiu, com ara la vostra adreça IP i la ubicació general. Assegureu-vos de confiar en l'autor d'aquesta extensió abans de seguir.", + "tw.loadExtension.unsandboxed": "Executar l'extensió sense sandbox", + "tw.loadExtension.unsandboxedWarning": "Carregar extensions sense sandbox és perillós. Poden corrompre el vostre projecte, suprimir les vostres configuracions, obtenir contrasenyes i altres actes perillosos. Els desenvolupadors de {APP_NAME} no es fan responsables dels problemes que se'n poden derivar.", + "tw.loadExtension.url": "El projecte vol carregar una extensió personalitzada a partir d'aquesta URL:", "tw.loader.assets.known": "Baixant assets ({complete}/{total}) …", "tw.loader.assets.unknown": "Baixant assets…", "tw.loader.data": "S'estan baixant les dades del projecte...", @@ -66,6 +83,8 @@ "tw.menuBar.reportError2": "Això és un error. Informeu-ho si us plau.", "tw.menuBar.saveAs": "Desa com a {file}", "tw.menuBar.seeInside": "Veure per dins", + "tw.notify.permission": "Si es permet, és possible que es demani que habiliteu les notificacions al vostre navegador i es permetran més notificacions de forma automàtica.", + "tw.notify.title": "El projecte demana mostrar notificacions.", "tw.oldDownload": "Desa com fitxer separat...", "tw.opcode.2000": "dies des del 2000", "tw.opcode.mousedown": "ratolí clicat?", @@ -76,6 +95,10 @@ "tw.openWindow.title": "El projecte vol obrir una finestra o una pestanya amb l'URL:", "tw.paint.alpha": "Opacitat", "tw.privacy": "Política de privacitat", + "tw.recordAudio.permission": "Si es permet, és possible que se us demani que habiliteu l'accés al micròfon des del vostre navegador, i es permetran més accessos al micròfon de forma automàtica.", + "tw.recordAudio.title": "El projecte vol gravar àudio del micròfon. Això pot incloure transcripció de text o dades d'arrel de l'àudio. És possible que el projecte comparteixi l'àudio amb altres persones o servidors.", + "tw.recordVideo.permission": "Si es permet, és possible que se us demani que habiliteu l'accés a la càmera des del vostre navegador, i es permetran més accessos a la càmera de forma automàtica.", + "tw.recordVideo.title": "El projecte vol gravar vídeo des de la càmera. És possible que el projecte comparteixi imatges amb altres persones o servidors.", "tw.redirect.dangerous": "Els desenvolupadors de {APP_NAME} no han revisat aquest lloc web. Pot contenir codi perillós o maliciós.", "tw.redirect.title": "El projecte vol reencaminar aquesta pestanya a l'URL:", "tw.restorePoint.confirm": "L'editor registra automàticament un punt de restauració en cas que alguna cosa surti malament i oblidis desar els canvis. No hauríes de confiar en això i no és pot garantir que recuperi el vostre projecte. Vols intentar carregar-lo?", @@ -86,6 +109,7 @@ "tw.securityManager.allow": "Permet", "tw.securityManager.deny": "Denega", "tw.securityManager.title": "Seguretat de l'Extensió", + "tw.securityManager.trust": "Si s'hi permet, es permetran automàticament més sol·licituds al mateix lloc web.", "tw.securityManager.why": "Es pot utilitzar per descarregar imatges, so, implementar multijugador, accedir a una API o amb propòsits maliciosos. Això compartirà la vostra adreça IP, la ubicació generalitzada, i altres dades amb el lloc web.", "tw.settingsModal.customStageSize": "Mida personalitzada de l'escenari:", "tw.settingsModal.customStageSizeHelp": "Canvia la mida de l'escenari Scratch de 480x360 a una altra mida. Prova 640x360 per fer que l'escenari sigui panoràmic. Molt pocs projectes ho gestionen correctament.", @@ -331,7 +355,6 @@ "tw.paint.alpha": "Durchsichtigkeit", "tw.privacy": "Datenschutzerklärung", "tw.recordAudio.permission": "Wenn zugelassen, könnte eine Anfrage deines Browsers erscheinen, und zukünftige Zugriffe werden automatisch zugelassen.", - "tw.recordAudio.title": "Das Projekt möchte Mikrofonaudio aufnehmen. Dies könnte eine Transkribierung oder einfache Audiodatein bedeuten. Es könnte in der Lage sein, sie mit anderen Nutzern oder Servern zu teilen.", "tw.redirect.dangerous": "Diese Website wurde nicht von den {APP_NAME}-Entwicklern geprüft. Sie könnte schädlichen oder bösartigen Code enthalten.", "tw.redirect.title": "Das Projekt will von diesem Tab zur URL:", "tw.restorePoint.confirm": "Der Editor speichert einen automatischen Wiederherstellungspunkt, falls du vergessen hast zu speichern und ein Fehler auftritt. Du solltest dich nicht darauf verlassen und wir können nicht garantieren, dass dein Projekt wiederhergestellt wird. Versuchen, ihn zu laden?", From ee0c4cfda1a1a118ffae828d9cbb5ff5208c53b0 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 11 Jun 2023 20:13:08 -0500 Subject: [PATCH 002/640] Revert "Temporarily delist sensing+ from the built in library" This reverts commit 36324f856b8f75402fcee82b3a461cf72085ec41. --- src/lib/libraries/extensions/index.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx index d7144591e11..c5fbb6be1b1 100644 --- a/src/lib/libraries/extensions/index.jsx +++ b/src/lib/libraries/extensions/index.jsx @@ -455,6 +455,13 @@ export default [ extensionURL: 'https://extensions.turbowarp.org/runtime-options.js', iconURL: runtimeOptionsIcon }), + galleryItem({ + name: 'Sensing Plus', + description: 'An extension to the sensing category. Created by ObviousAlexC.', + extensionId: 'obviousalexsensing', + extensionURL: 'https://extensions.turbowarp.org/obviousAlexC/SensingPlus.js', + iconURL: sensingPlusIcon + }), galleryItem({ name: 'Clones Plus', description: 'Expansion of Scratch\'s clone features. Created by LukeManiaStudios.', From 7b9a360668e2bd8cfb2f004444bbf1792f6b792d Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 12:32:02 -0500 Subject: [PATCH 003/640] Improve list monitor footer styles Removed empty space underneath footer, and added border above footer. --- src/components/monitor/monitor.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/monitor/monitor.css b/src/components/monitor/monitor.css index f922d4542db..acbbf05d51f 100644 --- a/src/components/monitor/monitor.css +++ b/src/components/monitor/monitor.css @@ -94,7 +94,7 @@ display: flex; flex-direction: column; overflow-x: hidden; - height: calc(100% - 44px); + flex-grow: 1; } .list-row { @@ -132,6 +132,7 @@ padding: 3px; font-size: 0.75rem; font-weight: bold; + border-top: 1px solid $ui-black-transparent-default; color: $text-primary-light; /* tw: do not look different in dark mode */ } From 5109b44a351b74f40ad6e8cde8a1e29216d42e55 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 12:36:50 -0500 Subject: [PATCH 004/640] Expose monitor ID in DOM may be useful for extensions --- src/components/monitor/monitor.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx index a9c08c6c3ee..5c184fa524c 100644 --- a/src/components/monitor/monitor.jsx +++ b/src/components/monitor/monitor.jsx @@ -48,6 +48,7 @@ const MonitorComponent = props => ( className={styles.monitorContainer} componentRef={props.componentRef} onDoubleClick={props.mode === 'list' || !props.draggable ? null : props.onNextMode} + data-id={props.id} > {React.createElement(modes[props.mode], { categoryColor: categories[props.category], @@ -131,6 +132,7 @@ MonitorComponent.propTypes = { category: PropTypes.oneOf(Object.keys(categories)), componentRef: PropTypes.func.isRequired, draggable: PropTypes.bool.isRequired, + id: PropTypes.string.isRequired, label: PropTypes.string.isRequired, mode: PropTypes.oneOf(monitorModes), onDragEnd: PropTypes.func.isRequired, From d6eb0685c603616bf96ee692bd49d38f38bfe7a9 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 13:00:18 -0500 Subject: [PATCH 005/640] Expose monitor opcode in DOM may be useful for extensions TW/packager does something similar --- src/components/monitor/monitor.jsx | 2 ++ src/containers/monitor.jsx | 1 + 2 files changed, 3 insertions(+) diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx index 5c184fa524c..ef78e985c1c 100644 --- a/src/components/monitor/monitor.jsx +++ b/src/components/monitor/monitor.jsx @@ -49,6 +49,7 @@ const MonitorComponent = props => ( componentRef={props.componentRef} onDoubleClick={props.mode === 'list' || !props.draggable ? null : props.onNextMode} data-id={props.id} + data-opcode={props.opcode} > {React.createElement(modes[props.mode], { categoryColor: categories[props.category], @@ -135,6 +136,7 @@ MonitorComponent.propTypes = { id: PropTypes.string.isRequired, label: PropTypes.string.isRequired, mode: PropTypes.oneOf(monitorModes), + opcode: PropTypes.string.isRequired, onDragEnd: PropTypes.func.isRequired, onExport: PropTypes.func, onImport: PropTypes.func, diff --git a/src/containers/monitor.jsx b/src/containers/monitor.jsx index 58e442c4b18..982befb94a3 100644 --- a/src/containers/monitor.jsx +++ b/src/containers/monitor.jsx @@ -215,6 +215,7 @@ class Monitor extends React.Component { Date: Sun, 18 Jun 2023 13:31:54 -0500 Subject: [PATCH 006/640] Remove small gap between list items and footer --- src/components/monitor/list-monitor-scroller.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/monitor/list-monitor-scroller.jsx b/src/components/monitor/list-monitor-scroller.jsx index cfcd6a248b8..0d0e5b73165 100644 --- a/src/components/monitor/list-monitor-scroller.jsx +++ b/src/components/monitor/list-monitor-scroller.jsx @@ -81,7 +81,7 @@ class ListMonitorScroller extends React.Component { Date: Sun, 18 Jun 2023 18:28:00 -0500 Subject: [PATCH 007/640] Update extension library --- .../libraries/extensions/gallery/README.md | 36 +------------------ .../extensions/gallery/animated-text.svg | 2 +- .../libraries/extensions/gallery/bigint.svg | 1 + .../libraries/extensions/gallery/cursor.svg | 2 +- src/lib/libraries/extensions/gallery/file.svg | 1 - .../libraries/extensions/gallery/files.svg | 1 + .../extensions/gallery/local-storage.svg | 1 + .../extensions/gallery/looksplus.svg | 1 + .../extensions/gallery/pointerlock.svg | 2 +- .../extensions/gallery/runtime-options.svg | 2 +- .../libraries/extensions/gallery/stretch.svg | 2 +- .../extensions/gallery/utilities.svg | 2 +- src/lib/libraries/extensions/index.jsx | 31 ++++++++++++---- 13 files changed, 36 insertions(+), 48 deletions(-) create mode 100644 src/lib/libraries/extensions/gallery/bigint.svg delete mode 100644 src/lib/libraries/extensions/gallery/file.svg create mode 100644 src/lib/libraries/extensions/gallery/files.svg create mode 100644 src/lib/libraries/extensions/gallery/local-storage.svg create mode 100644 src/lib/libraries/extensions/gallery/looksplus.svg diff --git a/src/lib/libraries/extensions/gallery/README.md b/src/lib/libraries/extensions/gallery/README.md index e875e341452..42394fd9f15 100644 --- a/src/lib/libraries/extensions/gallery/README.md +++ b/src/lib/libraries/extensions/gallery/README.md @@ -1,35 +1 @@ -# Image attribution - -Most of these images are adapted from: https://github.com/TurboWarp/extensions/tree/master/images - -## gallery.svg - - Created by @True-Fantom in https://github.com/TurboWarp/extensions/issues/204 - -## stretch.svg - - Created by @True-Fantom in https://github.com/TurboWarp/extensions/issues/204 - - Arrow icon from https://icon-icons.com/ru/%D0%B7%D0%BD%D0%B0%D1%87%D0%BE%D0%BA/%D0%A0%D0%B0%D0%B7%D0%BC%D0%B5%D1%80-%D1%81%D1%82%D1%80%D0%B5%D0%BB%D0%BA%D0%B8/83819 under CC-BY 4.0 - - Dango icon from Twemoji under CC-BY 4.0 - - Background is from https://haikei.app/ - -## cursor.svg - - Created by @True-Fantom in https://github.com/TurboWarp/extensions/issues/204 - - Background is from https://app.haikei.app/ - - One cursor from https://commons.wikimedia.org/wiki/File:%D0%9A%D1%83%D1%80%D1%81%D0%BE%D1%80_%22%D0%97%D0%B0%D0%BD%D1%8F%D1%82%22.png under CC-BY-SA 4.0 - - Two other cursors from https://commons.wikimedia.org/wiki/File:Mouse-cursor-hand-pointer.svg under CC-BY 2.5 - -## gamepad.svg - - Created by @True-Fantom in https://github.com/TurboWarp/extensions/issues/204 - - Gamepad icon from https://icon-icons.com/icon/appliances-console-controller-dualshock-gamepad-games-videogame/106553 under CC-BY 4.0 - - Background from https://app.haikei.app/ - -## files.svg -Created by @True-Fantom in https://github.com/TurboWarp/extensions/issues/204. File icons are from https://icon-icons.com/ru/%D0%B7%D0%BD%D0%B0%D1%87%D0%BE%D0%BA/%D1%84%D0%B0%D0%B9%D0%BB-%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82-PDF/153412 under CC-BY 4.0. Folder icon is based on https://fontawesome.com/icons/folder-open?s=regular&f=classic under CC-BY 4.0. Background is from https://bgjar.com/rect-light under CC-BY 4.0. - -## pointerlock.svg -Created by @David-Orangemoon in https://github.com/TurboWarp/extensions/issues/90#issuecomment-1367723798. The font is Noto Sans. - -## runtime-options.svg -The image is based on TurboWarp's advanced settings menu. Idea from https://github.com/TurboWarp/extensions/issues/90#issuecomment-1366314834. Font is Noto Sans. The check icon is from https://akaricons.com/ licensed under [MIT](https://raw.githubusercontent.com/artcoholic/akar-icons/master/LICENSE). - -## utilities.svg -Created by @David-Orangemoon in https://github.com/TurboWarp/extensions/issues/90#issuecomment-1367709835. The font is Deja Vu Sans. +These images are adapted from: https://github.com/TurboWarp/extensions/tree/master/images diff --git a/src/lib/libraries/extensions/gallery/animated-text.svg b/src/lib/libraries/extensions/gallery/animated-text.svg index baf23810b3c..c82a64d126b 100644 --- a/src/lib/libraries/extensions/gallery/animated-text.svg +++ b/src/lib/libraries/extensions/gallery/animated-text.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/bigint.svg b/src/lib/libraries/extensions/gallery/bigint.svg new file mode 100644 index 00000000000..e9db2ebe213 --- /dev/null +++ b/src/lib/libraries/extensions/gallery/bigint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/cursor.svg b/src/lib/libraries/extensions/gallery/cursor.svg index bf0f81dfe50..b0242c2d9e6 100644 --- a/src/lib/libraries/extensions/gallery/cursor.svg +++ b/src/lib/libraries/extensions/gallery/cursor.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/file.svg b/src/lib/libraries/extensions/gallery/file.svg deleted file mode 100644 index 09ca4a33a4a..00000000000 --- a/src/lib/libraries/extensions/gallery/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/files.svg b/src/lib/libraries/extensions/gallery/files.svg new file mode 100644 index 00000000000..0b0459d5d50 --- /dev/null +++ b/src/lib/libraries/extensions/gallery/files.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/local-storage.svg b/src/lib/libraries/extensions/gallery/local-storage.svg new file mode 100644 index 00000000000..c4b923d2fdb --- /dev/null +++ b/src/lib/libraries/extensions/gallery/local-storage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/looksplus.svg b/src/lib/libraries/extensions/gallery/looksplus.svg new file mode 100644 index 00000000000..455c8d24c72 --- /dev/null +++ b/src/lib/libraries/extensions/gallery/looksplus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/pointerlock.svg b/src/lib/libraries/extensions/gallery/pointerlock.svg index 1f0ce06c0e5..f5993789751 100644 --- a/src/lib/libraries/extensions/gallery/pointerlock.svg +++ b/src/lib/libraries/extensions/gallery/pointerlock.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/runtime-options.svg b/src/lib/libraries/extensions/gallery/runtime-options.svg index 92565ceff3c..82a28042894 100644 --- a/src/lib/libraries/extensions/gallery/runtime-options.svg +++ b/src/lib/libraries/extensions/gallery/runtime-options.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/stretch.svg b/src/lib/libraries/extensions/gallery/stretch.svg index 280514ecf07..e117eb7c0b0 100644 --- a/src/lib/libraries/extensions/gallery/stretch.svg +++ b/src/lib/libraries/extensions/gallery/stretch.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/libraries/extensions/gallery/utilities.svg b/src/lib/libraries/extensions/gallery/utilities.svg index 537c108ef9e..4f062909a32 100644 --- a/src/lib/libraries/extensions/gallery/utilities.svg +++ b/src/lib/libraries/extensions/gallery/utilities.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx index c5fbb6be1b1..acc0f50a661 100644 --- a/src/lib/libraries/extensions/index.jsx +++ b/src/lib/libraries/extensions/index.jsx @@ -50,24 +50,28 @@ import twIcon from './tw/tw.svg'; import customExtensionIcon from './custom/custom.svg'; -import galleryIcon from './gallery/gallery.svg'; +// eslint-disable-next-line no-unused-vars import unknownIcon from './gallery/unknown.svg'; +import galleryIcon from './gallery/gallery.svg'; import animatedTextIcon from './gallery/animated-text.svg'; import stretchIcon from './gallery/stretch.svg'; import gamepadIcon from './gallery/gamepad.svg'; import cursorIcon from './gallery/cursor.svg'; -import fileIcon from './gallery/file.svg'; +import filesIcon from './gallery/files.svg'; import pointerlockIcon from './gallery/pointerlock.svg'; import runtimeOptionsIcon from './gallery/runtime-options.svg'; import utilitiesIcon from './gallery/utilities.svg'; import sensingPlusIcon from './gallery/sensingplus.svg'; import clonesPlusIcon from './gallery/clonesplus.svg'; +import looksPlusIcon from './gallery/looksplus.svg'; import clippingBlendingIcon from './gallery/clippingblending.svg'; import regexIcon from './gallery/regex.svg'; import bitwiseIcon from './gallery/bitwise.svg'; import textIcon from './gallery/text.svg'; import fetchIcon from './gallery/fetch.svg'; import box2dIcon from './gallery/box2d.svg'; +import localStorageIcon from './gallery/local-storage.svg'; +import bigIntIcon from './gallery/bigint.svg'; const galleryItem = object => ({ ...object, @@ -431,7 +435,7 @@ export default [ description: 'Read and download files.', extensionId: 'files', extensionURL: 'https://extensions.turbowarp.org/files.js', - iconURL: fileIcon + iconURL: filesIcon }), galleryItem({ name: 'Pointerlock', @@ -464,11 +468,19 @@ export default [ }), galleryItem({ name: 'Clones Plus', - description: 'Expansion of Scratch\'s clone features. Created by LukeManiaStudios.', + description: 'Expansion of Scratch\'s clone features. Created by LilyMakesThings.', extensionId: 'lmsclonesplus', - extensionURL: 'https://extensions.turbowarp.org/LukeManiaStudios/ClonesPlus.js', + extensionURL: 'https://extensions.turbowarp.org/Lily/ClonesPlus.js', iconURL: clonesPlusIcon }), + galleryItem({ + name: 'Looks Plus', + // eslint-disable-next-line max-len + description: 'Expands upon the looks category, allowing you to show/hide, get costume data and edit SVG skins on sprites. Created by LilyMakesThings.', + extensionId: 'lmsLooksPlus', + extensionURL: 'https://extensions.turbowarp.org/Lily/LooksPlus.js', + iconURL: looksPlusIcon + }), galleryItem({ name: 'Clipping & Blending', description: 'Clipping outside of a specified rectangular area and additive color blending. Created by Vadik1.', @@ -490,6 +502,13 @@ export default [ extensionURL: 'https://extensions.turbowarp.org/bitwise.js', iconURL: bitwiseIcon }), + galleryItem({ + name: 'BigInt', + description: 'Math blocks that work on infinitely large integers (no decimals). Created by Skyhigh173.', + extensionId: 'skyhigh173BigInt', + extensionURL: 'https://extensions.turbowarp.org/Skyhigh173/bigint.js', + iconURL: bigIntIcon + }), galleryItem({ name: 'RegExp', description: 'Full interface for working with Regular Expressions. Created by TrueFantom.', @@ -516,7 +535,7 @@ export default [ description: 'Store data persistently.', extensionId: 'localstorage', extensionURL: 'https://extensions.turbowarp.org/local-storage.js', - iconURL: unknownIcon + iconURL: localStorageIcon }), galleryItem({ name: 'Utilities', From 62849eb30d4304658a4c231b9a206fe40a323564 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 18:37:31 -0500 Subject: [PATCH 008/640] Remove ScratchX URL rewriting This code has been unused since we implemented the custom extension modal As scratchx.org was shut down, very very few people will type them anyways --- src/containers/extension-library.jsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 16085483145..fd88ddb31dd 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -25,15 +25,6 @@ const messages = defineMessages({ } }); -export const parseExtensionURL = url => { - // Parse real extension URL from scratchx.org URL - const match = url.match(/^https?:\/\/scratchx\.org\/\?url=(.*)$/); - if (match) { - return match[1]; - } - return url; -}; - class ExtensionLibrary extends React.PureComponent { constructor (props) { super(props); @@ -59,8 +50,7 @@ class ExtensionLibrary extends React.PureComponent { if (this.props.vm.extensionManager.isExtensionLoaded(extensionId)) { this.props.onCategorySelected(extensionId); } else { - const parsedURL = isCustomURL ? parseExtensionURL(url) : url; - this.props.vm.extensionManager.loadExtensionURL(parsedURL) + this.props.vm.extensionManager.loadExtensionURL(url) .then(() => { this.props.onCategorySelected(extensionId); }) From c37321eb344c818fed30f42a01e5d1f6eef216a9 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 18:45:13 -0500 Subject: [PATCH 009/640] When opening extension gallery, close chooser in desktop app --- src/components/library/library.jsx | 9 ++++++++- src/containers/extension-library.jsx | 4 ++++ src/containers/library-item.jsx | 6 +----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/library/library.jsx b/src/components/library/library.jsx index cc8f6e5e0c6..26a258e154c 100644 --- a/src/components/library/library.jsx +++ b/src/components/library/library.jsx @@ -3,6 +3,7 @@ import bindAll from 'lodash.bindall'; import PropTypes from 'prop-types'; import React from 'react'; import {defineMessages, injectIntl, intlShape} from 'react-intl'; +import {isScratchDesktop} from '../../lib/isScratchDesktop'; import LibraryItem from '../../containers/library-item.jsx'; import Modal from '../../containers/modal.jsx'; @@ -81,7 +82,13 @@ class LibraryComponent extends React.Component { } } handleSelect (id) { - this.handleClose(); + const extension = this.getFilteredData()[id]; + if (extension.href) { + window.open(extension.href); + } + if (!extension.href || isScratchDesktop()) { + this.handleClose(); + } this.props.onItemSelected(this.getFilteredData()[id]); } handleClose () { diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index fd88ddb31dd..e502c667689 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -33,6 +33,10 @@ class ExtensionLibrary extends React.PureComponent { ]); } handleItemSelect (item) { + if (item.href) { + return; + } + const extensionId = item.extensionId; const isCustomURL = !item.disabled && !extensionId; if (isCustomURL) { diff --git a/src/containers/library-item.jsx b/src/containers/library-item.jsx index 4a2d0764037..0cb839dd0ce 100644 --- a/src/containers/library-item.jsx +++ b/src/containers/library-item.jsx @@ -34,11 +34,7 @@ class LibraryItem extends React.PureComponent { } handleClick (e) { if (!this.props.disabled) { - if (this.props.href) { - window.open(this.props.href); - } else { - this.props.onSelect(this.props.id); - } + this.props.onSelect(this.props.id); } e.preventDefault(); } From d65e0b6ce291032cd5a953c4adb285a56991ce6c Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 18:51:45 -0500 Subject: [PATCH 010/640] Remove test for feature that doesn't exist anymore --- test/unit/containers/extension-library-tw.test.jsx | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 test/unit/containers/extension-library-tw.test.jsx diff --git a/test/unit/containers/extension-library-tw.test.jsx b/test/unit/containers/extension-library-tw.test.jsx deleted file mode 100644 index f6b362dd192..00000000000 --- a/test/unit/containers/extension-library-tw.test.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import {parseExtensionURL} from '../../../src/containers/extension-library.jsx'; - -test('extension URL parsing', () => { - expect(parseExtensionURL('https://extensions.turbowarp.org/fetch.js')).toBe('https://extensions.turbowarp.org/fetch.js'); - expect(parseExtensionURL('http://scratchx.org/?url=https://sayamindu.github.io/scratch-extensions/text-to-speech/text_to_speech_extension.js')).toBe('https://sayamindu.github.io/scratch-extensions/text-to-speech/text_to_speech_extension.js'); -}); From 244c193ab10c4763161456b39ce3ba532a93dbba Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 19:09:40 -0500 Subject: [PATCH 011/640] Hide tw-disable-cloud-variables addon in desktop app --- .../tw-disable-cloud-variables/_manifest_entry.js | 2 ++ src/addons/pull.js | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/addons/addons/tw-disable-cloud-variables/_manifest_entry.js b/src/addons/addons/tw-disable-cloud-variables/_manifest_entry.js index d0ed1157fce..3dfd4fd2dc8 100644 --- a/src/addons/addons/tw-disable-cloud-variables/_manifest_entry.js +++ b/src/addons/addons/tw-disable-cloud-variables/_manifest_entry.js @@ -13,4 +13,6 @@ const manifest = { ], "enabledByDefault": false }; +import {isScratchDesktop} from "../../../lib/isScratchDesktop"; +if (isScratchDesktop()) manifest.unsupported = true; export default manifest; diff --git a/src/addons/pull.js b/src/addons/pull.js index 633334f330d..119d84ec9ef 100644 --- a/src/addons/pull.js +++ b/src/addons/pull.js @@ -219,13 +219,17 @@ const generateManifestEntry = (id, manifest) => { } if (manifest.permissions && manifest.permissions.includes('clipboardWrite')) { result += 'import {clipboardSupported} from "../../environment";\n'; - result += `if (!clipboardSupported) manifest.unsupported = true;\n`; + result += 'if (!clipboardSupported) manifest.unsupported = true;\n'; } if (id === 'mediarecorder') { result += 'import {mediaRecorderSupported} from "../../environment";\n'; - result += `if (!mediaRecorderSupported) manifest.unsupported = true;\n`; + result += 'if (!mediaRecorderSupported) manifest.unsupported = true;\n'; } - result += `export default manifest;\n`; + if (id === 'tw-disable-cloud-variables') { + result += 'import {isScratchDesktop} from "../../../lib/isScratchDesktop";\n'; + result += 'if (isScratchDesktop()) manifest.unsupported = true;\n'; + } + result += 'export default manifest;\n'; return result; }; From bf458670a877c982f98b335659dedc9f889354d8 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 19:12:14 -0500 Subject: [PATCH 012/640] Update translations --- .../generated-translations.json | 79 ++++++++++++++----- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/src/lib/tw-translations/generated-translations.json b/src/lib/tw-translations/generated-translations.json index fced6df5794..b9f77d66ff6 100644 --- a/src/lib/tw-translations/generated-translations.json +++ b/src/lib/tw-translations/generated-translations.json @@ -1000,7 +1000,18 @@ "tw.confirmIncompatibleExtension": "この拡張機能はScratchとの互換性がありません。これを使って作ったプロジェクトは、Scratchのウェブサイトにアップロードできません。本当に使いますか?", "tw.customExtension.name": "カスタム拡張機能", "tw.customExtensionModal.file": "ファイル", + "tw.customExtensionModal.load": "読み込む", + "tw.customExtensionModal.promptFile": "拡張機能のJavaScriptファイルを選択する: ", + "tw.customExtensionModal.promptText": "拡張機能のJavaScriptのソースコードをペーストする:", + "tw.customExtensionModal.promptURL": "拡張機能のURLを入力: ", + "tw.customExtensionModal.text": "文字", + "tw.customExtensionModal.title": "カスタム拡張機能を読み込む", + "tw.customExtensionModal.unsandboxed": "サンドボックスなしで拡張機能を実行する", + "tw.customExtensionModal.unsandboxedWarning1": "サンドボックスなしで拡張機能を読み込むのは危険です。よく分からない場合は無効にしてください。", + "tw.customExtensionModal.unsandboxedWarning2": "サンドボックス化していない拡張機能は、プロジェクトを破損させたり、設定を削除したり、パスワードをフィッシングしたり、その他の悪いことをする可能性があります。{APP_NAME}の開発者は、この拡張機能によって生じるいかなる問題にも責任を負いません。", + "tw.extensionGallery.description": "ここでは、都合上、たくさんの拡張機能をリストアップしています。extensions.turbowarp.orgでは、さらに多くの拡張機能を見つけることができます。", "tw.extensionGallery.name": "TurboWarpの拡張機能集", + "tw.extensions.incompatible": "Scratchには対応していません。", "tw.featuredProjectsStudio": "Scratchのスタジオで見る", "tw.feedback": "フィードバックとバグ", "tw.feedbackButton": "TurboWarpへのフィードバック", @@ -1021,6 +1032,7 @@ "tw.invalidParameters.clones": "「クローン」のURLパラメーターが無効です", "tw.invalidParameters.fps": "「FPS」のURLパラメーターが無効です", "tw.loadError": "プロジェクトを読み込めませんでした: {error}", + "tw.loadExtension.unsandboxed": "サンドボックスなしで拡張機能を実行する", "tw.loader.assets.known": "プロジェクトをダウンロード中 ({complete}/{total}) ...", "tw.loader.assets.unknown": "ダウンロード中…", "tw.loader.data": "プロジェクトのデータをダウンロード中…", @@ -1210,12 +1222,12 @@ }, "ko": { "tw.alerts.autosaving": "복원 지점 생성 중...", - "tw.alerts.lostPeripheralConnection": "{extensionName} 확장 기능과 연결이 끊어짐.", + "tw.alerts.lostPeripheralConnection": "{extensionName} 확장 기능과 연결이 끊어졌습니다.", "tw.alerts.savedToDisk": "컴퓨터에 저장되었습니다.", "tw.backpack.rename": "새로운 이름:", "tw.blocks.addons": "애드온", "tw.blocks.buttonIsDown": "[MOUSE_BUTTON]마우스 버튼이 눌렸는가?", - "tw.blocks.lastKeyPressed": "최근 눌린 키", + "tw.blocks.lastKeyPressed": "마지막으로 눌린 키", "tw.blocks.mouseButton.middle": "(1) 가운데", "tw.blocks.mouseButton.primary": "(0) 왼쪽", "tw.blocks.mouseButton.secondary": "(2) 오른쪽", @@ -1223,17 +1235,30 @@ "tw.browserModal.desc": "최신 버전의 크롬, Mozilla Firefox, Microsoft Edge 또는 Apple Safari를 사용하고 있는지 확인하세요.", "tw.cantUseCloud": "클라우드 변수를 생성할 수는 있으나, 해당 프로젝트가 Scratch에 업로드 되거나 {packager}같은 툴을 사용해 변환되지 않는 한 클라우드 변수는 작동하지 않습니다.", "tw.changeUsername.cannotChangeWhileRunning": "'사용자 이름'은 프로젝트가 실행되는 동안 변경될 수 없습니다.", + "tw.clipboard.danger": "클립보드에 비밀번호와 같은 민감한 정보가 포함되어 있을 경우, 프로젝트에 의해 해당 정보가 다른 사용자 또는 서버와 공유될 수 있습니다.", + "tw.clipboard.permission": "몇몇 브라우저에서는 클립보드에 접근이 불가능할 수 있습니다. 권한을 허용할 경우, 추가적인 클립보드 읽기 요청이 자동으로 허용됩니다.", + "tw.clipboard.title": "프로젝트가 클립보드의 데이터 읽기 권한을 요청합니다.", "tw.cloudVariableBadge": "이 프로젝트는 클라우드 변수를 사용합니다. 터보워프는 스크래치와 독립적인 클라우드 변수 서버를 사용합니다. 사용자 이름은 어떤 것으로든 바꿀 수 있으니 사칭을 주의하세요. {learnMore}", "tw.code": "소스코드", - "tw.confirmIncompatibleExtension": "이 확장 기능은 스크래치에 호환되지 않습니다. 프로젝트가 스크래치 웹사이트에 업로드되 지 않을 수 있습니다. 확장 기능을 활성화할까요?", - "tw.customExtension.name": "사용자 지정 확장 프로그램", + "tw.confirmIncompatibleExtension": "이 확장 기능은 스크래치와 호환되지 않습니다.\n해당 확장 기능을 활성화할 경우 프로젝트가 스크래치 웹사이트에 업로드되지 않을 수 있습니다. 계속하시겠습니까?", + "tw.customExtension.description": "사용자 지정 확장 기능을 URL, 파일 또는 자바스크립트 소스코드를 통해 불러옵니다.", + "tw.customExtension.name": "사용자 지정 확장 기능", "tw.customExtensionModal.file": "파일", "tw.customExtensionModal.load": "불러오기", + "tw.customExtensionModal.promptFile": "확장 기능의 자바스크립트 파일을 선택하세요 : ", + "tw.customExtensionModal.promptText": "확장 기능의 자바스크립트 소스코드를 붙여넣기 하세요 : ", + "tw.customExtensionModal.promptURL": "확장 기능의 URL을 입력하세요 : ", + "tw.customExtensionModal.text": "텍스트", "tw.customExtensionModal.title": "사용자 지정 확장 기능 불러오기", + "tw.customExtensionModal.trusted": "해당 확장 기능은 신뢰할 수 있는 공급원으로부터 제공되기 때문에 샌드박스되지 않은 상태로 로딩됩니다.", + "tw.customExtensionModal.unsandboxed": "확장 기능을 샌드박스 하지 않고 실행", + "tw.customExtensionModal.unsandboxedWarning1": "확장 기능을 샌드박스 하지 않고 불러오는 것은 위험하며, 해당 확장 기능이 활성화되지 않을 수 있습니다.\n본인이 무엇을 하고 있는지 모를 경우 해당 작업을 중단하세요.", + "tw.customExtensionModal.unsandboxedWarning2": "샌드박스되지 않은 확장 기능은 사용자 설정을 삭제하거나, 비밀번호를 훔쳐 가는 등의 악의적인 행동으로 프로젝트를 망칠 수 있습니다.\n{APP_NAME}의 개발자는 해당 문제로 인해 야기되는 결과에 어떤 책임도 지지 않습니다.", + "tw.customExtensionModal.untrusted": "신뢰할 수 없는 URL로부터 제공되는 확장 기능은 보안을 위해 항상 샌드박스 처리됩니다.", "tw.extensionGallery.description": "편의를 위해서 여기에 많은 확장 기능을 나열해 놓았습니다. 더 많은 확장 기능을 extensions.turbowarp.org에서 찾을 수 있습니다.", "tw.extensionGallery.name": "터보워프 확장 기능 갤러리", "tw.extensions.incompatible": "스크래치와 호환되지 않습니다.", - "tw.featuredProjectsStudio": "스크래치 스튜디오 보기", + "tw.featuredProjectsStudio": "스크래치 스튜디오에서 특집 프로젝트 목록 보기", "tw.feedback": "문의 및 오류 신고", "tw.feedbackButton": "문의하기", "tw.fetch.title": "프로젝트가 웹사이트 연결을 요청합니다 : ", @@ -1256,6 +1281,11 @@ "tw.invalidParameters.clones": "\"clone\" URL 파라미터가 유효하지 않습니다", "tw.invalidParameters.fps": "\"fps\" URL 파라미터가 유효하지 않습니다", "tw.loadError": "프로젝트를 불러올 수 없습니다.\n오류 코드 : {error}", + "tw.loadExtension.embedded": "프로젝트가 코드를 포함한 사용자 지정 확장 기능을 요구합니다 : ", + "tw.loadExtension.sandboxed": "코드가 샌드박스 되더라도, 여전히 IP 주소나 위치 정보 같은 사용자 기기 정보에 접근할 수 있습니다.\n계속하기 전에 해당 확장 기능의 제작자를 신뢰할 수 있는지 확인하세요.", + "tw.loadExtension.unsandboxed": "확장 기능을 샌드박스 하지 않고 실행", + "tw.loadExtension.unsandboxedWarning": "샌드박스 없이 확장 기능을 불러오는 것은 위험합니다. 해당 확장 기능이 사용자 설정을 삭제하거나, 비밀번호를 훔쳐 가는 등의 악의적인 행동으로 프로젝트를 망칠 수 있습니다.\n{APP_NAME}의 개발자는 해당 문제로 인해 야기되는 결과에 어떤 책임도 지지 않습니다.", + "tw.loadExtension.url": "프로젝트가 URL로부터 사용자 지정 확장 기능을 요구합니다 : ", "tw.loader.assets.known": "정보를 내려받는 중입니다({complete}/{total}) ...", "tw.loader.assets.unknown": "정보를 내려받는 중입니다...", "tw.loader.data": "프로젝트 자료를 내려받는 중입니다...", @@ -1274,10 +1304,12 @@ "tw.menuBar.newFramerate": "사용할 FPS:", "tw.menuBar.package": "프로젝트 패키징", "tw.menuBar.reportError1": "일부 스크립트를 컴파일 할 수 없습니다.", - "tw.menuBar.reportError2": "이것은 오류입니다. 해당 오류를 신고해주실 것을 부탁드립니다.", + "tw.menuBar.reportError2": "오류가 발생했습니다. 해당 오류를 보고해주실 것을 부탁드립니다.", "tw.menuBar.saveAs": "{file}로 저장하기", "tw.menuBar.seeInside": "스크립트 보기", "tw.mono": "모노", + "tw.notify.permission": "해당 권한을 허용할 경우, 브라우저에서 알림 표시 허용 여부를 묻는 알림 창이 나타날 수 있으며, 추가적인 알림 표시 요청이 자동으로 허용됩니다.", + "tw.notify.title": "프로젝트가 알림 표시 권한을 요청합니다.", "tw.oldDownload": "다른 파일로 저장", "tw.opcode.2000": "2000년 이후 경과 날짜", "tw.opcode.mousedown": "마우스가 눌렸는가?", @@ -1288,6 +1320,10 @@ "tw.openWindow.title": "프로젝트가 다음 URL을 새 창이나 탭에서 열 것을 요청합니다 : ", "tw.paint.alpha": "불투명도", "tw.privacy": "개인정보 보호 정책", + "tw.recordAudio.permission": "해당 권한을 허용할 경우, 브라우저에서 마이크 접근 허용 여부를 묻는 알림 창이 나타날 수 있으며, 추가적인 마이크 접근 요청이 자동으로 허용됩니다.", + "tw.recordAudio.title": "프로젝트가 사용자의 마이크를 통해 오디오를 녹음할 수 있는 권한을 요청합니다. 여기에는 텍스트 변환 음성이나 원본 오디오 데이터가 포함됩니다.\n녹음된 오디오는 프로젝트에 의해 다른 사용자 또는 서버와 공유될 수 있습니다.", + "tw.recordVideo.permission": "해당 권한을 허용할 경우, 브라우저에서 카메라 접근 허용 여부를 묻는 알림 창이 나타날 수 있으며, 추가적인 카메라 접근 요청이 자동으로 허용됩니다.", + "tw.recordVideo.title": "프로젝트가 사용자의 카메라를 통해 비디오를 녹화할 수 있는 권한을 요청합니다.\n녹화된 데이터는 프로젝트에 의해 다른 사용자 또는 서버와 공유될 수 있습니다.", "tw.redirect.dangerous": "해당 웹사이트는 {APP_NAME}의 개발자에 의해 보고된 웹사이트가 아니며, 위험하거나 악의적인 코드를 포함하고 있을 수 있습니다.", "tw.redirect.title": "프로젝트가 해당 탭을 다음 URL로 전환하기를 요청합니다 : ", "tw.restorePoint.confirm": "에디터에서 문제가 발생하거나 사용자가 저장하는 것을 깜빡했을 경우를 대비하여 터보워프에서 하나의 복원 지점을 자동으로 기록합니다. 이 기능에 의존해서는 안 되며, 해당 기능이 프로젝트를 완전히 복구하지 못 할 수도 있습니다. 저장된 복원 지점을 불러오시겠습니까?", @@ -1298,37 +1334,38 @@ "tw.securityManager.allow": "허용", "tw.securityManager.deny": "거부", "tw.securityManager.title": "확장 기능 보안", + "tw.securityManager.trust": "해당 권한을 허용할 경우, 현재 웹사이트에서의 추가적인 요청이 자동으로 허용됩니다.", "tw.securityManager.why": "해당 권한은 이미지 또는 소리 파일 다운로드, 멀티플레이어 구현, API 액세스를 위해 사용될 수 있지만 타인에 의해 악용될 수도 있습니다. 이 권한을 허용하게 되면 내 컴퓨터의 IP 주소, 위치 정보 및 기타 정보를 공유하게 됩니다.", - "tw.settingsModal.customStageSize": "무대 크기 변경하기", - "tw.settingsModal.customStageSizeHelp": "스크래치 무대 크기를 480x360외에 다른 것으로 바꿉니다. 넓은 화면을 시도해 보려면 640x360를 시도해 보세요. 매우 적은 프로젝트들은 제대로 작동할 것입니다.", + "tw.settingsModal.customStageSize": "사용자 지정 무대 크기", + "tw.settingsModal.customStageSizeHelp": "무대 크기를 480x360이 아닌 다른 크기로 바꿉니다.\n넓은 화면을 원하는 경우 640x360를 시도해 보세요. 일부 프로젝트는 올바르게 작동할 수 있습니다.", "tw.settingsModal.dangerZone": "위험한 기능", - "tw.settingsModal.disableCompiler": "컴파일러 비활성화하기", - "tw.settingsModal.disableCompilerHelp": "컴파일러를 비활성화합니다. 프로젝트를 편집하는 동안 스크립트가 즉시 업데이트되도록 하려면 컴파일러를 비활성화해도 됩니다. 이 경우가 아니라면 컴파일러를 비활성화하지 마세요.", + "tw.settingsModal.disableCompiler": "컴파일러 비활성화", + "tw.settingsModal.disableCompilerHelp": "컴파일러를 비활성화합니다.\n프로젝트를 편집하는 동안 스크립트가 즉시 업데이트되도록 하려면 컴파일러를 비활성화해도 됩니다. 이 경우가 아니라면 컴파일러를 비활성화하지 마세요.", "tw.settingsModal.featured": "추천 기능", "tw.settingsModal.fps": "60 FPS (또는 사용자 지정 FPS) 활성화", "tw.settingsModal.fpsHelp": "스크립트를 초당 30회로 실행하는 대신 초당 60회 실행합니다. 대부분의 프로젝트는 이 기능이 활성화된 상태에서 제대로 작동하지 않으며, 이 같은 경우에는 60 FPS모드 대신 보간법을 사용해야 합니다. {customFramerate}", "tw.settingsModal.fpsHelp.customFramerate": "30 또는 60 이외의 프레임률을 사용하려면 클릭합니다.", "tw.settingsModal.help": "클릭하여 도움말 열기", - "tw.settingsModal.highQualityPen": "고품질 펜 활성화하기", + "tw.settingsModal.highQualityPen": "고품질 펜 활성화", "tw.settingsModal.highQualityPenHelp": "펜 프로젝트가 더 높은 해상도에서 렌더링될 수 있도록 해주며, 에디터에서 적용되는 일부 좌표 반올림을 비활성화합니다. 이 옵션은 모든 프로젝트에 도움이 되지는 않으며 성능에 영향을 미칠 수 있습니다.", - "tw.settingsModal.infiniteClones": "무제한 복제본 활성화하기", + "tw.settingsModal.infiniteClones": "무제한 복제본 활성화", "tw.settingsModal.infiniteClonesHelp": "스크래치의 300개 복제본 제한을 비활성화합니다.", - "tw.settingsModal.interpolation": "보간법 활성화하기", + "tw.settingsModal.interpolation": "보간법 활성화", "tw.settingsModal.interpolationHelp": "스프라이트의 움직임을 보간해 프로젝트의 동작을 더 부드럽게 만듭니다. 보간법은 3D 프로젝트, 레이트레이서, 펜 프로젝트, 렉이 걸리는 프로젝트 등에서 사용할 경우 오히려 프로젝트를 더 느려지게 만들 수 있습니다.", - "tw.settingsModal.largeStageWarning": "사용자 지정 무대 크기는 권장되지 않습니다! 대신, 동일한 가로 세로 비율의 더 낮은 크기를 사용하고 전체 화면 모드가 사용자 디스플레이에 맞게 업스케일되도록 합니다.", - "tw.settingsModal.removeFencing": "무대 밖 동작 제한 풀기", + "tw.settingsModal.largeStageWarning": "사용자 지정 무대의 크기가 너무 큽니다!\n더 작은 크기의 동일한 화면비를 가진 사용자 지정 무대를 설정하고, 풀스크린 모드로 전환하여 화면이 자동으로 업스케일되도록 하는 것을 권장합니다.", + "tw.settingsModal.removeFencing": "무대 밖 동작 활성화", "tw.settingsModal.removeFencingHelp": "스프라이트가 화면 밖으로 나갈 수 있도록 하거나, 스프라이트가 원하는 만큼 커지거나 작아질 수 있도록 하거나, 충돌 감지 블록이 화면 밖에서도 작동할 수 있도록 합니다.", "tw.settingsModal.removeLimits": "제한 풀기", - "tw.settingsModal.removeMiscLimits": "기타 몇몇 제한 풀기", + "tw.settingsModal.removeMiscLimits": "기타 제한 비활성화", "tw.settingsModal.removeMiscLimitsHelp": "이미지 효과 제한과 펜 굵기 제한을 제거합니다.", "tw.settingsModal.storeProjectOptions": "프로젝트에 고급 설정 저장하기", - "tw.settingsModal.storeProjectOptionsHelp": "TurboWarp가 이 프로젝트를 불러올 때 현재 설정이 자동으로 적용되도록 고급 설정을 프로젝트에 저장합니다. <워프 타이머>와 <컴파일러 비활성화하기>는 저장되지 않습니다.", + "tw.settingsModal.storeProjectOptionsHelp": "TurboWarp가 이 프로젝트를 불러올 때 현재 설정이 자동으로 적용되도록 고급 설정을 프로젝트에 저장합니다.\n<워프 타이머 활성화>와 <컴파일러 비활성화>는 저장되지 않습니다.", "tw.settingsModal.title": "고급 설정", - "tw.settingsModal.warpTimer": "워프 타이머", - "tw.settingsModal.warpTimerHelp": "루프가 끝날 때까지 중단되지 않고 스크립트가 긴 루프 또는 무한 루프에 갇혀 낮은 프레임률로 실행되는지 확인합니다. 이러면 대부분의 충돌이 해결되지만 성능에 큰 영향을 미치므로 기본적으로 편집기에서만 사용하도록 설정됩니다.", + "tw.settingsModal.warpTimer": "워프 타이머 활성화", + "tw.settingsModal.warpTimerHelp": "스크립트가 긴 반복문 또는 무한 반복문에 갇힌 경우, 반복문이 끝날 때까지 코드가 멈춰 있지 않고 낮은 프레임에서 실행되고 있는지 확인합니다.\n이 기능은 대부분의 충돌을 막을 수 있지만 성능을 확연히 저하시킵니다. 따라서 해당 기능은 편집기에서만 활성화됩니다.", "tw.spriteSelectorItem.rename": "이름 재설정하기", "tw.stereo": "스테레오", - "tw.stereoAlert": "해당 스테레오 음원을 수정하면 자동으로 모노 음원으로 변환되며 돌이킬 수 없게 됩니다.", + "tw.stereoAlert": "해당 스테레오 음원을 수정하면 자동으로 모노 음원으로 변환되며 이 작업은 되돌릴 수 없습니다.", "tw.studioview.authorAttribution": "제작자 : {author}", "tw.studioview.error": "프로젝트의 다음 페이지를 불러오는 도중 오류가 발생했습니다.", "tw.studioview.hoverText": "{author}의 \"{title}\"", @@ -1345,7 +1382,7 @@ "tw.usernameModal.mustChange.resetIt": "초기화하기 (권장됨)", "tw.usernameModal.reset": "초기화하기", "tw.usernameModal.title": "사용자 이름 변경하기", - "tw.viewFeaturedProjects": "특집 프로젝트를 보려면 클릭하세요.", + "tw.viewFeaturedProjects": "여기를 클릭하여 특집 프로젝트를 볼 수 있습니다.", "tw.viewOnScratch": "프로젝트 스크래치에서 보기", "tw.webglModal.description": "당신의 브라우저나 컴퓨터가 {webGlLink}. 이 기술은 TurboWarp를 실행하기 위해 필요합니다. 당신의 브라우저와 그래픽 드라이버를 업데이트하거나 컴퓨터를 재시작하세요." }, From ef6987699ba065052fed1d8e61c26eb752db980b Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sun, 18 Jun 2023 20:25:44 -0500 Subject: [PATCH 013/640] Update builtin extension list --- src/lib/libraries/extensions/gallery/json.svg | 1 + src/lib/libraries/extensions/index.jsx | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/lib/libraries/extensions/gallery/json.svg diff --git a/src/lib/libraries/extensions/gallery/json.svg b/src/lib/libraries/extensions/gallery/json.svg new file mode 100644 index 00000000000..28e3b6c2fc0 --- /dev/null +++ b/src/lib/libraries/extensions/gallery/json.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx index acc0f50a661..647cbdd1a51 100644 --- a/src/lib/libraries/extensions/index.jsx +++ b/src/lib/libraries/extensions/index.jsx @@ -72,6 +72,7 @@ import fetchIcon from './gallery/fetch.svg'; import box2dIcon from './gallery/box2d.svg'; import localStorageIcon from './gallery/local-storage.svg'; import bigIntIcon from './gallery/bigint.svg'; +import jsonIcon from './gallery/json.svg'; const galleryItem = object => ({ ...object, @@ -509,6 +510,13 @@ export default [ extensionURL: 'https://extensions.turbowarp.org/Skyhigh173/bigint.js', iconURL: bigIntIcon }), + galleryItem({ + name: 'JSON', + description: 'Work with JSON objects and arrays.', + extensionId: 'skyhigh173JSON', + extensionURL: 'https://extensions.turbowarp.org/Skyhigh173/json.js', + iconURL: jsonIcon + }), galleryItem({ name: 'RegExp', description: 'Full interface for working with Regular Expressions. Created by TrueFantom.', @@ -532,7 +540,7 @@ export default [ }), galleryItem({ name: 'Local Storage', - description: 'Store data persistently.', + description: 'Store data persistently. Like cookies, but better.', extensionId: 'localstorage', extensionURL: 'https://extensions.turbowarp.org/local-storage.js', iconURL: localStorageIcon From d98d51be3c8fbcc1276d74a6e07b94ebdb4ddf17 Mon Sep 17 00:00:00 2001 From: godslayerakp <74981904+RedMan13@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:57:27 -0700 Subject: [PATCH 014/640] Update tw-packager-integration-hoc.jsx --- src/lib/tw-packager-integration-hoc.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tw-packager-integration-hoc.jsx b/src/lib/tw-packager-integration-hoc.jsx index c03b528aef4..fa0fbdc378f 100644 --- a/src/lib/tw-packager-integration-hoc.jsx +++ b/src/lib/tw-packager-integration-hoc.jsx @@ -61,7 +61,7 @@ const PackagerIntegrationHOC = function (WrappedComponent) { this.props.vm.saveProjectSb3() .then(readBlobAsArrayBuffer) .then(buffer => { - const name = `${this.props.reduxProjectTitle}.pm`; + const name = `${this.props.reduxProjectTitle}.pmp`; e.source.postMessage({ p4: { type: 'finish-import', From 73911a8956a44c8098ca7e2ae8c7e86e6d55d36a Mon Sep 17 00:00:00 2001 From: godslayerakp <74981904+RedMan13@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:58:03 -0700 Subject: [PATCH 015/640] Update sb-file-uploader-hoc.jsx --- src/lib/sb-file-uploader-hoc.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sb-file-uploader-hoc.jsx b/src/lib/sb-file-uploader-hoc.jsx index 5da6e99160d..eb0cfc116e9 100644 --- a/src/lib/sb-file-uploader-hoc.jsx +++ b/src/lib/sb-file-uploader-hoc.jsx @@ -104,7 +104,7 @@ const SBFileUploaderHOC = function (WrappedComponent) { } else { // create element and add it to DOM this.inputElement = document.createElement('input'); - this.inputElement.accept = '.sb,.sb2,.sb3,.pm'; + this.inputElement.accept = '.sb,.sb2,.sb3,.pm,.pmp'; this.inputElement.style = 'display: none;'; this.inputElement.type = 'file'; this.inputElement.onchange = this.handleChange; // connects to step 3 From 439d16234dd6f15787d33e429800ae1276ec1ee2 Mon Sep 17 00:00:00 2001 From: godslayerakp <74981904+RedMan13@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:58:56 -0700 Subject: [PATCH 016/640] Update sb3-downloader.jsx --- src/containers/sb3-downloader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/sb3-downloader.jsx b/src/containers/sb3-downloader.jsx index a57d8659d2c..775274954cf 100644 --- a/src/containers/sb3-downloader.jsx +++ b/src/containers/sb3-downloader.jsx @@ -16,7 +16,7 @@ const getProjectTitleFromFilename = fileInputFilename => { if (!fileInputFilename) return ''; // only parse title with valid scratch project extensions // (.sb, .sb2, .sb3, and .pm) - const matches = fileInputFilename.match(/^(.*)(\.sb[23]?|\.pm)$/); + const matches = fileInputFilename.match(/^(.*)(\.sb[23]?|\.pm|\.pmp)$/); if (!matches) return ''; return matches[1].substring(0, 100); // truncate project title to max 100 chars }; From b99a1784a2b96ff1282d347290da682e4b25dcd9 Mon Sep 17 00:00:00 2001 From: godslayerakp <74981904+RedMan13@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:59:54 -0700 Subject: [PATCH 017/640] Update tw-filesystem-api.js --- src/lib/tw-filesystem-api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/tw-filesystem-api.js b/src/lib/tw-filesystem-api.js index fd9ec7207ab..479ed572f03 100644 --- a/src/lib/tw-filesystem-api.js +++ b/src/lib/tw-filesystem-api.js @@ -6,7 +6,7 @@ const showSaveFilePicker = fileName => window.showSaveFilePicker({ { description: 'PenguinMod Project', accept: { - 'application/x.scratch.sb3': '.pm' + 'application/x.scratch.sb3': '.pmp' } } ], @@ -20,7 +20,7 @@ const showOpenFilePicker = async () => { { description: 'Scratch Project', accept: { - 'application/x.scratch.sb3': ['.sb', '.sb2', '.sb3', '.pm'] + 'application/x.scratch.sb3': ['.sb', '.sb2', '.sb3', '.pm', '.pmp'] } } ] From 5d2e420647aa9c2902300ce3cd8d401973974198 Mon Sep 17 00:00:00 2001 From: godslayerakp <74981904+RedMan13@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:03:45 -0700 Subject: [PATCH 018/640] Update target-pane.jsx --- src/containers/target-pane.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 60330c7945c..4589ec8a87d 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -97,7 +97,7 @@ class TargetPane extends React.Component { document.body.appendChild(saveLink); this.props.vm.exportSprite(id).then(content => { - downloadBlob(`${spriteName}.sprite3`, content); + downloadBlob(`${spriteName}.pms`, content); }); } handleSelectSprite (id) { From 0a5da1e84bd8a0667848ce7a10c25161e247f5eb Mon Sep 17 00:00:00 2001 From: godslayerakp <74981904+RedMan13@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:04:20 -0700 Subject: [PATCH 019/640] Update sprite-selector.jsx --- src/components/sprite-selector/sprite-selector.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx index a3d3c24778f..bfc8ce41f82 100644 --- a/src/components/sprite-selector/sprite-selector.jsx +++ b/src/components/sprite-selector/sprite-selector.jsx @@ -120,7 +120,7 @@ const SpriteSelectorComponent = function (props) { title: intl.formatMessage(messages.addSpriteFromFile), img: fileUploadIcon, onClick: onFileUploadClick, - fileAccept: '.svg, .png, .bmp, .jpg, .jpeg, .jfif, .webp, .sprite2, .sprite3, .gif', + fileAccept: '.svg, .png, .bmp, .jpg, .jpeg, .jfif, .webp, .sprite2, .sprite3, .gif, .pms', fileChange: onSpriteUpload, fileInput: spriteFileInput, fileMultiple: true From 99fd478ed93060e3d8603e9159846c3f9c1eddda Mon Sep 17 00:00:00 2001 From: JeremyGamer13 <69337718+JeremyGamer13@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:38:56 -0600 Subject: [PATCH 020/640] missing stuff i think --- src/containers/sb3-downloader.jsx | 2 +- .../penguinmod/extensions/controls_expanded.png | Bin 0 -> 5854 bytes src/lib/sb-file-uploader-hoc.jsx | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/lib/libraries/extensions/penguinmod/extensions/controls_expanded.png diff --git a/src/containers/sb3-downloader.jsx b/src/containers/sb3-downloader.jsx index 775274954cf..608036d41ca 100644 --- a/src/containers/sb3-downloader.jsx +++ b/src/containers/sb3-downloader.jsx @@ -252,7 +252,7 @@ const getProjectFilename = (curTitle, defaultTitle) => { if (!filenameTitle || filenameTitle.length === 0) { filenameTitle = defaultTitle; } - return `${filenameTitle.substring(0, 100)}.pm`; + return `${filenameTitle.substring(0, 100)}.pmp`; }; SB3Downloader.propTypes = { diff --git a/src/lib/libraries/extensions/penguinmod/extensions/controls_expanded.png b/src/lib/libraries/extensions/penguinmod/extensions/controls_expanded.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2937cc45c741596fda83cf3cef208260960928 GIT binary patch literal 5854 zcmZvg2T;>Nv%vpIQP2eG5DZ913{7dF_bL#i7^*<1(kw`kCM8HoKoL+8r1ww)f(ioC zLYF3?h#(-nBOQS+=)8IJ=6#v$B)i$W+uQ%$=I$4bFwg?gKxhB}0O@F}-v)qlNB|)7 zzCcd0v@3p4Bz=%Qx~-)OlzzRkNKz=ARrFN=03Uzxj~yjR&qdra(KsmqPD_E)GSZvu zq!jpr15V0-;}YNq2OJgyy9K~nHn5TjEM)+*slZeUF!37ro(v4X1O}f2Js9Ai2>6`` z%%uUu1fV}2*vthM(}8ayo(wFc12b=d=@eiz5y)0`VJ8JleP7%B5deT2 z&xGvb3%6AOU~1A)S26LkSxs}Zgy@x{1?Iod(NUz1wp*iFJ?Y5wPfiWx$YI7qR^NE5 z3ztgwx+Z$-DW3FX)sS8S;+I4N#V^sm#$OWUJw>r{XrBy=$=e!dPEp=W{wI19AW>w? zjnB4(|1ZxOFEx|v^pjf9b#16_g+xw~#UdkFy8nwL+-%->%0}|KedFJ7BPK3>johR` z&RW3i*=)+A9Q_(G|0`T@=}WXD*PzW8nehh3^#vxPZaW0i8m{l<$F;?pbw1`LR*9RF zY+7O{=v0H#Y|X?yuaa%Zs@ZYPz#rPvSB?0>zRdAOB1B<-u5KGrwWh%q5{7Ta?8F9F z)xTXo!MwLwoiV)8jm)ru{=pX7XfIpGTFG@I*^Q6ae(gc(RED~l>hQWrw30&?>l*c` z(=T5)VlOs7iaJ=Aab0Cjrx)0)?imMZZ9Em#Y?cc&i{L%g=or37Za68r_oHmTWh80< zIl1J;q9lzWyzIC?Qeu+Q6I^Jre_Jr=u*WHP016g(xp{PM;(c(qAvShDuj#aO(g5f5 z+v3BcxbG5%k0ut0b2tAeN?DA>-_`@jWUhOd7A18X8pZfa~=rT5d4;7$mXegA-`d(`h_ zxQRNJJ=ZB;Pob`v7zEk#Bor{IwJgzYAodnOr}B3~-cAojCrxL>uQN$>4FCKjpMofR z$2kRrY`e879nw$29KxiD`WNE|?f(39rWwz_FHR7UeJ`@H+Qhd}SgzEV?ai9FIj$7G z)gLXhe;~Hf%=iO!B>YlNeyewS?RD)P!~M8W`YjW`N9bqmuWb%*jTjo$q)z{4D`5}p zy-MvX1RLG}*jBr^Id+_lKg-`9OQDeGq+-X{L)XSjA{#!`{=uh$XkSGwlGl6qMcl(p zHCEdmrH|cf9^DYTXFYR7rS+Qd#A@af-A;%k^~0EFM@d+%!1*uQon%}0#X!4?!D<`5 z9~A`K@@e;pc69dHlad^xqIrht#^4}YbbdJHv}C<6B0`!;k;DBo?oQ+&4A!)u65ILeXD*i9(4?c{(nlZZ9VC5Ti5VO%ZrSq`kGp!I z9XYvc!Ddmx9R4hE_uR?~yC(g)Dc0hO`Rg5nKQ9IhPTh;fQz#G6M&#ChrB?*i_dN&! z4T@-Hrz0>s5nALehT)=&dqlh%MoS^|7CN9-29npvvfCi5nUf~QO|&tbB!Bk?EHbY1 zIZphV00l~+rblp_fDSq5v?D$9z zrI;?a=kw*VliD%ip_>X1H8!iku)ie9#@;=~$P4!2tJvvq&}xWo<0|*y-N${NIH7kf zarhRFH@?$OF-zBTJF&CG@8|tAWgpCKFVgdTi}6G$$jEW0bY`{PciPz{Cln<9G7@J| zIaum?C8Am3=o4zRq+W?1-}U+N`zP30>Xw(7L6V?i?qEY3!^jOnWk2?1o_z!%)rPoF&;UK7x-m^fJoNwU6Q;5WRq&s+4U4mtBtA*=#xa}wF=Hbh-`tfQ8uTNz#obq&ClX% zF7ioa2-+VF|Y;zt)@{r(NU%`U7tmaOf%c0Ez3)~P8!M?Ts zhC^KZ4aRm?WtM(k+UY{XG(=BrOWeg#zC@X=Lr{#l7JU7)CD&vxxkUQ`nImtutx!&V zq^UYK`+Rv&T7V!A+X2D8;hVF|6@gv^*F4$S1Rve+O6fGodVbpp(QfS!z`BznU~@+3IoI43V2@SaKGg%LC^I` z+brzv4bdM)Y1H6HZWo;MpUFMP_wZe2JTiwpw)#r7;f=)g=WPb)%R7i5<+5e<&iRDIzA)5Y7b zL1z)1uH+DEK6Bm3BBbdBcDckW-5Dg=(PrBc;KM&Qpl;D!ht{w%BpdXBLM^4@?$ zneR=M6tlJ5y(742U;HYKP>1?^ykM(^s#&SqM1K_V34zu;(emCE8AY0K)I}B$UWqccvG?il>X?dbGrxHqPh3|! z@25pFBw#gSRlkSwyKp|CN>k?^LAo2UPJ+hs;o| z9{a>;MV;%J0gIyAwde~{%+$#690FC%ybm~+m(+Y))6`9G4zYH?psS-^j(6c~AlJcy zpaltz*Af%72beBGkE)zt;3@}wQg6RBVLq<3jFLXeNvT}#>7&M@G~i1Y!XR=kT3J*j z6x`DG{n!Ja=S^s^$*qheWs>_?Yy8eJJU=7d3Ti>E?%9$xL3K0vB3m&T$$J>-T^F$8 z8^5O3l@j2>clnbs*N|2ZQp5q5-lu!@Ayz~9qnV#?=0E@~scATVWy^@ACgvMv=X~D| zixZ6H29f5kJ>~MZk^Tq~C!A6f<}nSFP?Jo<)+IY(U9cQPSwfK@uY<-owq;l^h;5(w zm*O+58#>7cW|48T z_Hg1|yW%F}1tgQbZsM~cyg}m(~ZsO*HIG6mrRIZ1%OET9bH=Q}wgqfYwsn;U~ zK2Kx4U^T_`H`lG;1T_Ted~G1`H1>ZYZP~pjJk3%Lplw5UR={q^A0vgqLWP)jA#RKC=2t2D8 zIgsD9zD-`DI`%QM`WF*pg6?ewkZA>xR8)uvo-R#_a*)oU`mcd%(P$M@R3!QhhJyMY?6~%c4<37arCFLROb4cf;ezi)5t29c21it>E)uM<9?K5CeQ>EYv6IwmCNHon?|i>hKei7l0~brS zE0P_D#YDAk5w*+CU#rMah!mt!1Q2zWItsBCK_UDO0%Z;qp3JzWFfgRAgaM$`Lq-&h4u}v+W>k z7aSAF-O?_Z*@t}eF0i|4g9QfQ4C&?O z^ej=&BYT}_-8M#$9prtFy?G2kYiT&Ua_}|)BX{vZzT5?m)1}V804~~VddD} zP4%XqsRC;^F?XOA*x4L&S8Xg+F6hOx<0VmRWm~*))sjS_vt?TaB-_t&Kvf53(H24(T3o zNsoGEeVa$(=M>gPw>f4^qNk&VT&y;8?STC3(I5@;>iuZa6`%?aF`!gjo<4AC-89Yn zey0;VbUy~%>8$3=(QUz+X_gT1L&lVIi0$*s56(OoL*Y&yvvr&ng(B*+|i`3kk6JZJ-0FX;!8Yc*^PVdzv1dL9cfu|*={13`pcxd4t-$qyBXKT zsc~%0%^q|=%eEnUZ|_cN<=EI^Wv6F%nzfx(kM57-+m>UNF7D1Eb?DM zt-|$8Ipx>AdX4;~b_6?2SwEWVN)1=Yz_xzNz!gY$vP@RDN%Nb~JB`wNsvctXG;PQ` zYcbYs-tE2pUaWR7vdeLJaOzNkO}|i$X@5o_Qv$x!!hl6Txy0e_O5cNG-cfGeyVkCr z+j2iAtUq>n`u^P8@o*_#Tb8@(if(b4G#4&B-~9DQWcv6W9t_vzuoxsfpcv;an>ezL zV3oBVf?ul-5sOGfd9bbWS(qEzOilTeNzo&pRPo^o?>9GkzaDZl)r(y@mXT9^OC=p4 z#pF4X4z8@u@!Vev-3&i7vEUy*IU2N+N*LJO_;}Z&i&-IXx%@(=$K3Pc@Pg+G=~MkD zdS6G5(p2Q4_}{37VUY0igLc&xC(Ao_nVl0hRJQ{se_j|uYUMS23Ln2Me`(RxG1 zFVC5Lv@0v~?z*>M#@|voW%r&Y=gziFqb4qO!Y(hVO7v7?HPg@kSMjZ8&>c7z`!X^w zNvU_t+3WZC(C+dIdst7YPlN}KxX>Q>+VSNLjss_eq80TV?i?;!HsUQRbOP`40?Vl= z5HD(VC2>^xmgT>5n|$@?M9^VJMMmalnmRl*#V} z;-!!${!(`>z;U8VjY2|}txmWPxTG@1f`T?fM5|^fbA% zRM}%WFGK8nnrDyyk7erVN>fjqc?2xZY=)K%EAVov+_F&O?eXoxja-D#&f6Z{T)8U- z{4?P;eKWUM_P>3M61DeO?6J}LrSyCe)?{17x7eyHE9Xx62gzK?n9jtt3AsM+pmM_F zqP0?#T7a#hwhaC}6bDCoM2{oc700Zcia Date: Fri, 23 Jun 2023 13:24:48 -0600 Subject: [PATCH 021/640] pull some list monitor fixes --- src/components/monitor/list-monitor-scroller.jsx | 2 +- src/components/monitor/monitor.css | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/monitor/list-monitor-scroller.jsx b/src/components/monitor/list-monitor-scroller.jsx index cfcd6a248b8..0d0e5b73165 100644 --- a/src/components/monitor/list-monitor-scroller.jsx +++ b/src/components/monitor/list-monitor-scroller.jsx @@ -81,7 +81,7 @@ class ListMonitorScroller extends React.Component { Date: Fri, 23 Jun 2023 14:04:04 -0600 Subject: [PATCH 022/640] Create FORKING.md --- FORKING.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 FORKING.md diff --git a/FORKING.md b/FORKING.md new file mode 100644 index 00000000000..a82fa9025bb --- /dev/null +++ b/FORKING.md @@ -0,0 +1,26 @@ +This file contains instructions on what you should do upon forking PenguinMod. + +You don't HAVE to follow this document but it'll prevent a lot of confusion for your users and likely prevent your mod from being blacklisted from our internal servers. + +# Branding changes +Update any labels or text that says "PenguinMod" and put your mod's name. +You shouldn't do this for IDs or anything like that. + +You should also update your credits & privacy policy pages. We didn't work on your mod, you just made an extension of ours. + +Update any links to our github to yours so people can find the source code. Also make sure to update links that you have your own version of like feedback, docs, or packager. + +### The "don't do that" section +Don't remove any credit to Scratch, TurboWarp, or PenguinMod. That's just not cool. +Imagine if someone just forked your copy and pretended they made it all themselves. + +I'm not legal advice but you should probably not change the license or make the public repos you forked into private ones. + +To my knowledge, you can't just do that without some problems. + +# Server changes +Unless you plan on keeping your mod 100% compatible with PenguinMod, please remove the Upload button and any project sharing features. + +Our APIs are only built for PenguinMod and therefore will likely end up breaking in your mod at some point or another. + +We also do not allow projects that only work outside of PenguinMod so if you end up having a lot of uploads from your site, then we'll have to block your site from being able to use the project API. \ No newline at end of file From 1115703c431a97d98bef8f4cd38b8f4280e42c4f Mon Sep 17 00:00:00 2001 From: JeremyGamer13 <69337718+JeremyGamer13@users.noreply.github.com> Date: Sat, 24 Jun 2023 00:53:20 -0600 Subject: [PATCH 023/640] update bug report link to PM discord --- src/components/menu-bar/menu-bar.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index fd7b5d7cb4c..5844beb8702 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -504,14 +504,14 @@ class MenuBar extends React.Component { onRequestClose={this.props.onRequestCloseErrors} > - + - + Date: Sat, 24 Jun 2023 03:00:15 -0600 Subject: [PATCH 024/640] extension library updates --- src/components/gui/gui.jsx | 1 + src/components/library/library.jsx | 50 +++++++++--- .../custom-extension-modal.jsx | 80 ++++++------------- src/containers/extension-library.jsx | 78 +++++++++--------- src/lib/libraries/extension-tags.js | 5 ++ src/lib/libraries/extensions/index.jsx | 27 ++++--- src/lib/libraries/tag-messages.js | 5 ++ 7 files changed, 131 insertions(+), 115 deletions(-) diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 74a0025e998..e7878cef671 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -211,6 +211,7 @@ const GUIComponent = props => { > {usernameModalVisible && } {settingsModalVisible && } + {customExtensionModalVisible && } {telemetryModalVisible ? ( - {tagListPrefix.concat(this.props.tags).map((tagProps, id) => ( - - ))} + {tagListPrefix.concat(this.props.tags).map((tagProps, id) => { + let onclick = this.handleTagClick; + if (tagProps.type === 'divider') { + return (); + } + if (tagProps.type === 'custom') { + onclick = () => { + const api = {}; + api.useTag = this.handleTagClick; + api.close = this.handleClose; + api.select = (id) => { + const items = this.state.data; + for (const item of items) { + if (item.extensionId === id) { + this.handleClose(); + this.props.onItemSelected(item); + return; + }; + } + }; + tagProps.func(api); + }; + } + return ( + + ); + })} } diff --git a/src/components/tw-custom-extension-modal/custom-extension-modal.jsx b/src/components/tw-custom-extension-modal/custom-extension-modal.jsx index aac88b9a1dd..75e1b9ea167 100644 --- a/src/components/tw-custom-extension-modal/custom-extension-modal.jsx +++ b/src/components/tw-custom-extension-modal/custom-extension-modal.jsx @@ -125,61 +125,33 @@ const CustomExtensionModal = props => ( )} - {props.onChangeUnsandboxed ? ( - - - {props.unsandboxed && ( -

- - -

- )} -
- ) : ( - props.unsandboxed ? ( -

- -

- ) : ( -

- -

- ) + {props.onChangeUnsandboxed ? null : ( +

+ +

)} +

+ + +

) : props.restorePoints.length === 0 ? ( -
-
- -
+
+
) : ( -
+
{props.restorePoints.map(restorePoint => ( ( />
-
+ )} - {!props.isLoading && ( + {!props.isLoading && props.onClickLoadLegacy && (
{/* This is going away within a few days */} {/* No reason to bother translating */} @@ -158,13 +227,14 @@ const RestorePointModal = props => ( RestorePointModal.propTypes = { intl: intlShape, + interval: PropTypes.number.isRequired, + onChangeInterval: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, onClickCreate: PropTypes.func.isRequired, onClickDelete: PropTypes.func.isRequired, onClickDeleteAll: PropTypes.func.isRequired, onClickLoad: PropTypes.func.isRequired, - onClickLoadLegacy: PropTypes.func.isRequired, - disabled: PropTypes.bool.isRequired, + onClickLoadLegacy: PropTypes.func, isLoading: PropTypes.bool.isRequired, totalSize: PropTypes.number.isRequired, restorePoints: PropTypes.arrayOf(PropTypes.shape({})), diff --git a/src/containers/tw-restore-point-manager.jsx b/src/containers/tw-restore-point-manager.jsx index 4f05b77d6c1..907f5b58ae2 100644 --- a/src/containers/tw-restore-point-manager.jsx +++ b/src/containers/tw-restore-point-manager.jsx @@ -10,16 +10,18 @@ import {setFileHandle} from '../reducers/tw'; import TWRestorePointModal from '../components/tw-restore-point-modal/restore-point-modal.jsx'; import RestorePointAPI from '../lib/tw-restore-point-api'; import log from '../lib/log'; -import AddonHooks from '../addons/hooks'; +import isScratchDesktop from '../lib/isScratchDesktop'; /* eslint-disable no-alert */ -const AUTOMATIC_INTERVAL = 1000 * 60 * 5; const SAVE_DELAY = 250; const MINIMUM_SAVE_TIME = 750; const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); +// TODO: remove legacy stuff completely after next desktop app update +const shouldRemoveLegacyRestorePoint = () => !isScratchDesktop(); + const messages = defineMessages({ confirmLoad: { defaultMessage: 'You have unsaved changes. Replace existing project?', @@ -50,6 +52,7 @@ class TWRestorePointManager extends React.Component { 'handleClickCreate', 'handleClickDelete', 'handleClickDeleteAll', + 'handleChangeInterval', 'handleClickLoad', 'handleClickLoadLegacy' ]); @@ -57,15 +60,21 @@ class TWRestorePointManager extends React.Component { loading: true, totalSize: 0, restorePoints: [], - error: null + error: null, + wasChanged: props.projectChanged, + interval: RestorePointAPI.readInterval() }; this.timeout = null; } componentDidMount () { - if (this.shouldBeAutosaving()) { + if (this.state.wasChanged) { this.queueRestorePoint(); } + + if (shouldRemoveLegacyRestorePoint()) { + RestorePointAPI.deleteLegacyRestorePoint(); + } } componentWillReceiveProps (nextProps) { @@ -76,35 +85,30 @@ class TWRestorePointManager extends React.Component { restorePoints: [] }); } - } - componentDidUpdate (prevProps) { - if ( - this.props.projectChanged !== prevProps.projectChanged || - this.props.isShowingProject !== prevProps.isShowingProject - ) { - if (this.shouldBeAutosaving()) { - // Project was modified - this.queueRestorePoint(); - } else { - // Project was saved - clearTimeout(this.timeout); - this.timeout = null; - } + if (nextProps.projectChanged && !this.props.projectChanged && !this.state.wasChanged) { + this.setState({ + wasChanged: true + }); } - } - componentWillUnmount () { - clearTimeout(this.timeout); - this.timeout = null; + if (!nextProps.isShowingProject && this.props.isShowingProject) { + this.setState({ + wasChanged: false + }); + } } - shouldBeAutosaving () { - return this.props.projectChanged && this.props.isShowingProject; + componentDidUpdate (prevProps, prevState) { + if (this.state.wasChanged && !prevState.wasChanged) { + this.queueRestorePoint(); + } else if (!this.state.wasChanged && prevState.wasChanged) { + this.cancelQueuedRestorePoint(); + } } - isDisabled () { - return AddonHooks.disableRestorePoints; + componentWillUnmount () { + this.cancelQueuedRestorePoint(); } handleClickCreate () { @@ -216,25 +220,39 @@ class TWRestorePointManager extends React.Component { this._finishLoading(false); } + handleChangeInterval (e) { + const interval = +e.target.value; + RestorePointAPI.setInterval(interval); + this.setState({ + interval + }, () => { + if (this.state.wasChanged) { + this.cancelQueuedRestorePoint(); + this.queueRestorePoint(); + } + }); + } + queueRestorePoint () { - if (this.timeout) { + if (this.timeout || this.state.interval < 0) { return; } this.timeout = setTimeout(() => { this.createRestorePoint(RestorePointAPI.TYPE_AUTOMATIC).then(() => { this.timeout = null; - if (this.shouldBeAutosaving()) { - this.queueRestorePoint(); - } + this.queueRestorePoint(); }); - }, AUTOMATIC_INTERVAL); + }, this.state.interval); } - createRestorePoint (type) { - if (this.isDisabled()) { - return Promise.reject(new Error('Disabled')); + cancelQueuedRestorePoint () { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; } + } + createRestorePoint (type) { if (this.props.isModalVisible) { this.setState({ loading: true @@ -303,8 +321,9 @@ class TWRestorePointManager extends React.Component { onClickDelete={this.handleClickDelete} onClickDeleteAll={this.handleClickDeleteAll} onClickLoad={this.handleClickLoad} - onClickLoadLegacy={this.handleClickLoadLegacy} - disabled={this.isDisabled()} + onClickLoadLegacy={shouldRemoveLegacyRestorePoint() ? null : this.handleClickLoadLegacy} + interval={this.state.interval} + onChangeInterval={this.handleChangeInterval} isLoading={this.state.loading} totalSize={this.state.totalSize} restorePoints={this.state.restorePoints} diff --git a/src/lib/alerts/index.jsx b/src/lib/alerts/index.jsx index b1e2da42516..2ec1a62b359 100644 --- a/src/lib/alerts/index.jsx +++ b/src/lib/alerts/index.jsx @@ -143,7 +143,8 @@ const alerts = [ { alertId: 'saveSuccess', alertType: AlertTypes.INLINE, - clearList: ['saveSuccess', 'saving', 'savingError', 'twSaveToDiskSuccess'], + clearList: ['saveSuccess', 'saving', 'savingError', 'twSaveToDiskSuccess', + 'twCreatingRestorePoint', 'twRestorePointSuccess', 'twRestorePointError'], content: ( openDB().then(db => new Promise((resolve, reject) => }; })); -// We will enable this after a couple days -/* -const deleteLegacyData = () => { +const LEGACY_DATABASE_NAME = 'TW_AutoSave'; +const LEGACY_DATABASE_VERSION = 1; +const LEGACY_STORE_NAME = 'project'; + +const deleteLegacyRestorePoint = () => { try { if (typeof indexedDB !== 'undefined') { - const _request = indexedDB.deleteDatabase('TW_AutoSave'); + const _request = indexedDB.deleteDatabase(LEGACY_DATABASE_NAME); // don't really care what happens to the request at this point } } catch (e) { // ignore } }; -*/ const loadLegacyRestorePoint = () => new Promise((resolve, reject) => { if (typeof indexedDB === 'undefined') { @@ -522,10 +523,6 @@ const loadLegacyRestorePoint = () => new Promise((resolve, reject) => { return; } - const LEGACY_DATABASE_NAME = 'TW_AutoSave'; - const LEGACY_DATABASE_VERSION = 1; - const LEGACY_STORE_NAME = 'project'; - const openRequest = indexedDB.open(LEGACY_DATABASE_NAME, LEGACY_DATABASE_VERSION); openRequest.onerror = () => { reject(new Error(`Error opening DB: ${openRequest.error}`)); @@ -564,6 +561,42 @@ const loadLegacyRestorePoint = () => new Promise((resolve, reject) => { }; }); +const DEFAULT_INTERVAL = 1000 * 60 * 5; +const INTERVAL_STORAGE_KEY = 'tw:restore-point-interval'; + +const readInterval = () => { + try { + const stored = localStorage.getItem(INTERVAL_STORAGE_KEY); + if (stored) { + const number = +stored; + if (Number.isFinite(number)) { + return number; + } + } + + // TODO: this is temporary, remove it after enough has passed for people that care to have migrated + const addonSettings = localStorage.getItem('tw:addons'); + if (addonSettings) { + const parsedAddonSettings = JSON.parse(addonSettings); + const addonObject = parsedAddonSettings['tw-disable-restore-points']; + if (addonObject && addonObject.enabled) { + return -1; + } + } + } catch (e) { + // ignore + } + return DEFAULT_INTERVAL; +}; + +const setInterval = interval => { + try { + localStorage.setItem(INTERVAL_STORAGE_KEY, interval); + } catch (err) { + // ignore + } +}; + export default { TYPE_AUTOMATIC, TYPE_MANUAL, @@ -574,5 +607,8 @@ export default { deleteAllRestorePoints, getThumbnail, loadRestorePoint, - loadLegacyRestorePoint + loadLegacyRestorePoint, + deleteLegacyRestorePoint, + readInterval, + setInterval }; From c0a094bef33719c8c181f7f1e0e229becf00a4bb Mon Sep 17 00:00:00 2001 From: Muffin Date: Sat, 12 Aug 2023 00:12:25 -0500 Subject: [PATCH 140/640] Modify some strings so Transifex handles them properly Our transifex config doesn't do plurals, so we'll just do different strings, which actually makes more sense here in English too --- .../restore-point-modal.jsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/tw-restore-point-modal/restore-point-modal.jsx b/src/components/tw-restore-point-modal/restore-point-modal.jsx index 5957f109d6e..f231d925e9e 100644 --- a/src/components/tw-restore-point-modal/restore-point-modal.jsx +++ b/src/components/tw-restore-point-modal/restore-point-modal.jsx @@ -17,13 +17,19 @@ const messages = defineMessages({ never: { defaultMessage: 'never', id: 'tw.restorePoints.never', - description: 'Part of restore point modal. Appears in a message like "Restore points are created [never v]".' + description: 'Part of restore point modal. Appears as dropdown in context "Restore points are created [never]"' + }, + oneMinute: { + defaultMessage: 'every minute', + id: 'tw.restorePoints.1minute', + // eslint-disable-next-line max-len + description: 'Part of restore point modal. Appears as dropdown in context "Restore points are created [every minute]"' }, minutes: { - defaultMessage: 'every {minutes, plural, one {# minute} other {# minutes}}', + defaultMessage: 'every {n} minutes', id: 'tw.restorePoints.minutes', // eslint-disable-next-line max-len - description: 'Part of restore point modal. Appears in a message like "Restore points are created [every one minute v]".' + description: 'Part of restore point modal. Appears as dropdown in context "Restore points are created [every 5 minutes]". {n} will be replaced with a number greater than 1.' } }); @@ -48,9 +54,11 @@ const IntervalSelector = props => ( > {interval < 0 ? ( props.intl.formatMessage(messages.never) + ) : interval === MINUTE ? ( + props.intl.formatMessage(messages.oneMinute) ) : ( props.intl.formatMessage(messages.minutes, { - minutes: Math.round(interval / MINUTE) + n: Math.round(interval / MINUTE) }) )} @@ -94,7 +102,7 @@ const RestorePointModal = props => ( defaultMessage="Restore points are created {time}." id="tw.restorePoints.intervalOption" // eslint-disable-next-line max-len - description="{time} will be replaced with a dropdown with values such as [every 5 minutes v] and [never v]" + description="{time} will be replaced with a dropdown with values such as [every 5 minutes] and [never]" values={{ time: ( Date: Sat, 12 Aug 2023 11:12:20 -0500 Subject: [PATCH 141/640] Update translations --- .../generated-translations.json | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/lib/tw-translations/generated-translations.json b/src/lib/tw-translations/generated-translations.json index 6cf9be5af16..b4b6096b723 100644 --- a/src/lib/tw-translations/generated-translations.json +++ b/src/lib/tw-translations/generated-translations.json @@ -525,7 +525,6 @@ "tw.restorePoints.confirmLoad": "Tiene cambios sin guardar. ¿Remplazar el proyecto existente?", "tw.restorePoints.deleteAll": "Eliminar Todos", "tw.restorePoints.description": "{APP_NAME} Guarda periódicamente puntos de guardado en su computadora para ayudarlo a recuperar su proyecto olvida guardarlo. Esto es guardado como una última oportunidad de recuperación. Su computadora podría silenciosamente eliminar estos puntos de guardado en cualquier momento. NO confíe en esta función.", - "tw.restorePoints.disabled": "Deshabilite el addon \"Deshabilitar puntos de recuperación\" para rehabilitar creación de puntos de recuperación.", "tw.restorePoints.empty": "No se encontraron puntos de recuperación.", "tw.restorePoints.error": "Error al cargar punto de recuperación: {error}", "tw.restorePoints.loading": "Cargando...", @@ -853,6 +852,7 @@ "tw.alerts.restorePointError": "Impossibile creare il punto di ripristino", "tw.alerts.restorePointSuccess": "Puoi accedere ai punti di ripristino nel menu \"File\"", "tw.alerts.savedToDisk": "Salvato sul computer.", + "tw.aug23downtime": "Gli aggiornamenti previsti di Scratch possono creare problemi a {APP_NAME}", "tw.backpack.rename": "Nuovo nome:", "tw.blocks.PROCEDURES_DOCS": "Come usare il blocco Risultato", "tw.blocks.PROCEDURES_RETURN": "risultato {v}", @@ -961,16 +961,20 @@ "tw.recordVideo.title": "Il progetto vuole registrare il video della tua webcam. Il progetto potrebbe condividere immagini con altri utenti o altri server.", "tw.redirect.dangerous": "Questo sito web non è stato recensito dagli sviluppatori di {APP_NAME}. Può contenere codice pericoloso o dannoso.", "tw.redirect.title": "Il progetto richiede di aprire in questa scheda l'URL:", + "tw.restorePoints.1minute": "ogni minuto", "tw.restorePoints.assets": "{n} risorse", "tw.restorePoints.confirmDelete": "Sei sicuro di voler cancellare \"{projectTitle}\"? Questa operazione non potrà essere annullata.", "tw.restorePoints.confirmDeleteAll": "Sei sicuro di voler cancellare TUTTI i punti di ripristino? Questa operazione non potrà annullata.", "tw.restorePoints.confirmLoad": "Ci sono modifiche non salvate. Rimpiazzare il progetto corrente?", "tw.restorePoints.deleteAll": "Rimuovi Tutto", "tw.restorePoints.description": "{APP_NAME} salva periodicamente dei punti di ripristino sul computer per aiutarti a recuperare il progetto se dimentichi di salvare. Questo sistema fornisce un ultima risorsa per poter recuperare il tuo progetto. il tuo computer potrebbe rimuovere senza preavviso questi punti di interruzione. NON fare affidamento su questa possibilità.", - "tw.restorePoints.disabled": "Disabilit l'addon \"Disabilita punti di interruzione\" per riprendere la creazione di punti di ripristino.", "tw.restorePoints.empty": "Nessun punto di ripristino trovato.", "tw.restorePoints.error": "Errore nel caricamento del punto di ripristino: {error}", + "tw.restorePoints.intervalOption": "I punti di ripristino vengono creati {time}.", "tw.restorePoints.loading": "Caricamento...", + "tw.restorePoints.minutes": "ogni {n} minuti", + "tw.restorePoints.never": "mai", + "tw.restorePoints.off": "Disabilitare i punti di ripristino è rischioso.", "tw.restorePoints.size": "Spazio di archiviazione usato: {size}", "tw.restorePoints.size2": "Costumi e suoni usati da più punti di ripristino vengono archiviati una sola volta.", "tw.restorePoints.title": "Ripristina Punti di Ripristino", @@ -1619,8 +1623,15 @@ "nl": { "tw.alerts.creatingRestorePoint": "Herstelpunt aan het maken...", "tw.alerts.lostPeripheralConnection": "Verbinding verloren met {extensionName}.", + "tw.alerts.restorePointError": "Kon herstelpunt niet maken", + "tw.alerts.restorePointSuccess": "Krijg toegang tot herstelpunten in \"Bestand\"", "tw.alerts.savedToDisk": "Opgeslagen op je computer.", + "tw.aug23downtime": "Aanstaande onderhoud aan Scratch kan delen van {APP_NAME} beïnvloeden", "tw.backpack.rename": "Nieuwe naam:", + "tw.blocks.PROCEDURES_DOCS": "Hoe gebruik ik retourneer?", + "tw.blocks.PROCEDURES_RETURN": "retourneer {v}", + "tw.blocks.PROCEDURES_TO_REPORTER": "Veranderen naar Waardeblok", + "tw.blocks.PROCEDURES_TO_STATEMENT": "Veranderen naar Statement", "tw.blocks.buttonIsDown": "[MOUSE_BUTTON]muisknop ingedrukt?", "tw.blocks.lastKeyPressed": "laatst ingedrukte toets", "tw.blocks.mouseButton.middle": "(1) midden", @@ -1650,6 +1661,8 @@ "tw.customExtensionModal.unsandboxedWarning1": "Extensies laden zonder sandbox is gevaarlijk en moet alleen worden gedaan als je weet wat je doet.", "tw.customExtensionModal.unsandboxedWarning2": "Niet-gesandboxede extensies kunnen je project beschadigen, je instellingen verwijderen, wachtwoorden phishen, en andere slechte zaken. De {APP_NAME}-ontwikkelaars zijn niet verantwoordelijk voor enige resulterende problemen.", "tw.customExtensionModal.untrusted": "Voor veiligheidsredenen worden extensies van niet-vertrouwde URL's altijd geladen met de sandbox.", + "tw.customReporters.description": "Geef Mijn Blokken de mogelijkheid om waarden te hebben en te worden gebruikt als invoer.", + "tw.customReporters.name": "Aangepaste Waardeblokken", "tw.extensionGallery.description": "We hebben voor het gemak een lijst van een paar extensies gemaakt. Op extensions.turbowarp.org kun je er meer vinden.", "tw.extensionGallery.name": "TurboWarp Extensiegalerij", "tw.extensions.incompatible": "Niet compatibel met Scratch.", @@ -1663,6 +1676,8 @@ "tw.footer.donate": "Doneren", "tw.footer.embed": "Invoeging", "tw.footer.parameters": "URL-Parameters", + "tw.geolocate.permission": "Indien toegestaan kan je door je browser worden gevraagd om toegang tot je locatie toe te staan.", + "tw.geolocate.title": "Het project wilt toegang tot je locatie.", "tw.gui.crashMessage.description": "Het spijt ons, maar het lijkt erop dat de pagina is gecrasht. Ververs je pagina om het opnieuw te proberen.", "tw.guiDefaultTitle": "Speel Scratch-projecten sneller af", "tw.home.credit": "Opmerkingen en credits", @@ -1695,6 +1710,7 @@ "tw.menuBar.package": "Project packagen", "tw.menuBar.reportError1": "Sommige scripts konden niet worden gecompileerd.", "tw.menuBar.reportError2": "Dit is een bug. Rapporteer het alsjeblieft.", + "tw.menuBar.restorePoints": "Herstelpunten", "tw.menuBar.saveAs": "Opslaan als {file}", "tw.menuBar.seeInside": "Bekijk van binnen", "tw.notify.permission": "Indien toegestaan kan je door je browser worden gevraagd om notificaties in te schakelen, en toekomstige notificaties worden automatisch toegestaan.", @@ -1715,6 +1731,23 @@ "tw.recordVideo.title": "Het project wilt video van je camera opnemen. Het project kan afbeeldingen delen met andere gebruikers of servers.", "tw.redirect.dangerous": "Deze website is niet beoordeeld door de {APP_NAME}-ontwikkelaars. Het kan gevaarlijke of kwaadaardige code bevatten.", "tw.redirect.title": "Het project wil dit tabblad doorsturen naar de URL:", + "tw.restorePoints.1minute": "elke minuut", + "tw.restorePoints.assets": "{n} onderdelen", + "tw.restorePoints.confirmDelete": "Weet je zeker dat je \"{projectTitle}\" wilt verwijderen? Dit kan niet ongedaan worden gemaakt.", + "tw.restorePoints.confirmDeleteAll": "Weet je zeker dat je ALLE herstelpunten wilt verwijderen? Dit kan niet ongedaan worden gemaakt.", + "tw.restorePoints.confirmLoad": "Je hebt niet-opgeslagen wijzigingen. Wil je het bestaand project vervangen?", + "tw.restorePoints.deleteAll": "Alles Verwijderen", + "tw.restorePoints.description": "{APP_NAME} slaat af en toe herstelpunten op je computer op zodat je je project terug kan halen als je bent vergeten op te slaan. Dit is bedoeld als een laatste redmiddel voor herstel. Je computer kan deze herstelpunten op elk moment verwijderen. Hang NIET af van deze functie.", + "tw.restorePoints.empty": "Geen herstelpunten gevonden.", + "tw.restorePoints.error": "Er is een fout opgetreden bij het laden van herstelpunt: {error}", + "tw.restorePoints.intervalOption": "Herstelpunten worden {time} gemaakt.", + "tw.restorePoints.loading": "Bezig met laden...", + "tw.restorePoints.minutes": "elke {n} minuten", + "tw.restorePoints.never": "nooit", + "tw.restorePoints.off": "Herstelpunten uitschakelen is gevaarlijk.", + "tw.restorePoints.size": "Geschatte gebruikte opslagruimte: {size}", + "tw.restorePoints.size2": "Uiterlijken en geluiden die gebruikt worden door hestelpunten worden maar één keer opgeslagen.", + "tw.restorePoints.title": "Herstelpunten", "tw.saveAs": "Opslaan als...", "tw.saveTo": "Opslaan in {file}", "tw.scratchUnsafeCloud": "Als je deze cloudvariabele maakt, zal het project het limiet van {number} variabelen overschrijden, en sommige variabelen zullen niet goed werken als je het project uploadt naar Scratch.", @@ -2576,6 +2609,7 @@ "tw.menuBar.package": "Packa projektet", "tw.menuBar.reportError1": "Några skript kunde inte kompileras", "tw.menuBar.reportError2": "Detta är en bugg. Snälla rapportera buggen. ", + "tw.menuBar.restorePoints": "Återställspunkter", "tw.menuBar.saveAs": "Spara som {file}", "tw.menuBar.seeInside": "Se inuti", "tw.notify.permission": "Om tillåtet, kommer du kanske att bli frågad om att tillåta notifikationer av din webbläsare, och senare notifikationer kommer att bli automatiskt tillåtna. ", @@ -2596,6 +2630,11 @@ "tw.recordVideo.title": "Projektet vill spela in video från din kamera. Projektet kan dela bilder med andra användare eller servrar.", "tw.redirect.dangerous": "Denna webbsida har inte granskats av utvecklarna {APP_NAME} . Den kan innehålla farlig eller skadlig kod.", "tw.redirect.title": "Projektet will navigera dig till denna webbsida:", + "tw.restorePoints.deleteAll": "Radera allt", + "tw.restorePoints.description": "{APP_NAME} sparar återställspunkter med jämna mellanrum på don dator för att hjälpa återställa projekt om du glömde spara. Detta är avsett som sista utvägen för återhämtning. Din dator kan ta bart återställningspunkterna när som helts. Bero INTE på denna funktion.", + "tw.restorePoints.error": "Ett fel uppstod under laddningen av återställningspunkten: {error}", + "tw.restorePoints.loading": "Laddar...", + "tw.restorePoints.title": "Återställspunkter", "tw.saveAs": "Spara som...", "tw.saveTo": "Spara till {file}", "tw.scratchUnsafeCloud": "Om du gör denna molnvariabel så kommer projektet att överskrida Scratchs gräns av {number} variabler och några variabler kommer inte fungera om du kommer ladda upp projektet till Scratch.", From dac18304414c412b74abd4cc498c4d9fdf32bc58 Mon Sep 17 00:00:00 2001 From: JeremyGamer13 <69337718+JeremyGamer13@users.noreply.github.com> Date: Mon, 14 Aug 2023 23:57:03 -0600 Subject: [PATCH 142/640] Update library.svg --- src/lib/libraries/extensions/penguinmod/library.svg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/libraries/extensions/penguinmod/library.svg b/src/lib/libraries/extensions/penguinmod/library.svg index 3ea1123e6d0..805cda13a48 100644 --- a/src/lib/libraries/extensions/penguinmod/library.svg +++ b/src/lib/libraries/extensions/penguinmod/library.svg @@ -3,31 +3,31 @@ style="mix-blend-mode:normal;fill-rule:nonzero;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;fill:#00c3ff;fill-opacity:1" /> + style="mix-blend-mode:normal;fill-rule:nonzero;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;fill:#00aeff;fill-opacity:1" /> + style="mix-blend-mode:normal;fill-rule:nonzero;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;fill:#00aeff;fill-opacity:1" /> + style="mix-blend-mode:normal;fill-rule:nonzero;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;fill:#00aeff;fill-opacity:1" /> + style="mix-blend-mode:normal;fill-rule:nonzero;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;fill:#00aeff;fill-opacity:1" /> From 7057f92ed634efe8bf10f1c30f95adea6eeeb381 Mon Sep 17 00:00:00 2001 From: Muffin Date: Tue, 15 Aug 2023 17:16:17 -0500 Subject: [PATCH 143/640] Disable dragging restore point delete icon --- src/components/tw-restore-point-modal/restore-point.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/tw-restore-point-modal/restore-point.jsx b/src/components/tw-restore-point-modal/restore-point.jsx index c463ceaaf2b..df77a9013e8 100644 --- a/src/components/tw-restore-point-modal/restore-point.jsx +++ b/src/components/tw-restore-point-modal/restore-point.jsx @@ -135,6 +135,7 @@ class RestorePoint extends React.Component { Delete
From 1c6e353242bf51a868a6ffec634fed457d855cff Mon Sep 17 00:00:00 2001 From: Muffin Date: Tue, 15 Aug 2023 17:47:21 -0500 Subject: [PATCH 144/640] Add support for .ogg, .flac, .aac, and others Instead of throwing an error, unknown audio formats will be interpreted by the system's AudioContext and encoded as a wav instead. File sizes are pretty bad, but this is still net improvement. https://github.com/ScratchAddons/ScratchAddons/issues/6515#issuecomment-1679721804 --- src/containers/sound-tab.jsx | 2 +- src/lib/file-uploader.js | 7 ++++++- src/lib/tw-convert-audio-wav.js | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/lib/tw-convert-audio-wav.js diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index f849284c0d9..1b6b9cd87d6 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -230,7 +230,7 @@ class SoundTab extends React.Component { title: intl.formatMessage(messages.fileUploadSound), img: fileUploadIcon, onClick: this.handleFileUploadClick, - fileAccept: '.wav, .mp3', + fileAccept: '.wav, .mp3, .ogg, .flac, .aac', fileChange: this.handleSoundUpload, fileInput: this.setFileInput, fileMultiple: true diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index c57e3ff58bd..b7e50bd1884 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -3,6 +3,7 @@ import randomizeSpritePosition from './randomize-sprite-position.js'; import bmpConverter from './bmp-converter'; import gifDecoder from './gif-decoder'; import fixSVG from './tw-svg-fixer'; +import convertAudioToWav from './tw-convert-audio-wav.js'; /** * Extract the file name given a string of the form fileName + ext @@ -211,7 +212,11 @@ const soundUpload = function (fileData, fileType, storage, handleSound, handleEr break; } default: - handleError(`Encountered unexpected file type: ${fileType}`); + convertAudioToWav(fileData) + .then(fixed => { + soundUpload(fixed, 'audio/wav', storage, handleSound, handleError); + }) + .catch(handleError); return; } diff --git a/src/lib/tw-convert-audio-wav.js b/src/lib/tw-convert-audio-wav.js new file mode 100644 index 00000000000..36e1ba4ab33 --- /dev/null +++ b/src/lib/tw-convert-audio-wav.js @@ -0,0 +1,21 @@ +import SharedAudioContext from './audio/shared-audio-context'; +import WavEncoder from 'wav-encoder'; + +const convertAudioToWav = fileData => { + /** @type {AudioContext} */ + const audioContext = new SharedAudioContext(); + + return audioContext.decodeAudioData(fileData) + .then(decodedData => { + const channels = []; + for (let i = 0; i < decodedData.numberOfChannels; i++) { + channels.push(decodedData.getChannelData(i)); + } + return WavEncoder.encode({ + sampleRate: decodedData.sampleRate, + channelData: channels + }); + }); +}; + +export default convertAudioToWav; From 533cff90774f051de584be97d44230700370100a Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Wed, 16 Aug 2023 10:46:39 -0500 Subject: [PATCH 145/640] Add custom font management modal (#789) --- src/components/gui/gui.jsx | 4 + src/components/tw-fonts-modal/add-button.jsx | 25 +++ .../tw-fonts-modal/add-custom-font.jsx | 199 ++++++++++++++++++ .../tw-fonts-modal/add-system-font.jsx | 114 ++++++++++ src/components/tw-fonts-modal/custom.svg | 2 + src/components/tw-fonts-modal/delete.svg | 1 + src/components/tw-fonts-modal/export.svg | 2 + .../tw-fonts-modal/font-fallback.jsx | 85 ++++++++ src/components/tw-fonts-modal/font-name.jsx | 63 ++++++ .../tw-fonts-modal/font-playground.jsx | 46 ++++ src/components/tw-fonts-modal/fonts-modal.css | 190 +++++++++++++++++ src/components/tw-fonts-modal/fonts-modal.jsx | 158 ++++++++++++++ .../tw-fonts-modal/load-temporary-font.jsx | 47 +++++ src/components/tw-fonts-modal/manage-font.jsx | 116 ++++++++++ src/components/tw-fonts-modal/system.svg | 2 + src/containers/gui.jsx | 1 + src/containers/paint-editor-wrapper.jsx | 32 ++- src/containers/tw-fonts-modal.jsx | 114 ++++++++++ src/reducers/modals.js | 14 +- 19 files changed, 1209 insertions(+), 6 deletions(-) create mode 100644 src/components/tw-fonts-modal/add-button.jsx create mode 100644 src/components/tw-fonts-modal/add-custom-font.jsx create mode 100644 src/components/tw-fonts-modal/add-system-font.jsx create mode 100644 src/components/tw-fonts-modal/custom.svg create mode 100644 src/components/tw-fonts-modal/delete.svg create mode 100644 src/components/tw-fonts-modal/export.svg create mode 100644 src/components/tw-fonts-modal/font-fallback.jsx create mode 100644 src/components/tw-fonts-modal/font-name.jsx create mode 100644 src/components/tw-fonts-modal/font-playground.jsx create mode 100644 src/components/tw-fonts-modal/fonts-modal.css create mode 100644 src/components/tw-fonts-modal/fonts-modal.jsx create mode 100644 src/components/tw-fonts-modal/load-temporary-font.jsx create mode 100644 src/components/tw-fonts-modal/manage-font.jsx create mode 100644 src/components/tw-fonts-modal/system.svg create mode 100644 src/containers/tw-fonts-modal.jsx diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index a49afd3a242..010f5dbc18d 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -36,6 +36,7 @@ import TWSettingsModal from '../../containers/tw-settings-modal.jsx'; import TWSecurityManager from '../../containers/tw-security-manager.jsx'; import TWCustomExtensionModal from '../../containers/tw-custom-extension-modal.jsx'; import TWRestorePointManager from '../../containers/tw-restore-point-manager.jsx'; +import TWFontsModal from '../../containers/tw-fonts-modal.jsx'; import layout, {STAGE_SIZE_MODES} from '../../lib/layout-constants'; import {resolveStageSize} from '../../lib/screen-utils'; @@ -145,6 +146,7 @@ const GUIComponent = props => { usernameModalVisible, settingsModalVisible, customExtensionModalVisible, + fontsModalVisible, vm, ...componentProps } = omit(props, 'dispatch'); @@ -172,6 +174,7 @@ const GUIComponent = props => { {usernameModalVisible && } {settingsModalVisible && } {customExtensionModalVisible && } + {fontsModalVisible && } ); @@ -498,6 +501,7 @@ GUIComponent.propTypes = { usernameModalVisible: PropTypes.bool, settingsModalVisible: PropTypes.bool, customExtensionModalVisible: PropTypes.bool, + fontsModalVisible: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired }; GUIComponent.defaultProps = { diff --git a/src/components/tw-fonts-modal/add-button.jsx b/src/components/tw-fonts-modal/add-button.jsx new file mode 100644 index 00000000000..51175a17f46 --- /dev/null +++ b/src/components/tw-fonts-modal/add-button.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {FormattedMessage} from 'react-intl'; +import styles from './fonts-modal.css'; + +const AddButton = props => ( + +); + +AddButton.propTypes = { + onClick: PropTypes.func.isRequired, + disabled: PropTypes.bool +}; + +export default AddButton; diff --git a/src/components/tw-fonts-modal/add-custom-font.jsx b/src/components/tw-fonts-modal/add-custom-font.jsx new file mode 100644 index 00000000000..3d5c54743db --- /dev/null +++ b/src/components/tw-fonts-modal/add-custom-font.jsx @@ -0,0 +1,199 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl'; +import bindAll from 'lodash.bindall'; +import styles from './fonts-modal.css'; +import LoadTemporaryFont from './load-temporary-font.jsx'; +import FontName from './font-name.jsx'; +import FontPlayground from './font-playground.jsx'; +import FontFallback from './font-fallback.jsx'; +import AddButton from './add-button.jsx'; + +const messages = defineMessages({ + error: { + defaultMessage: 'Failed to read font file: {error}', + description: 'Part of font management modal. Appears when a font from a local file could not be read.', + id: 'tw.fonts.readError' + } +}); + +export const FONT_FORMATS = [ + 'ttf', + 'otf', + 'woff', + 'woff2' +]; + +const formatFontName = filename => { + // Remove file extension + const idx = filename.indexOf('.'); + if (idx !== -1) { + filename = filename.substring(0, idx); + } + return filename; +}; + +const getDataFormat = filename => { + const parts = filename.split('.'); + const extension = parts[parts.length - 1]; + if (FONT_FORMATS.includes(extension)) { + return extension; + } + // We'll just guess + return 'ttf'; +}; + +class AddCustomFont extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleChangeFile', + 'handleChangeName', + 'handleChangeFallback', + 'handleFinish' + ]); + this.state = { + file: null, + url: null, + name: '', + format: '', + fallback: FontFallback.DEFAULT, + loading: false + }; + } + + componentWillUnmount () { + URL.revokeObjectURL(this.state.url); + } + + handleChangeFile (e) { + const file = e.target.files[0] || null; + if (file) { + this.setState({ + file, + name: formatFontName(file.name), + format: getDataFormat(file.name), + url: URL.createObjectURL(file) + }); + } else { + URL.revokeObjectURL(this.state.url); + this.setState({ + file, + name: null, + url: null + }); + } + } + + handleChangeName (name) { + this.setState({ + name + }); + } + + handleChangeFallback (fallback) { + this.setState({ + fallback + }); + } + + handleFinish () { + this.setState({ + loading: true + }); + + const fr = new FileReader(); + fr.onload = () => { + const data = new Uint8Array(fr.result); + const storage = this.props.fontManager.runtime.storage; + const asset = storage.createAsset( + storage.AssetType.Font, + this.state.format, + data, + null, + true + ); + this.props.fontManager.addCustomFont(this.state.name, this.state.fallback, asset); + this.props.onClose(); + }; + fr.onerror = () => { + // eslint-disable-next-line no-alert + alert(this.props.intl.formatMessage(messages.error), { + error: fr.error + }); + + this.setState({ + loading: false + }); + }; + fr.readAsArrayBuffer(this.state.file); + } + + render () { + return ( + +

+ +

+ + `.${ext}`).join(',')} + readOnly={this.state.loading} + /> + + {this.state.file && ( + +

+ +

+ + + + {family => ( + + )} + + +
+ )} + + +
+ ); + } +} + +AddCustomFont.propTypes = { + intl: intlShape, + fontManager: PropTypes.shape({ + addCustomFont: PropTypes.func, + runtime: PropTypes.shape({ + // eslint-disable-next-line react/forbid-prop-types + storage: PropTypes.any + }) + }), + onClose: PropTypes.func.isRequired +}; + +export default injectIntl(AddCustomFont); diff --git a/src/components/tw-fonts-modal/add-system-font.jsx b/src/components/tw-fonts-modal/add-system-font.jsx new file mode 100644 index 00000000000..c06fd83784e --- /dev/null +++ b/src/components/tw-fonts-modal/add-system-font.jsx @@ -0,0 +1,114 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {FormattedMessage} from 'react-intl'; +import bindAll from 'lodash.bindall'; +import FontName from './font-name.jsx'; +import FontPlayground from './font-playground.jsx'; +import FontFallback from './font-fallback.jsx'; +import AddButton from './add-button.jsx'; + +class AddSystemFont extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleChangeName', + 'handleChangeFallback', + 'handleFinish' + ]); + this.state = { + name: '', + fallback: FontFallback.DEFAULT, + localFonts: null + }; + } + + componentDidMount () { + // Chrome-only API + if (typeof queryLocalFonts === 'function') { + // eslint-disable-next-line no-undef + queryLocalFonts().then(fonts => { + const uniqueFamilies = [...new Set(fonts.map(i => i.family))]; + this.setState({ + localFonts: uniqueFamilies + }); + }); + } + } + + handleChangeName (name) { + this.setState({ + name + }); + } + + handleChangeFallback (fallback) { + this.setState({ + fallback + }); + } + + handleFinish () { + this.props.fontManager.addSystemFont(this.state.name, this.state.fallback); + this.props.onClose(); + } + + render () { + return ( + +

+ +

+ + {/* TODO: datalist is pretty bad at this. we should try our own dropdown? */} + + {this.state.localFonts && ( + + {this.state.localFonts.map(family => ( + + )} + + {this.state.name && ( + + + + + + )} + + +
+ ); + } +} + +AddSystemFont.propTypes = { + fontManager: PropTypes.shape({ + addSystemFont: PropTypes.func.isRequired, + hasFont: PropTypes.func.isRequired + }).isRequired, + onClose: PropTypes.func.isRequired +}; + +export default AddSystemFont; diff --git a/src/components/tw-fonts-modal/custom.svg b/src/components/tw-fonts-modal/custom.svg new file mode 100644 index 00000000000..61392fe37dc --- /dev/null +++ b/src/components/tw-fonts-modal/custom.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/components/tw-fonts-modal/delete.svg b/src/components/tw-fonts-modal/delete.svg new file mode 100644 index 00000000000..b3a1e5974b0 --- /dev/null +++ b/src/components/tw-fonts-modal/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/tw-fonts-modal/export.svg b/src/components/tw-fonts-modal/export.svg new file mode 100644 index 00000000000..61604982c07 --- /dev/null +++ b/src/components/tw-fonts-modal/export.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/components/tw-fonts-modal/font-fallback.jsx b/src/components/tw-fonts-modal/font-fallback.jsx new file mode 100644 index 00000000000..6bafd606174 --- /dev/null +++ b/src/components/tw-fonts-modal/font-fallback.jsx @@ -0,0 +1,85 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {FormattedMessage} from 'react-intl'; +import styles from './fonts-modal.css'; +import VanillaFonts from 'scratch-paint/src/lib/fonts'; +import bindAll from 'lodash.bindall'; +import classNames from 'classnames'; + +class FontFallbackButton extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleClick' + ]); + } + + handleClick () { + this.props.onClick(this.props.family); + } + + formatName () { + // keep in sync with scratch-paint/src/containers/font-dropdown.jsx + switch (this.props.family) { + case VanillaFonts.CHINESE: + return '中文'; + case VanillaFonts.KOREAN: + return '한국어'; + case VanillaFonts.JAPANESE: + return '日本語'; + } + return this.props.family; + } + + render () { + return ( + + ); + } +} + +FontFallbackButton.propTypes = { + family: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + selected: PropTypes.bool.isRequired +}; + +const FontFallback = props => ( +
+
+ +
+ +
+ {Object.values(VanillaFonts).map(family => ( + + ))} +
+
+); + +FontFallback.DEFAULT = 'Sans Serif'; + +FontFallback.propTypes = { + fallback: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +}; + +export default FontFallback; diff --git a/src/components/tw-fonts-modal/font-name.jsx b/src/components/tw-fonts-modal/font-name.jsx new file mode 100644 index 00000000000..22108be0fce --- /dev/null +++ b/src/components/tw-fonts-modal/font-name.jsx @@ -0,0 +1,63 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './fonts-modal.css'; +import bindAll from 'lodash.bindall'; + +class FontName extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleChange', + 'handleFlush', + 'handleKeyPress' + ]); + } + + handleChange (e) { + this.props.onChange(e.target.value); + } + + handleFlush () { + this.props.onChange(this.props.fontManager.getSafeName(this.props.name)); + } + + handleKeyPress (e) { + if (e.key === 'Enter') { + this.handleFlush(); + e.target.blur(); + } + } + + render () { + const { + /* eslint-disable no-unused-vars */ + name, + onChange, + fontManager, + /* eslint-enable no-unused-vars */ + ...props + } = this.props; + return ( + + ); + } +} + +FontName.propTypes = { + name: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + fontManager: PropTypes.shape({ + getSafeName: PropTypes.func.isRequired + }).isRequired +}; + +export default FontName; diff --git a/src/components/tw-fonts-modal/font-playground.jsx b/src/components/tw-fonts-modal/font-playground.jsx new file mode 100644 index 00000000000..328fc23e879 --- /dev/null +++ b/src/components/tw-fonts-modal/font-playground.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './fonts-modal.css'; +import bindAll from 'lodash.bindall'; +import classNames from 'classnames'; + +// TODO: is this something to localize? +const QUICK_BROWN_FOX = 'The quick brown fox jumps over the lazy dog.'; + +class FontPlayground extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleChange' + ]); + this.state = { + value: QUICK_BROWN_FOX + }; + } + + handleChange (e) { + this.setState({ + value: e.target.value + }); + } + + render () { + return ( +