diff --git a/package.json b/package.json index 8d21e3484f..f825aa04ca 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@types/koa__multer": "2.0.2", "@types/koa__router": "8.0.2", "@types/markdown-it": "10.0.3", + "@types/matter-js": "0.14.7", "@types/mocha": "7.0.2", "@types/node": "14.0.22", "@types/node-fetch": "2.5.7", @@ -175,6 +176,7 @@ "lookup-dns-cache": "2.1.0", "markdown-it": "11.0.1", "markdown-it-anchor": "6.0.1", + "matter-js": "0.14.2", "mocha": "8.2.1", "moji": "0.5.1", "ms": "2.1.2", diff --git a/src/client/pages/about-misskey.vue b/src/client/pages/about-misskey.vue index 4e0e74bfa1..77922fc966 100644 --- a/src/client/pages/about-misskey.vue +++ b/src/client/pages/about-misskey.vue @@ -1,50 +1,54 @@ <template> -<FormBase class="znqjceqz"> - <section class="_formItem"> - <div class="_formPanel" style="text-align: center; padding: 16px;"> - <img src="/assets/icons/512.png" alt="" style="display: block; width: 100px; margin: 0 auto; border-radius: 16px;" ref="icon"/> - <div style="margin-top: 0.75em;">Misskey</div> - <div style="opacity: 0.5;">v{{ version }}</div> - </div> - </section> - <section class="_formItem" style="text-align: center; padding: 0 16px;"> - {{ $t('_aboutMisskey.about') }} - </section> - <FormGroup> - <FormLink to="https://github.com/syuilo/misskey" external> - <template #icon><Fa :icon="faCode"/></template> - {{ $t('_aboutMisskey.source') }} - <template #suffix>GitHub</template> - </FormLink> - <FormLink to="https://crowdin.com/project/misskey" external> - <template #icon><Fa :icon="faLanguage"/></template> - {{ $t('_aboutMisskey.translation') }} - <template #suffix>Crowdin</template> - </FormLink> - <FormLink to="https://www.patreon.com/syuilo" external> - <template #icon><Fa :icon="faHandHoldingMedical"/></template> - {{ $t('_aboutMisskey.donate') }} - <template #suffix>Patreon</template> - </FormLink> - </FormGroup> - <FormGroup> - <template #label>{{ $t('_aboutMisskey.contributors') }}</template> - <FormLink to="https://github.com/syuilo" external>@syuilo</FormLink> - <FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink> - <FormLink to="https://github.com/mei23" external>@mei23</FormLink> - <FormLink to="https://github.com/acid-chicken" external>@acid-chicken</FormLink> - <FormLink to="https://github.com/tamaina" external>@tamaina</FormLink> - <FormLink to="https://github.com/rinsuki" external>@rinsuki</FormLink> - <FormLink to="https://github.com/Xeltica" external>@Xeltica</FormLink> - <FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink> - <template #caption><MkLink url="https://github.com/syuilo/misskey/graphs/contributors">{{ $t('_aboutMisskey.allContributors') }}</MkLink></template> - </FormGroup> - <FormGroup> - <template #label><Mfm text="[jelly ❤]"/> {{ $t('_aboutMisskey.patrons') }}</template> - <FormKeyValueView v-for="patron in patrons" :key="patron"><template #key>{{ patron }}</template></FormKeyValueView> - <template #caption>{{ $t('_aboutMisskey.morePatrons') }}</template> - </FormGroup> -</FormBase> +<div style="overflow: hidden;"> + <FormBase class="znqjceqz"> + <div id="debug"></div> + <section class="_formItem"> + <div class="_formPanel" style="text-align: center; padding: 16px;" ref="about"> + <img src="/assets/icons/512.png" alt="" style="display: block; width: 100px; margin: 0 auto; border-radius: 16px;" ref="icon" @load="iconLoaded" draggable="false"/> + <div style="margin: 0.75em auto 0 auto; width: max-content;">Misskey</div> + <div style="margin: 0 auto; opacity: 0.5; width: max-content;">v{{ version }}</div> + <span v-for="emoji in easterEggEmojis" :key="emoji.emoji" class="_physics_circle_" :style="{ position: 'absolute', top: emoji.top, left: emoji.left, userSelect: 'none' }"><MkEmoji style="pointer-events: none; font-size: 24px; width: 24px;" :emoji="emoji.emoji" :custom-emojis="$store.state.instance.meta.emojis" :is-reaction="false" :normal="true" :no-style="true"/></span> + </div> + </section> + <section class="_formItem" style="text-align: center; padding: 0 16px;" @click="gravity"> + {{ $t('_aboutMisskey.about') }} + </section> + <FormGroup> + <FormLink to="https://github.com/syuilo/misskey" external> + <template #icon><Fa :icon="faCode"/></template> + {{ $t('_aboutMisskey.source') }} + <template #suffix>GitHub</template> + </FormLink> + <FormLink to="https://crowdin.com/project/misskey" external> + <template #icon><Fa :icon="faLanguage"/></template> + {{ $t('_aboutMisskey.translation') }} + <template #suffix>Crowdin</template> + </FormLink> + <FormLink to="https://www.patreon.com/syuilo" external> + <template #icon><Fa :icon="faHandHoldingMedical"/></template> + {{ $t('_aboutMisskey.donate') }} + <template #suffix>Patreon</template> + </FormLink> + </FormGroup> + <FormGroup> + <template #label>{{ $t('_aboutMisskey.contributors') }}</template> + <FormLink to="https://github.com/syuilo" external>@syuilo</FormLink> + <FormLink to="https://github.com/AyaMorisawa" external>@AyaMorisawa</FormLink> + <FormLink to="https://github.com/mei23" external>@mei23</FormLink> + <FormLink to="https://github.com/acid-chicken" external>@acid-chicken</FormLink> + <FormLink to="https://github.com/tamaina" external>@tamaina</FormLink> + <FormLink to="https://github.com/rinsuki" external>@rinsuki</FormLink> + <FormLink to="https://github.com/Xeltica" external>@Xeltica</FormLink> + <FormLink to="https://github.com/u1-liquid" external>@u1-liquid</FormLink> + <template #caption><MkLink url="https://github.com/syuilo/misskey/graphs/contributors">{{ $t('_aboutMisskey.allContributors') }}</MkLink></template> + </FormGroup> + <FormGroup> + <template #label><Mfm text="[jelly ❤]"/> {{ $t('_aboutMisskey.patrons') }}</template> + <FormKeyValueView v-for="patron in patrons" :key="patron"><template #key>{{ patron }}</template></FormKeyValueView> + <template #caption>{{ $t('_aboutMisskey.morePatrons') }}</template> + </FormGroup> + </FormBase> +</div> </template> <script lang="ts"> @@ -57,6 +61,7 @@ import FormBase from '@/components/form/base.vue'; import FormGroup from '@/components/form/group.vue'; import FormKeyValueView from '@/components/form/key-value-view.vue'; import MkLink from '@/components/link.vue'; +import { physics } from '@/scripts/physics.ts'; import * as os from '@/os'; const patrons = [ @@ -115,10 +120,24 @@ export default defineComponent({ }, version, patrons, + easterEggReady: false, + easterEggEmojis: [], + easterEggEngine: null, faInfoCircle, faCode, faLanguage, faHandHoldingMedical, } }, + created() { + const emojis = this.$store.state.settings.reactions; + for (let i = 0; i < 32; i++) { + this.easterEggEmojis.push({ + top: -(32 + (Math.random() * 256)) + 'px', + left: (Math.random() * 99) + '%', + emoji: emojis[Math.floor(Math.random() * emojis.length)], + }); + } + }, + mounted() { VanillaTilt.init(this.$refs.icon, { max: 30, @@ -127,6 +146,27 @@ export default defineComponent({ speed: 1000, }); }, + + beforeUnmount() { + if (this.easterEggEngine) { + this.easterEggEngine.stop(); + } + }, + + methods: { + iconLoaded() { + this.$nextTick(() => { + this.easterEggReady = true; + }); + }, + + gravity() { + if (!this.easterEggReady) return; + this.easterEggReady = false; + this.$refs.icon.vanillaTilt.destroy(); + this.easterEggEngine = physics(this.$refs.about); + } + } }); </script> diff --git a/src/client/scripts/physics.ts b/src/client/scripts/physics.ts new file mode 100644 index 0000000000..c7f6b44a9f --- /dev/null +++ b/src/client/scripts/physics.ts @@ -0,0 +1,168 @@ +import Matter from 'matter-js'; + +export function physics(container: HTMLElement) { + const containerWidth = container.offsetWidth; + const containerHeight = container.offsetHeight; + const containerCenterX = containerWidth / 2; + + // サイズ固定化(要らないかも?) + container.style.position = 'relative'; + container.style.boxSizing = 'border-box'; + container.style.width = `${containerWidth}px`; + container.style.height = `${containerHeight}px`; + + // create engine + const engine = Matter.Engine.create(); + const world = engine.world; + + // create renderer + const render = Matter.Render.create({ + engine: engine, + //element: document.getElementById('debug'), + options: { + width: containerWidth, + height: containerHeight, + background: 'transparent', // transparent to hide + wireframeBackground: 'transparent', // transparent to hide + hasBounds: false, + enabled: true, + wireframes: false, + showSleeping: true, + showDebug: false, + showBroadphase: false, + showBounds: false, + showVelocity: false, + showCollisions: false, + showAxes: false, + showPositions: false, + showAngleIndicator: false, + showIds: false, + showShadows: false + } + }); + + // Disable to hide debug + Matter.Render.run(render); + + // create runner + const runner = Matter.Runner.create(); + Matter.Runner.run(runner, engine); + + // add walls + const wallopts = { + isStatic: true, + restitution: 0.2, + friction: 1 + }; + const groundopts = { + isStatic: true, + restitution: 0.1, + friction: 2 + }; + + const groundThickness = 100; + const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, groundopts); + //const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts); + //const wallLeft = Matter.Bodies.rectangle(-50, window.innerHeight/2, 100, window.innerHeight, wallopts); + + Matter.World.add(world, [ + ground, + //wallRight, + //wallLeft, + ]); + + const objEls = Array.from(container.children); + const objs = []; + for (const objEl of objEls) { + let obj; + if (objEl.classList.contains('_physics_circle_')) { + obj = Matter.Bodies.circle( + objEl.offsetLeft + (objEl.offsetWidth / 2), + objEl.offsetTop + (objEl.offsetHeight / 2), + Math.max(objEl.offsetWidth, objEl.offsetHeight) / 2, + { + restitution: 0.1, + friction: 4, + frictionAir: 0, + frictionStatic: 50, + density: 100, + } + ); + } else { + const style = window.getComputedStyle(objEl); + obj = Matter.Bodies.rectangle( + objEl.offsetLeft + (objEl.offsetWidth / 2), + objEl.offsetTop + (objEl.offsetHeight / 2), + objEl.offsetWidth, + objEl.offsetHeight, + { + restitution: 0.1, + friction: 4, + frictionAir: 0, + frictionStatic: 50, + density: 100, + chamfer: { radius: parseInt(style.borderRadius, 10) }, + } + ); + } + objEl.id = obj.id; + objs.push(obj); + } + + Matter.World.add(engine.world, objs); + + // Add mouse control + + const mouse = Matter.Mouse.create(container); + const mouseConstraint = Matter.MouseConstraint.create(engine, { + mouse: mouse, + constraint: { + stiffness: 1, + render: { + visible: false + } + } + }); + + Matter.World.add(engine.world, mouseConstraint); + + // keep the mouse in sync with rendering + render.mouse = mouse; + + for (const objEl of objEls) { + objEl.style.position = `absolute`; + objEl.style.top = 0; + objEl.style.left = 0; + objEl.style.margin = 0; + objEl.style.userSelect = 'none'; + objEl.style.willChange = 'transform'; + } + + window.requestAnimationFrame(update); + + let stop = false; + + function update() { + for (const objEl of objEls) { + const obj = objs.find(obj => obj.id.toString() === objEl.id.toString()); + if (obj == null) continue; + + const x = (obj.position.x - objEl.offsetWidth / 2); + const y = (obj.position.y - objEl.offsetHeight / 2); + const angle = obj.angle; + + objEl.style.transform = `translate(${x}px, ${y}px) rotate(${angle}rad)`; + } + + if (!stop) { + window.requestAnimationFrame(update); + } + } + + return { + stop: () => { + stop = true; + Matter.Runner.stop(runner); + } + }; +} diff --git a/yarn.lock b/yarn.lock index e56214ff75..6919719791 100644 --- a/yarn.lock +++ b/yarn.lock @@ -661,6 +661,11 @@ "@types/mdurl" "*" highlight.js "^9.7.0" +"@types/matter-js@0.14.7": + version "0.14.7" + resolved "https://registry.yarnpkg.com/@types/matter-js/-/matter-js-0.14.7.tgz#b816f1e7b441ee7499027f9566e4fb5baea637b3" + integrity sha512-HLUhVTUoKsibpPZ2tCzoCC/f/UYRWPP9WCOUh5F61BlrUESFV5fE7eKq/CmdoEGkNrLW9v407zYlfrTc9hnGIw== + "@types/mdurl@*": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" @@ -6116,6 +6121,11 @@ material-colors@^1.0.0: resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== +matter-js@0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.14.2.tgz#8169af9e06fdc356ba9e72b49624eb329839883b" + integrity sha512-3ttVT8cJlQnGRjBa8MyVrGyvGmnmOkZ3YsyemIw+KwEEdVi70mo32FH1Eta2b3GfdDJFbMDRqyMQt4heNKBUEA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"