mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-04-30 20:25:50 +02:00
Compare commits
10 Commits
copilot/fi
...
2025.10.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
259dd34b26 | ||
|
|
cf81406fae | ||
|
|
42f230f223 | ||
|
|
2e07e50bb4 | ||
|
|
d203e1a446 | ||
|
|
4988719a2e | ||
|
|
f0380f2d1c | ||
|
|
130d065d0c | ||
|
|
7b41fddf54 | ||
|
|
aafd8b6bf7 |
@@ -18,6 +18,8 @@
|
|||||||
- ウォーターマークを敷き詰めると上下左右反転した画像/文字が表示される問題を修正
|
- ウォーターマークを敷き詰めると上下左右反転した画像/文字が表示される問題を修正
|
||||||
- ウォーターマークを回転させた際に画面からはみ出た部分を考慮できるように
|
- ウォーターマークを回転させた際に画面からはみ出た部分を考慮できるように
|
||||||
- Fix: 投票が終了した後に投票結果が正しく表示されない問題を修正
|
- Fix: 投票が終了した後に投票結果が正しく表示されない問題を修正
|
||||||
|
- Fix: ダークモードの同期が機能しない場合がある問題を修正
|
||||||
|
- Fix: iOSで動画の圧縮を行うと音声トラックが失われる問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: 管理者/モデレーターはファイルのアップロード制限をバイパスするように
|
- Enhance: 管理者/モデレーターはファイルのアップロード制限をバイパスするように
|
||||||
|
|||||||
@@ -3201,6 +3201,7 @@ _watermarkEditor:
|
|||||||
title: "Edit Watermark"
|
title: "Edit Watermark"
|
||||||
cover: "Cover everything"
|
cover: "Cover everything"
|
||||||
repeat: "spread all over"
|
repeat: "spread all over"
|
||||||
|
preserveBoundingRect: "Adjust to prevent overflow when rotating"
|
||||||
opacity: "Opacity"
|
opacity: "Opacity"
|
||||||
scale: "Size"
|
scale: "Size"
|
||||||
text: "Text"
|
text: "Text"
|
||||||
|
|||||||
@@ -298,6 +298,7 @@ uploadFromUrlMayTakeTime: "Membutuhkan beberapa waktu hingga pengunggahan selesa
|
|||||||
explore: "Jelajahi"
|
explore: "Jelajahi"
|
||||||
messageRead: "Telah dibaca"
|
messageRead: "Telah dibaca"
|
||||||
noMoreHistory: "Tidak ada sejarah lagi"
|
noMoreHistory: "Tidak ada sejarah lagi"
|
||||||
|
startChat: "Kirim pesan"
|
||||||
nUsersRead: "Dibaca oleh {n}"
|
nUsersRead: "Dibaca oleh {n}"
|
||||||
agreeTo: "Saya setuju kepada {0}"
|
agreeTo: "Saya setuju kepada {0}"
|
||||||
agree: "Setuju"
|
agree: "Setuju"
|
||||||
@@ -510,6 +511,7 @@ emojiStyle: "Gaya emoji"
|
|||||||
native: "Native"
|
native: "Native"
|
||||||
menuStyle: "Gaya menu"
|
menuStyle: "Gaya menu"
|
||||||
style: "Gaya"
|
style: "Gaya"
|
||||||
|
popup: "Pemunculan"
|
||||||
showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
|
showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
|
||||||
showReactionsCount: "Lihat jumlah reaksi dalam catatan"
|
showReactionsCount: "Lihat jumlah reaksi dalam catatan"
|
||||||
noHistory: "Tidak ada riwayat"
|
noHistory: "Tidak ada riwayat"
|
||||||
@@ -566,6 +568,7 @@ showFixedPostForm: "Tampilkan form posting di atas lini masa"
|
|||||||
showFixedPostFormInChannel: "Tampilkan form posting di atas lini masa (Kanal)"
|
showFixedPostFormInChannel: "Tampilkan form posting di atas lini masa (Kanal)"
|
||||||
withRepliesByDefaultForNewlyFollowed: "Termasuk balasan dari pengguna baru yang diikuti pada lini masa secara bawaan"
|
withRepliesByDefaultForNewlyFollowed: "Termasuk balasan dari pengguna baru yang diikuti pada lini masa secara bawaan"
|
||||||
newNoteRecived: "Kamu mendapat catatan baru"
|
newNoteRecived: "Kamu mendapat catatan baru"
|
||||||
|
newNote: "Catatan baru"
|
||||||
sounds: "Bunyi"
|
sounds: "Bunyi"
|
||||||
sound: "Bunyi"
|
sound: "Bunyi"
|
||||||
listen: "Dengarkan"
|
listen: "Dengarkan"
|
||||||
@@ -1028,6 +1031,7 @@ permissionDeniedError: "Operasi ditolak"
|
|||||||
permissionDeniedErrorDescription: "Akun ini tidak memiliki izin untuk melakukan aksi ini."
|
permissionDeniedErrorDescription: "Akun ini tidak memiliki izin untuk melakukan aksi ini."
|
||||||
preset: "Prasetel"
|
preset: "Prasetel"
|
||||||
selectFromPresets: "Pilih dari prasetel"
|
selectFromPresets: "Pilih dari prasetel"
|
||||||
|
custom: "Penyesuaian"
|
||||||
achievements: "Pencapaian"
|
achievements: "Pencapaian"
|
||||||
gotInvalidResponseError: "Respon peladen tidak valid"
|
gotInvalidResponseError: "Respon peladen tidak valid"
|
||||||
gotInvalidResponseErrorDescription: "Peladen tidak dapat dijangkau atau sedang dalam perawatan. Mohon coba lagi nanti."
|
gotInvalidResponseErrorDescription: "Peladen tidak dapat dijangkau atau sedang dalam perawatan. Mohon coba lagi nanti."
|
||||||
@@ -1110,6 +1114,7 @@ preservedUsernamesDescription: "Daftar nama pengguna yang dicadangkan dipisah de
|
|||||||
createNoteFromTheFile: "Buat catatan dari berkas ini"
|
createNoteFromTheFile: "Buat catatan dari berkas ini"
|
||||||
archive: "Arsipkan"
|
archive: "Arsipkan"
|
||||||
archived: "Diarsipkan"
|
archived: "Diarsipkan"
|
||||||
|
unarchive: "Batalkan pengarsipan"
|
||||||
channelArchiveConfirmTitle: "Yakin untuk mengarsipkan {name}?"
|
channelArchiveConfirmTitle: "Yakin untuk mengarsipkan {name}?"
|
||||||
channelArchiveConfirmDescription: "Kanal yang diarsipkan tidak akan muncul pada daftar kanal atau hasil pencarian. Postingan baru juga tidak dapat ditambahkan lagi."
|
channelArchiveConfirmDescription: "Kanal yang diarsipkan tidak akan muncul pada daftar kanal atau hasil pencarian. Postingan baru juga tidak dapat ditambahkan lagi."
|
||||||
thisChannelArchived: "Kanal ini telah diarsipkan."
|
thisChannelArchived: "Kanal ini telah diarsipkan."
|
||||||
@@ -1251,6 +1256,7 @@ noDescription: "Tidak ada deskripsi"
|
|||||||
alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti"
|
alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti"
|
||||||
inquiry: "Hubungi kami"
|
inquiry: "Hubungi kami"
|
||||||
tryAgain: "Silahkan coba lagi."
|
tryAgain: "Silahkan coba lagi."
|
||||||
|
sensitiveMediaRevealConfirm: "Media sensitif. Apakah ingin melihat?"
|
||||||
createdLists: "Senarai yang dibuat"
|
createdLists: "Senarai yang dibuat"
|
||||||
createdAntennas: "Antena yang dibuat"
|
createdAntennas: "Antena yang dibuat"
|
||||||
fromX: "Dari {x}"
|
fromX: "Dari {x}"
|
||||||
@@ -1258,21 +1264,43 @@ noteOfThisUser: "Catatan oleh pengguna ini"
|
|||||||
clipNoteLimitExceeded: "Klip ini tak bisa ditambahi lagi catatan."
|
clipNoteLimitExceeded: "Klip ini tak bisa ditambahi lagi catatan."
|
||||||
performance: "Kinerja"
|
performance: "Kinerja"
|
||||||
modified: "Diubah"
|
modified: "Diubah"
|
||||||
|
discard: "Buang"
|
||||||
thereAreNChanges: "Ada {n} perubahan"
|
thereAreNChanges: "Ada {n} perubahan"
|
||||||
|
signinWithPasskey: "Masuk dengan kunci sandi"
|
||||||
|
unknownWebAuthnKey: "Kunci sandi tidak terdaftar."
|
||||||
|
passkeyVerificationFailed: "Verifikasi kunci sandi gagal."
|
||||||
|
passkeyVerificationSucceededButPasswordlessLoginDisabled: "Verifikasi kunci sandi berhasil, namun pemasukan tanpa sandi dinonaktifkan."
|
||||||
|
messageToFollower: "Pesan kepada pengikut"
|
||||||
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
|
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
|
||||||
|
lockdown: "Kuncitara"
|
||||||
|
noName: "Tidak ada nama"
|
||||||
|
skip: "Lewati"
|
||||||
|
paste: "Tempel"
|
||||||
|
emojiPalette: "Palet emoji"
|
||||||
postForm: "Buat catatan"
|
postForm: "Buat catatan"
|
||||||
information: "Informasi"
|
information: "Informasi"
|
||||||
|
chat: "Obrolan"
|
||||||
|
directMessage: "Obrolan pengguna"
|
||||||
|
right: "Kanan"
|
||||||
|
bottom: "Bawah"
|
||||||
|
top: "Atas"
|
||||||
|
advice: "Saran"
|
||||||
inMinutes: "menit"
|
inMinutes: "menit"
|
||||||
inDays: "hari"
|
inDays: "hari"
|
||||||
widgets: "Widget"
|
widgets: "Widget"
|
||||||
_chat:
|
_chat:
|
||||||
invitations: "Undang"
|
invitations: "Undang"
|
||||||
|
history: "Riwayat obrolan"
|
||||||
noHistory: "Tidak ada riwayat"
|
noHistory: "Tidak ada riwayat"
|
||||||
members: "Anggota"
|
members: "Anggota"
|
||||||
home: "Beranda"
|
home: "Beranda"
|
||||||
send: "Kirim"
|
send: "Kirim"
|
||||||
|
chatWithThisUser: "Obrolan pengguna"
|
||||||
_settings:
|
_settings:
|
||||||
webhook: "Webhook"
|
webhook: "Webhook"
|
||||||
|
contentsUpdateFrequency: "Frekuensi pembaruan konten"
|
||||||
|
_preferencesProfile:
|
||||||
|
profileName: "Nama profil"
|
||||||
_abuseUserReport:
|
_abuseUserReport:
|
||||||
accept: "Setuju"
|
accept: "Setuju"
|
||||||
reject: "Tolak"
|
reject: "Tolak"
|
||||||
@@ -1966,6 +1994,7 @@ _sfx:
|
|||||||
noteMy: "Catatan (Saya)"
|
noteMy: "Catatan (Saya)"
|
||||||
notification: "Notifikasi"
|
notification: "Notifikasi"
|
||||||
reaction: "Ketika memilih reaksi"
|
reaction: "Ketika memilih reaksi"
|
||||||
|
chatMessage: "Obrolan pengguna"
|
||||||
_soundSettings:
|
_soundSettings:
|
||||||
driveFile: "Menggunakan berkas audio dalam Drive"
|
driveFile: "Menggunakan berkas audio dalam Drive"
|
||||||
driveFileWarn: "Pilih berkas audio dari Drive"
|
driveFileWarn: "Pilih berkas audio dari Drive"
|
||||||
@@ -2168,6 +2197,7 @@ _widgets:
|
|||||||
chooseList: "Pilih daftar"
|
chooseList: "Pilih daftar"
|
||||||
clicker: "Pengeklik"
|
clicker: "Pengeklik"
|
||||||
birthdayFollowings: "Pengguna yang merayakan hari ulang tahunnya hari ini"
|
birthdayFollowings: "Pengguna yang merayakan hari ulang tahunnya hari ini"
|
||||||
|
chat: "Obrolan pengguna"
|
||||||
_cw:
|
_cw:
|
||||||
hide: "Sembunyikan"
|
hide: "Sembunyikan"
|
||||||
show: "Lihat konten"
|
show: "Lihat konten"
|
||||||
@@ -2416,6 +2446,7 @@ _deck:
|
|||||||
mentions: "Sebutan"
|
mentions: "Sebutan"
|
||||||
direct: "Langsung"
|
direct: "Langsung"
|
||||||
roleTimeline: "Lini masa peran"
|
roleTimeline: "Lini masa peran"
|
||||||
|
chat: "Obrolan pengguna"
|
||||||
_dialog:
|
_dialog:
|
||||||
charactersExceeded: "Kamu telah melebihi batas karakter maksimum! Saat ini pada {current} dari {max}."
|
charactersExceeded: "Kamu telah melebihi batas karakter maksimum! Saat ini pada {current} dari {max}."
|
||||||
charactersBelow: "Kamu berada di bawah batas minimum karakter! Saat ini pada {current} dari {min}."
|
charactersBelow: "Kamu berada di bawah batas minimum karakter! Saat ini pada {current} dari {min}."
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.10.1-beta.0",
|
"version": "2025.10.1-beta.3",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -39,13 +39,18 @@ for (let i = 0; i < emojilist.length; i++) {
|
|||||||
|
|
||||||
export const emojiCharByCategory = _charGroupByCategory;
|
export const emojiCharByCategory = _charGroupByCategory;
|
||||||
|
|
||||||
export function getUnicodeEmoji(char: string): UnicodeEmojiDef | string {
|
export function getUnicodeEmojiOrNull(char: string): UnicodeEmojiDef | null {
|
||||||
// Colorize it because emojilist.json assumes that
|
// Colorize it because emojilist.json assumes that
|
||||||
return unicodeEmojisMap.get(colorizeEmoji(char))
|
return unicodeEmojisMap.get(colorizeEmoji(char))
|
||||||
// カラースタイル絵文字がjsonに無い場合はテキストスタイル絵文字にフォールバックする
|
// カラースタイル絵文字がjsonに無い場合はテキストスタイル絵文字にフォールバックする
|
||||||
?? unicodeEmojisMap.get(char)
|
?? unicodeEmojisMap.get(char)
|
||||||
// それでも見つからない場合はそのまま返す(絵文字情報がjsonに無い場合、このフォールバックが無いとレンダリングに失敗する)
|
// それでも見つからない場合はnullを返す
|
||||||
?? char;
|
?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUnicodeEmoji(char: string): UnicodeEmojiDef | string {
|
||||||
|
// 絵文字が見つからない場合はそのまま返す(絵文字情報がjsonに無い場合、このフォールバックが無いとレンダリングに失敗する)
|
||||||
|
return getUnicodeEmojiOrNull(char) ?? char;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSupportedEmoji(char: string): boolean {
|
export function isSupportedEmoji(char: string): boolean {
|
||||||
|
|||||||
@@ -69,9 +69,6 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
|||||||
if (lastVersion !== version) {
|
if (lastVersion !== version) {
|
||||||
miLocalStorage.setItem('lastVersion', version);
|
miLocalStorage.setItem('lastVersion', version);
|
||||||
|
|
||||||
// テーマリビルドするため
|
|
||||||
miLocalStorage.removeItem('theme');
|
|
||||||
|
|
||||||
try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
|
try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
|
||||||
if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
|
if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
|
||||||
isClientUpdated = true;
|
isClientUpdated = true;
|
||||||
@@ -176,7 +173,7 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
applyTheme(theme);
|
applyTheme(theme);
|
||||||
}, { immediate: isSafeMode || miLocalStorage.getItem('theme') == null });
|
}, { immediate: true });
|
||||||
|
|
||||||
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
|
||||||
|
|
||||||
@@ -195,14 +192,6 @@ export async function common(createVue: () => Promise<App<Element>>) {
|
|||||||
applyTheme(theme ?? defaultLightTheme);
|
applyTheme(theme ?? defaultLightTheme);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (!isSafeMode) {
|
|
||||||
if (prefer.s.darkTheme && store.s.darkMode) {
|
|
||||||
if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
|
|
||||||
} else if (prefer.s.lightTheme && !store.s.darkMode) {
|
|
||||||
if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchInstanceMetaPromise.then(() => {
|
fetchInstanceMetaPromise.then(() => {
|
||||||
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア
|
||||||
|
|||||||
111
packages/frontend/src/components/MkAnimBg.fragment.glsl
Normal file
111
packages/frontend/src/components/MkAnimBg.fragment.glsl
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
vec3 mod289(vec3 x) {
|
||||||
|
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 mod289(vec2 x) {
|
||||||
|
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 permute(vec3 x) {
|
||||||
|
return mod289(((x*34.0)+1.0)*x);
|
||||||
|
}
|
||||||
|
|
||||||
|
float snoise(vec2 v) {
|
||||||
|
const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
|
||||||
|
|
||||||
|
vec2 i = floor(v + dot(v, C.yy));
|
||||||
|
vec2 x0 = v - i + dot(i, C.xx);
|
||||||
|
|
||||||
|
vec2 i1;
|
||||||
|
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||||
|
vec4 x12 = x0.xyxy + C.xxzz;
|
||||||
|
x12.xy -= i1;
|
||||||
|
|
||||||
|
i = mod289(i);
|
||||||
|
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
|
||||||
|
|
||||||
|
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
||||||
|
m = m*m;
|
||||||
|
m = m*m;
|
||||||
|
|
||||||
|
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||||
|
vec3 h = abs(x) - 0.5;
|
||||||
|
vec3 ox = floor(x + 0.5);
|
||||||
|
vec3 a0 = x - ox;
|
||||||
|
|
||||||
|
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
|
||||||
|
|
||||||
|
vec3 g;
|
||||||
|
g.x = a0.x * x0.x + h.x * x0.y;
|
||||||
|
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||||
|
return 130.0 * dot(m, g);
|
||||||
|
}
|
||||||
|
|
||||||
|
in vec2 in_uv;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform vec2 u_resolution;
|
||||||
|
uniform float u_spread;
|
||||||
|
uniform float u_speed;
|
||||||
|
uniform float u_warp;
|
||||||
|
uniform float u_focus;
|
||||||
|
uniform float u_itensity;
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
float circle(in vec2 _pos, in vec2 _origin, in float _radius) {
|
||||||
|
float SPREAD = 0.7 * u_spread;
|
||||||
|
float SPEED = 0.00055 * u_speed;
|
||||||
|
float WARP = 1.5 * u_warp;
|
||||||
|
float FOCUS = 1.15 * u_focus;
|
||||||
|
|
||||||
|
vec2 dist = _pos - _origin;
|
||||||
|
|
||||||
|
float distortion = snoise(vec2(
|
||||||
|
_pos.x * 1.587 * WARP + u_time * SPEED * 0.5,
|
||||||
|
_pos.y * 1.192 * WARP + u_time * SPEED * 0.3
|
||||||
|
)) * 0.5 + 0.5;
|
||||||
|
|
||||||
|
float feather = 0.01 + SPREAD * pow(distortion, FOCUS);
|
||||||
|
|
||||||
|
return 1.0 - smoothstep(
|
||||||
|
_radius - (_radius * feather),
|
||||||
|
_radius + (_radius * feather),
|
||||||
|
dot( dist, dist ) * 4.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 green = vec3(1.0) - vec3(153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0);
|
||||||
|
vec3 purple = vec3(1.0) - vec3(195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0);
|
||||||
|
vec3 orange = vec3(1.0) - vec3(255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0);
|
||||||
|
|
||||||
|
float ratio = u_resolution.x / u_resolution.y;
|
||||||
|
|
||||||
|
vec2 uv = vec2(in_uv.x, in_uv.y / ratio) * 0.5 + 0.5;
|
||||||
|
|
||||||
|
vec3 color = vec3(0.0);
|
||||||
|
|
||||||
|
float greenMix = snoise(in_uv * 1.31 + u_time * 0.8 * 0.00017) * 0.5 + 0.5;
|
||||||
|
float purpleMix = snoise(in_uv * 1.26 + u_time * 0.8 * -0.0001) * 0.5 + 0.5;
|
||||||
|
float orangeMix = snoise(in_uv * 1.34 + u_time * 0.8 * 0.00015) * 0.5 + 0.5;
|
||||||
|
|
||||||
|
float alphaOne = 0.35 + 0.65 * pow(snoise(vec2(u_time * 0.00012, uv.x)) * 0.5 + 0.5, 1.2);
|
||||||
|
float alphaTwo = 0.35 + 0.65 * pow(snoise(vec2((u_time + 1561.0) * 0.00014, uv.x )) * 0.5 + 0.5, 1.2);
|
||||||
|
float alphaThree = 0.35 + 0.65 * pow(snoise(vec2((u_time + 3917.0) * 0.00013, uv.x )) * 0.5 + 0.5, 1.2);
|
||||||
|
|
||||||
|
color += vec3(circle(uv, vec2(0.22 + sin(u_time * 0.000201) * 0.06, 0.80 + cos(u_time * 0.000151) * 0.06), 0.15)) * alphaOne * (purple * purpleMix + orange * orangeMix);
|
||||||
|
color += vec3(circle(uv, vec2(0.90 + cos(u_time * 0.000166) * 0.06, 0.42 + sin(u_time * 0.000138) * 0.06), 0.18)) * alphaTwo * (green * greenMix + purple * purpleMix);
|
||||||
|
color += vec3(circle(uv, vec2(0.19 + sin(u_time * 0.000112) * 0.06, 0.25 + sin(u_time * 0.000192) * 0.06), 0.09)) * alphaThree * (orange * orangeMix);
|
||||||
|
|
||||||
|
color *= u_itensity + 1.0 * pow(snoise(vec2(in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009)) * 0.5 + 0.5, 2.0);
|
||||||
|
|
||||||
|
vec3 inverted = vec3(1.0) - color;
|
||||||
|
out_color = vec4(color, max(max(color.x, color.y), color.z));
|
||||||
|
}
|
||||||
15
packages/frontend/src/components/MkAnimBg.vertex.glsl
Normal file
15
packages/frontend/src/components/MkAnimBg.vertex.glsl
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#version 300 es
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
in vec2 position;
|
||||||
|
uniform vec2 u_scale;
|
||||||
|
out vec2 in_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
in_uv = position / u_scale;
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
|
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
|
||||||
import isChromatic from 'chromatic/isChromatic';
|
import isChromatic from 'chromatic/isChromatic';
|
||||||
|
import vertexShaderSource from './MkAnimBg.vertex.glsl';
|
||||||
|
import fragmentShaderSource from './MkAnimBg.fragment.glsl';
|
||||||
import { initShaderProgram } from '@/utility/webgl.js';
|
import { initShaderProgram } from '@/utility/webgl.js';
|
||||||
|
|
||||||
const canvasEl = useTemplateRef('canvasEl');
|
const canvasEl = useTemplateRef('canvasEl');
|
||||||
@@ -42,126 +44,7 @@ onMounted(() => {
|
|||||||
const positionBuffer = gl.createBuffer();
|
const positionBuffer = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
|
|
||||||
const shaderProgram = initShaderProgram(gl, `#version 300 es
|
const shaderProgram = initShaderProgram(gl, vertexShaderSource, fragmentShaderSource);
|
||||||
in vec2 position;
|
|
||||||
uniform vec2 u_scale;
|
|
||||||
out vec2 in_uv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(position, 0.0, 1.0);
|
|
||||||
in_uv = position / u_scale;
|
|
||||||
}
|
|
||||||
`, `#version 300 es
|
|
||||||
precision mediump float;
|
|
||||||
|
|
||||||
vec3 mod289(vec3 x) {
|
|
||||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 mod289(vec2 x) {
|
|
||||||
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 permute(vec3 x) {
|
|
||||||
return mod289(((x*34.0)+1.0)*x);
|
|
||||||
}
|
|
||||||
|
|
||||||
float snoise(vec2 v) {
|
|
||||||
const vec4 C = vec4(0.211324865405187,
|
|
||||||
0.366025403784439,
|
|
||||||
-0.577350269189626,
|
|
||||||
0.024390243902439);
|
|
||||||
|
|
||||||
vec2 i = floor(v + dot(v, C.yy) );
|
|
||||||
vec2 x0 = v - i + dot(i, C.xx);
|
|
||||||
|
|
||||||
vec2 i1;
|
|
||||||
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
|
||||||
vec4 x12 = x0.xyxy + C.xxzz;
|
|
||||||
x12.xy -= i1;
|
|
||||||
|
|
||||||
i = mod289(i);
|
|
||||||
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
|
|
||||||
+ i.x + vec3(0.0, i1.x, 1.0 ));
|
|
||||||
|
|
||||||
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
|
|
||||||
m = m*m ;
|
|
||||||
m = m*m ;
|
|
||||||
|
|
||||||
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
|
||||||
vec3 h = abs(x) - 0.5;
|
|
||||||
vec3 ox = floor(x + 0.5);
|
|
||||||
vec3 a0 = x - ox;
|
|
||||||
|
|
||||||
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
|
|
||||||
|
|
||||||
vec3 g;
|
|
||||||
g.x = a0.x * x0.x + h.x * x0.y;
|
|
||||||
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
|
||||||
return 130.0 * dot(m, g);
|
|
||||||
}
|
|
||||||
|
|
||||||
in vec2 in_uv;
|
|
||||||
uniform float u_time;
|
|
||||||
uniform vec2 u_resolution;
|
|
||||||
uniform float u_spread;
|
|
||||||
uniform float u_speed;
|
|
||||||
uniform float u_warp;
|
|
||||||
uniform float u_focus;
|
|
||||||
uniform float u_itensity;
|
|
||||||
out vec4 out_color;
|
|
||||||
|
|
||||||
float circle( in vec2 _pos, in vec2 _origin, in float _radius ) {
|
|
||||||
float SPREAD = 0.7 * u_spread;
|
|
||||||
float SPEED = 0.00055 * u_speed;
|
|
||||||
float WARP = 1.5 * u_warp;
|
|
||||||
float FOCUS = 1.15 * u_focus;
|
|
||||||
|
|
||||||
vec2 dist = _pos - _origin;
|
|
||||||
|
|
||||||
float distortion = snoise( vec2(
|
|
||||||
_pos.x * 1.587 * WARP + u_time * SPEED * 0.5,
|
|
||||||
_pos.y * 1.192 * WARP + u_time * SPEED * 0.3
|
|
||||||
) ) * 0.5 + 0.5;
|
|
||||||
|
|
||||||
float feather = 0.01 + SPREAD * pow( distortion, FOCUS );
|
|
||||||
|
|
||||||
return 1.0 - smoothstep(
|
|
||||||
_radius - ( _radius * feather ),
|
|
||||||
_radius + ( _radius * feather ),
|
|
||||||
dot( dist, dist ) * 4.0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec3 green = vec3( 1.0 ) - vec3( 153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0 );
|
|
||||||
vec3 purple = vec3( 1.0 ) - vec3( 195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0 );
|
|
||||||
vec3 orange = vec3( 1.0 ) - vec3( 255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0 );
|
|
||||||
|
|
||||||
float ratio = u_resolution.x / u_resolution.y;
|
|
||||||
|
|
||||||
vec2 uv = vec2( in_uv.x, in_uv.y / ratio ) * 0.5 + 0.5;
|
|
||||||
|
|
||||||
vec3 color = vec3( 0.0 );
|
|
||||||
|
|
||||||
float greenMix = snoise( in_uv * 1.31 + u_time * 0.8 * 0.00017 ) * 0.5 + 0.5;
|
|
||||||
float purpleMix = snoise( in_uv * 1.26 + u_time * 0.8 * -0.0001 ) * 0.5 + 0.5;
|
|
||||||
float orangeMix = snoise( in_uv * 1.34 + u_time * 0.8 * 0.00015 ) * 0.5 + 0.5;
|
|
||||||
|
|
||||||
float alphaOne = 0.35 + 0.65 * pow( snoise( vec2( u_time * 0.00012, uv.x ) ) * 0.5 + 0.5, 1.2 );
|
|
||||||
float alphaTwo = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 1561.0 ) * 0.00014, uv.x ) ) * 0.5 + 0.5, 1.2 );
|
|
||||||
float alphaThree = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 3917.0 ) * 0.00013, uv.x ) ) * 0.5 + 0.5, 1.2 );
|
|
||||||
|
|
||||||
color += vec3( circle( uv, vec2( 0.22 + sin( u_time * 0.000201 ) * 0.06, 0.80 + cos( u_time * 0.000151 ) * 0.06 ), 0.15 ) ) * alphaOne * ( purple * purpleMix + orange * orangeMix );
|
|
||||||
color += vec3( circle( uv, vec2( 0.90 + cos( u_time * 0.000166 ) * 0.06, 0.42 + sin( u_time * 0.000138 ) * 0.06 ), 0.18 ) ) * alphaTwo * ( green * greenMix + purple * purpleMix );
|
|
||||||
color += vec3( circle( uv, vec2( 0.19 + sin( u_time * 0.000112 ) * 0.06, 0.25 + sin( u_time * 0.000192 ) * 0.06 ), 0.09 ) ) * alphaThree * ( orange * orangeMix );
|
|
||||||
|
|
||||||
color *= u_itensity + 1.0 * pow( snoise( vec2( in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009 ) ) * 0.5 + 0.5, 2.0 );
|
|
||||||
|
|
||||||
vec3 inverted = vec3( 1.0 ) - color;
|
|
||||||
out_color = vec4(color, max(max(color.x, color.y), color.z));
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
if (shaderProgram == null) return;
|
if (shaderProgram == null) return;
|
||||||
|
|
||||||
gl.useProgram(shaderProgram);
|
gl.useProgram(shaderProgram);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
|
import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { getUnicodeEmoji } from '@@/js/emojilist.js';
|
import { getUnicodeEmojiOrNull } from '@@/js/emojilist.js';
|
||||||
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
|
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
|
||||||
import type { MenuItem } from '@/types/menu';
|
import type { MenuItem } from '@/types/menu';
|
||||||
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
||||||
@@ -60,11 +60,11 @@ const buttonEl = useTemplateRef('buttonEl');
|
|||||||
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
|
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
|
||||||
|
|
||||||
const canToggle = computed(() => {
|
const canToggle = computed(() => {
|
||||||
const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction);
|
const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmojiOrNull(props.reaction);
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
//return !props.reaction.match(/@\w/) && $i && emoji && checkReactionPermissions($i, props.note, emoji);
|
//return !props.reaction.match(/@\w/) && $i && emoji && checkReactionPermissions($i, props.note, emoji);
|
||||||
return !props.reaction.match(/@\w/) && $i && emoji;
|
return props.reaction.match(/@\w/) == null && $i != null && emoji != null;
|
||||||
});
|
});
|
||||||
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
|
||||||
const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.');
|
const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.');
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Awaitable } from '@/types/misc.js';
|
||||||
|
|
||||||
export type SuperMenuDef = {
|
export type SuperMenuDef = {
|
||||||
title?: string;
|
title?: string;
|
||||||
items: ({
|
items: ({
|
||||||
@@ -80,7 +82,7 @@ export type SuperMenuDef = {
|
|||||||
text: string;
|
text: string;
|
||||||
danger?: boolean;
|
danger?: boolean;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
action: (ev: MouseEvent) => void | Promise<void>;
|
action: (ev: MouseEvent) => Awaitable<void>;
|
||||||
} | {
|
} | {
|
||||||
type?: 'link';
|
type?: 'link';
|
||||||
to: string;
|
to: string;
|
||||||
|
|||||||
@@ -634,7 +634,9 @@ export function useUploader(options: {
|
|||||||
bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
|
bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
|
||||||
},
|
},
|
||||||
audio: {
|
audio: {
|
||||||
bitrate: item.compressionLevel === 1 ? mediabunny.QUALITY_VERY_HIGH : item.compressionLevel === 2 ? mediabunny.QUALITY_MEDIUM : mediabunny.QUALITY_VERY_LOW,
|
// Explicitly keep audio (don't discard) and copy it if possible
|
||||||
|
// without re-encoding to avoid WebCodecs limitations on iOS Safari
|
||||||
|
discard: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { throttle } from 'throttle-debounce';
|
import { throttle } from 'throttle-debounce';
|
||||||
import type { Directive } from 'vue';
|
import type { Directive } from 'vue';
|
||||||
|
import type { Awaitable } from '@/types/misc.js';
|
||||||
|
|
||||||
interface HTMLElementWithObserver extends HTMLElement {
|
interface HTMLElementWithObserver extends HTMLElement {
|
||||||
_observer_?: IntersectionObserver;
|
_observer_?: IntersectionObserver;
|
||||||
@@ -31,4 +32,4 @@ export const appearDirective = {
|
|||||||
unmounted(src) {
|
unmounted(src) {
|
||||||
if (src._observer_) src._observer_.disconnect();
|
if (src._observer_) src._observer_.disconnect();
|
||||||
},
|
},
|
||||||
} as Directive<HTMLElementWithObserver, () => void>;
|
} as Directive<HTMLElementWithObserver, (() => Awaitable<void>) | null | undefined>;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const start = isTouchUsing ? 'touchstart' : 'mouseenter';
|
|||||||
const end = isTouchUsing ? 'touchend' : 'mouseleave';
|
const end = isTouchUsing ? 'touchend' : 'mouseleave';
|
||||||
|
|
||||||
type TooltipDirectiveState = {
|
type TooltipDirectiveState = {
|
||||||
text: string;
|
text: string | null | undefined;
|
||||||
_close: null | (() => void);
|
_close: null | (() => void);
|
||||||
showTimer: number | null;
|
showTimer: number | null;
|
||||||
hideTimer: number | null;
|
hideTimer: number | null;
|
||||||
@@ -53,6 +53,7 @@ export const tooltipDirective = {
|
|||||||
|
|
||||||
if (binding.arg === 'dialog') {
|
if (binding.arg === 'dialog') {
|
||||||
el.addEventListener('click', (ev) => {
|
el.addEventListener('click', (ev) => {
|
||||||
|
if (binding.value == null) return;
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
alert({
|
alert({
|
||||||
@@ -128,4 +129,4 @@ export const tooltipDirective = {
|
|||||||
if (self.checkTimer) window.clearTimeout(self.checkTimer);
|
if (self.checkTimer) window.clearTimeout(self.checkTimer);
|
||||||
self.close();
|
self.close();
|
||||||
},
|
},
|
||||||
} as Directive<TooltipDirectiveElement, string, TooltipDirectiveModifiers, TooltipDirectiveArg>;
|
} as Directive<TooltipDirectiveElement, string | null | undefined, TooltipDirectiveModifiers, TooltipDirectiveArg>;
|
||||||
|
|||||||
@@ -131,4 +131,4 @@ export const userPreviewDirective = {
|
|||||||
if (self == null) return;
|
if (self == null) return;
|
||||||
self.preview.detach();
|
self.preview.detach();
|
||||||
},
|
},
|
||||||
} as Directive<UserPreviewDirectiveElement, string | Misskey.entities.UserDetailed>;
|
} as Directive<UserPreviewDirectiveElement, string | Misskey.entities.UserDetailed | null | undefined>;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export type Keys = (
|
|||||||
'bootloaderLocales' |
|
'bootloaderLocales' |
|
||||||
'theme' |
|
'theme' |
|
||||||
'themeId' |
|
'themeId' |
|
||||||
|
'themeCachedVersion' |
|
||||||
'customCss' |
|
'customCss' |
|
||||||
'chatMessageDrafts' |
|
'chatMessageDrafts' |
|
||||||
'scratchpad' |
|
'scratchpad' |
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import tinycolor from 'tinycolor2';
|
|||||||
import lightTheme from '@@/themes/_light.json5';
|
import lightTheme from '@@/themes/_light.json5';
|
||||||
import darkTheme from '@@/themes/_dark.json5';
|
import darkTheme from '@@/themes/_dark.json5';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
|
import { version } from '@@/js/config.js';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import type { BundledTheme } from 'shiki/themes';
|
import type { BundledTheme } from 'shiki/themes';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
@@ -123,6 +124,7 @@ function applyThemeInternal(theme: Theme, persist: boolean) {
|
|||||||
if (persist) {
|
if (persist) {
|
||||||
miLocalStorage.setItem('theme', JSON.stringify(props));
|
miLocalStorage.setItem('theme', JSON.stringify(props));
|
||||||
miLocalStorage.setItem('themeId', theme.id);
|
miLocalStorage.setItem('themeId', theme.id);
|
||||||
|
miLocalStorage.setItem('themeCachedVersion', version);
|
||||||
miLocalStorage.setItem('colorScheme', colorScheme);
|
miLocalStorage.setItem('colorScheme', colorScheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +133,7 @@ function applyThemeInternal(theme: Theme, persist: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let timeout: number | null = null;
|
let timeout: number | null = null;
|
||||||
let currentTheme: Theme | null = null;
|
let currentThemeId = miLocalStorage.getItem('themeId');
|
||||||
|
|
||||||
export function applyTheme(theme: Theme, persist = true) {
|
export function applyTheme(theme: Theme, persist = true) {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
@@ -139,9 +141,8 @@ export function applyTheme(theme: Theme, persist = true) {
|
|||||||
timeout = null;
|
timeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deepEqual(currentTheme, theme)) return;
|
if (theme.id === currentThemeId && miLocalStorage.getItem('themeCachedVersion') === version) return;
|
||||||
// リアクティビティ解除
|
currentThemeId = theme.id;
|
||||||
currentTheme = deepClone(theme);
|
|
||||||
|
|
||||||
if (window.document.startViewTransition != null) {
|
if (window.document.startViewTransition != null) {
|
||||||
window.document.documentElement.classList.add('_themeChanging_');
|
window.document.documentElement.classList.add('_themeChanging_');
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
|
||||||
|
|
||||||
export type WithNonNullable<T, K extends keyof T> = T & { [P in K]-?: NonNullable<T[P]> };
|
|
||||||
6
packages/frontend/src/types/misc.ts
Normal file
6
packages/frontend/src/types/misc.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Awaitable <T> = T | Promise<T>;
|
||||||
23
packages/frontend/src/utility/snowfall-effect.fragment.glsl
Normal file
23
packages/frontend/src/utility/snowfall-effect.fragment.glsl
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
in vec4 v_color;
|
||||||
|
in float v_rotation;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 rotated = vec2(
|
||||||
|
cos(v_rotation) * (gl_PointCoord.x - 0.5) + sin(v_rotation) * (gl_PointCoord.y - 0.5) + 0.5,
|
||||||
|
cos(v_rotation) * (gl_PointCoord.y - 0.5) - sin(v_rotation) * (gl_PointCoord.x - 0.5) + 0.5
|
||||||
|
);
|
||||||
|
|
||||||
|
vec4 snowflake = texture(u_texture, rotated);
|
||||||
|
|
||||||
|
out_color = vec4(snowflake.rgb * v_color.xyz, snowflake.a * v_color.a);
|
||||||
|
}
|
||||||
@@ -3,59 +3,12 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import vertexSource from './snowfall-effect.vertex.glsl';
|
||||||
|
import fragmentSource from './snowfall-effect.fragment.glsl';
|
||||||
|
|
||||||
export class SnowfallEffect {
|
export class SnowfallEffect {
|
||||||
private VERTEX_SOURCE = `#version 300 es
|
private VERTEX_SOURCE = vertexSource;
|
||||||
in vec4 a_position;
|
private FRAGMENT_SOURCE = fragmentSource;
|
||||||
in vec4 a_color;
|
|
||||||
in vec3 a_rotation;
|
|
||||||
in vec3 a_speed;
|
|
||||||
in float a_size;
|
|
||||||
out vec4 v_color;
|
|
||||||
out float v_rotation;
|
|
||||||
uniform float u_time;
|
|
||||||
uniform mat4 u_projection;
|
|
||||||
uniform vec3 u_worldSize;
|
|
||||||
uniform float u_gravity;
|
|
||||||
uniform float u_wind;
|
|
||||||
uniform float u_spin_factor;
|
|
||||||
uniform float u_turbulence;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
v_color = a_color;
|
|
||||||
v_rotation = a_rotation.x + (u_time * u_spin_factor) * a_rotation.y;
|
|
||||||
|
|
||||||
vec3 pos = a_position.xyz;
|
|
||||||
|
|
||||||
pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x;
|
|
||||||
pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y;
|
|
||||||
|
|
||||||
pos.x += sin(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
|
||||||
pos.z += cos(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
|
||||||
|
|
||||||
gl_Position = u_projection * vec4(pos.xyz, a_position.w);
|
|
||||||
gl_PointSize = (a_size / gl_Position.w) * 100.0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
private FRAGMENT_SOURCE = `#version 300 es
|
|
||||||
precision mediump float;
|
|
||||||
|
|
||||||
in vec4 v_color;
|
|
||||||
in float v_rotation;
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
out vec4 out_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 rotated = vec2(
|
|
||||||
cos(v_rotation) * (gl_PointCoord.x - 0.5) + sin(v_rotation) * (gl_PointCoord.y - 0.5) + 0.5,
|
|
||||||
cos(v_rotation) * (gl_PointCoord.y - 0.5) - sin(v_rotation) * (gl_PointCoord.x - 0.5) + 0.5
|
|
||||||
);
|
|
||||||
|
|
||||||
vec4 snowflake = texture(u_texture, rotated);
|
|
||||||
|
|
||||||
out_color = vec4(snowflake.rgb * v_color.xyz, snowflake.a * v_color.a);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
private gl: WebGLRenderingContext;
|
private gl: WebGLRenderingContext;
|
||||||
private program: WebGLProgram;
|
private program: WebGLProgram;
|
||||||
|
|||||||
37
packages/frontend/src/utility/snowfall-effect.vertex.glsl
Normal file
37
packages/frontend/src/utility/snowfall-effect.vertex.glsl
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#version 300 es
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
in vec4 a_position;
|
||||||
|
in vec4 a_color;
|
||||||
|
in vec3 a_rotation;
|
||||||
|
in vec3 a_speed;
|
||||||
|
in float a_size;
|
||||||
|
out vec4 v_color;
|
||||||
|
out float v_rotation;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform mat4 u_projection;
|
||||||
|
uniform vec3 u_worldSize;
|
||||||
|
uniform float u_gravity;
|
||||||
|
uniform float u_wind;
|
||||||
|
uniform float u_spin_factor;
|
||||||
|
uniform float u_turbulence;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
v_color = a_color;
|
||||||
|
v_rotation = a_rotation.x + (u_time * u_spin_factor) * a_rotation.y;
|
||||||
|
|
||||||
|
vec3 pos = a_position.xyz;
|
||||||
|
|
||||||
|
pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x;
|
||||||
|
pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y;
|
||||||
|
|
||||||
|
pos.x += sin(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
||||||
|
pos.z += cos(u_time * a_speed.z * u_turbulence) * a_rotation.z;
|
||||||
|
|
||||||
|
gl_Position = u_projection * vec4(pos.xyz, a_position.w);
|
||||||
|
gl_PointSize = (a_size / gl_Position.w) * 100.0;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.10.1-beta.0",
|
"version": "2025.10.1-beta.3",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user