0
0
Fork 0

ガチャ機能実装など

This commit is contained in:
Ebise Lutica 2022-05-07 02:53:10 +09:00
parent e46bbd9fa5
commit 06a47b18e3
8 changed files with 133 additions and 141 deletions

View file

@ -0,0 +1,25 @@
const allKatakana = [
...('アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨ'.split('')),
'ウィ', 'ウェ',
'キャ', 'キュ', 'キョ',
'クァ', 'クォ',
'シャ', 'シュ', 'ショ',
'チャ', 'チュ', 'チョ',
'ヒャ', 'ヒュ', 'ヒョ',
'ミャ', 'ミュ', 'ミョ'
];
const allInfix = [ '', 'ー', 'ッ' ];
const getRandomKatakana = () => allKatakana[Math.floor(Math.random() * allKatakana.length)];
const getRandomInfix = () => allInfix[Math.floor(Math.random() * allInfix.length)];
export const createGacha = () => {
return [
getRandomKatakana(),
getRandomInfix(),
getRandomKatakana(),
getRandomInfix(),
...(new Array(Math.floor(Math.random() * 2 + 1)).fill('').map(() => getRandomKatakana()))
].join('');
};

View file

@ -2,48 +2,28 @@ import { config } from '../../config';
import { Score } from '../types/score'; import { Score } from '../types/score';
import { defaultTemplate } from '../../backend/const'; import { defaultTemplate } from '../../backend/const';
import { IUser } from '../types/user'; import { IUser } from '../types/user';
import { createGacha } from './create-gacha';
/** /**
* *
*/ */
export type Variable = { export type Variable = string | ((score: Score, user: IUser) => string);
replace?: string | ((score: Score, user: IUser) => string);
};
/** /**
* *
*/ */
export const variables: Record<string, Variable> = { export const variables: Record<string, Variable> = {
notesCount: { notesCount: score => String(score.notesCount),
replace: (score) => String(score.notesCount), followingCount: score => String(score.followingCount),
}, followersCount: score => String(score.followersCount),
followingCount: { notesDelta: score => String(score.notesDelta),
replace: (score) => String(score.followingCount), followingDelta: score => String(score.followingDelta),
}, followersDelta: score => String(score.followersDelta),
followersCount: { url: config.url,
replace: (score) => String(score.followersCount), username: (_, user) => String(user.username),
}, host: (_, user) => String(user.host),
notesDelta: { rating: (_, user) => String(user.rating),
replace: (score) => String(score.notesDelta), gacha: () => createGacha(),
},
followingDelta: {
replace: (score) => String(score.followingDelta),
},
followersDelta: {
replace: (score) => String(score.followersDelta),
},
url: {
replace: config.url,
},
username: {
replace: (_, user) => String(user.username),
},
host: {
replace: (_, user) => String(user.host),
},
rating: {
replace: (_, user) => String(user.rating),
},
}; };
const variableRegex = /\{([a-zA-Z0-9_]+?)\}/g; const variableRegex = /\{([a-zA-Z0-9_]+?)\}/g;
@ -58,6 +38,6 @@ export const format = (score: Score, user: IUser): string => {
const template = user.template || defaultTemplate; const template = user.template || defaultTemplate;
return template.replace(variableRegex, (m, name) => { return template.replace(variableRegex, (m, name) => {
const v = variables[name]; const v = variables[name];
return !v || !v.replace ? m : typeof v.replace === 'function' ? v.replace(score, user) : v.replace; return !v ? m : typeof v === 'function' ? v(score, user) : v;
}) + '\n\n#misshaialert'; }) + '\n\n#misshaialert';
}; };

View file

@ -132,76 +132,72 @@ export const AdminPage: React.VFC = () => {
<p>You are not an administrator and cannot open this page.</p> <p>You are not an administrator and cannot open this page.</p>
) : ( ) : (
<> <>
<article> <h2>Announcements</h2>
<h2>Announcements</h2> {!isEditMode && (
{!isEditMode && ( <label className="input-switch mb-1">
<label className="input-switch mb-1"> <input type="checkbox" checked={isDeleteMode} onChange={e => setDeleteMode(e.target.checked)}/>
<input type="checkbox" checked={isDeleteMode} onChange={e => setDeleteMode(e.target.checked)}/> <div className="switch"></div>
<div className="switch"></div> <span>Delete Mode</span>
<span>Delete Mode</span> </label>
</label> )}
)} <Card bodyClassName={isEditMode ? '' : 'px-0'}>
<Card bodyClassName={isEditMode ? '' : 'px-0'}> { !isEditMode ? (
{ !isEditMode ? ( <>
<> {isDeleteMode && <div className="ml-2 text-danger">Click the item to delete.</div>}
{isDeleteMode && <div className="ml-2 text-danger">Click the item to delete.</div>} <div className="large menu">
<div className="large menu"> {announcements.map(a => (
{announcements.map(a => ( <button className="item fluid" key={a.id} onClick={() => {
<button className="item fluid" key={a.id} onClick={() => { if (isDeleteMode) {
if (isDeleteMode) { deleteAnnouncement(a);
deleteAnnouncement(a); } else {
} else { selectAnnouncement(a);
selectAnnouncement(a); setEditMode(true);
setEditMode(true); }
}
}}>
{isDeleteMode && <i className="icon bi bi-trash text-danger" />}
{a.title}
</button>
))}
{!isDeleteMode && (
<button className="item fluid" onClick={() => setEditMode(true)}>
<i className="icon bi bi-plus"/ >
Create New
</button>
)}
</div>
</>
) : (
<div className="vstack">
<label className="input-field">
Title
<input type="text" value={draftTitle} onChange={e => setDraftTitle(e.target.value)} />
</label>
<label className="input-field">
Body
<textarea className="input-field" value={draftBody} rows={10} onChange={e => setDraftBody(e.target.value)}/>
</label>
<div className="hstack" style={{justifyContent: 'flex-end'}}>
<button className="btn primary" onClick={submitAnnouncement} disabled={!draftTitle || !draftBody}>
Submit
</button>
<button className="btn" onClick={() => {
selectAnnouncement(null);
setEditMode(false);
}}> }}>
Cancel {isDeleteMode && <i className="icon bi bi-trash text-danger" />}
{a.title}
</button> </button>
</div> ))}
{!isDeleteMode && (
<button className="item fluid" onClick={() => setEditMode(true)}>
<i className="icon bi bi-plus"/ >
Create New
</button>
)}
</div> </div>
)} </>
</Card> ) : (
</article> <div className="vstack">
<article> <label className="input-field">
<h2>Misshai</h2> Title
<div className="vstack"> <input type="text" value={draftTitle} onChange={e => setDraftTitle(e.target.value)} />
<button className="btn danger" onClick={onClickStartMisshaiAlertWorkerButton}> </label>
<label className="input-field">
</button> Body
<h3></h3> <textarea className="input-field" value={draftBody} rows={10} onChange={e => setDraftBody(e.target.value)}/>
<pre><code>{misshaiLog?.join('\n') ?? 'なし'}</code></pre> </label>
</div> <div className="hstack" style={{justifyContent: 'flex-end'}}>
</article> <button className="btn primary" onClick={submitAnnouncement} disabled={!draftTitle || !draftBody}>
Submit
</button>
<button className="btn" onClick={() => {
selectAnnouncement(null);
setEditMode(false);
}}>
Cancel
</button>
</div>
</div>
)}
</Card>
<h2>Misshai</h2>
<div className="vstack">
<button className="btn danger" onClick={onClickStartMisshaiAlertWorkerButton}>
</button>
<h3></h3>
<pre><code>{misshaiLog?.join('\n') ?? 'なし'}</code></pre>
</div>
</> </>
) )
} }

View file

@ -1,9 +1,10 @@
import React, { HTMLProps } from 'react'; import React, { HTMLProps, useMemo, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useGetMetaQuery } from '../services/session'; import { useGetMetaQuery, useGetSessionQuery } from '../services/session';
import { CHANGELOG_URL } from '../const'; import { CHANGELOG_URL } from '../const';
import { createGacha } from '../../common/functions/create-gacha';
export type HeaderProps = { export type HeaderProps = {
hasTopLink?: boolean; hasTopLink?: boolean;
@ -11,11 +12,15 @@ export type HeaderProps = {
style?: HTMLProps<HTMLElement>['style'], style?: HTMLProps<HTMLElement>['style'],
}; };
const messageNumber = Math.floor(Math.random() * 6) + 1;
export const Header: React.FC<HeaderProps> = ({hasTopLink, children, className, style}) => { export const Header: React.FC<HeaderProps> = ({hasTopLink, children, className, style}) => {
const {data: meta} = useGetMetaQuery(undefined); const {data: meta} = useGetMetaQuery(undefined);
const {data: session} = useGetSessionQuery(undefined);
const { t } = useTranslation(); const { t } = useTranslation();
const [generation, setGeneration] = useState(0);
const gacha = useMemo(() => createGacha(), [generation]);
return ( return (
<header className={`card ${className ?? ''}`} style={style}> <header className={`card ${className ?? ''}`} style={style}>
<div className="body"> <div className="body">
@ -27,7 +32,21 @@ export const Header: React.FC<HeaderProps> = ({hasTopLink, children, className,
</a> </a>
)} )}
</h1> </h1>
<h2 className="text-dimmed ml-1">{t(`_welcomeMessage.pattern${messageNumber}`)}</h2> <h2 className="text-dimmed">
<button onClick={() => setGeneration(g => g + 1)} className="text-primary">
<i className="bi bi-dice-5-fill" />
</button>
{gacha}
{session && (
<a
href={`https://${session.host}/share?text=${encodeURIComponent(`${gacha} https://misskey.tools`)}`}
target="_blank"
rel="noreferrer noopener"
className="ml-1">
<i className="bi bi-share-fill" />
</a>
)}
</h2>
{children} {children}
</div> </div>
</header> </header>

View file

@ -27,6 +27,7 @@ const variables = [
'username', 'username',
'host', 'host',
'rating', 'rating',
'gacha',
] as const; ] as const;
type SettingDraftType = Partial<Pick<IUser, type SettingDraftType = Partial<Pick<IUser,
@ -203,7 +204,7 @@ export const MisshaiPage: React.VFC = () => {
<div className="misshaiPageLayout"> <div className="misshaiPageLayout">
<div className="card misshaiData"> <div className="card misshaiData">
<div className="body"> <div className="body">
<h1><i className="bi-activity"></i> {t('_missHai.data')}</h1> <h1><i className="bi bi-activity"></i> {t('_missHai.data')}</h1>
<table className="table fluid"> <table className="table fluid">
<thead> <thead>
<tr> <tr>
@ -246,7 +247,7 @@ export const MisshaiPage: React.VFC = () => {
</div> </div>
<div className="card misshaiRanking"> <div className="card misshaiRanking">
<div className="body"> <div className="body">
<h1><i className="bi-bar-chart"></i> {t('_missHai.ranking')}</h1> <h1><i className="bi bi-bar-chart"></i> {t('_missHai.ranking')}</h1>
<Ranking limit={limit} /> <Ranking limit={limit} />
{limit && <button className="btn primary" onClick={() => setLimit(undefined)}>{t('_missHai.showAll')}</button>} {limit && <button className="btn primary" onClick={() => setLimit(undefined)}>{t('_missHai.showAll')}</button>}
<label className="input-check mt-2"> <label className="input-check mt-2">
@ -260,7 +261,7 @@ export const MisshaiPage: React.VFC = () => {
<div className="misshaiPageLayout"> <div className="misshaiPageLayout">
<div className="card alertModeSetting"> <div className="card alertModeSetting">
<div className="body"> <div className="body">
<h1 className="mb-2"><i className="bi-gear"></i> {t('alertMode')}</h1> <h1 className="mb-2"><i className="bi bi-gear"></i> {t('alertMode')}</h1>
<div className="vstack"> <div className="vstack">
{ alertModes.map((mode) => ( { alertModes.map((mode) => (
<label key={mode} className="input-check"> <label key={mode} className="input-check">

View file

@ -91,7 +91,7 @@ export const SettingPage: React.VFC = () => {
) : ( ) : (
<div className="vstack fade"> <div className="vstack fade">
<Card bodyClassName="vstack"> <Card bodyClassName="vstack">
<h1><i className="bi-palette"></i> {t('appearance')}</h1> <h1><i className="bi bi-palette"></i> {t('appearance')}</h1>
<h2>{t('theme')}</h2> <h2>{t('theme')}</h2>
<div className="vstack"> <div className="vstack">
{ {

View file

@ -133,7 +133,8 @@
"url": "本サイトのURL", "url": "本サイトのURL",
"username": "アカウントのユーザー名", "username": "アカウントのユーザー名",
"host": "アカウントの所属ホスト名", "host": "アカウントの所属ホスト名",
"rating": "レート" "rating": "レート",
"gacha": "ガチャ"
} }
}, },
"_error": { "_error": {

View file

@ -1905,11 +1905,6 @@ enquirer@^2.3.5:
dependencies: dependencies:
ansi-colors "^4.1.1" ansi-colors "^4.1.1"
entities@~1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@~2.1.0: entities@~2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
@ -3188,13 +3183,6 @@ json5@^2.1.2, json5@^2.1.3:
dependencies: dependencies:
minimist "^1.2.5" minimist "^1.2.5"
jstransformer-markdown-it@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/jstransformer-markdown-it/-/jstransformer-markdown-it-2.1.0.tgz#69ec30ce4518bed5997b38f027648e8c285e92f7"
integrity sha1-aewwzkUYvtWZezjwJ2SOjChekvc=
dependencies:
markdown-it "^8.0.0"
jstransformer@1.0.0: jstransformer@1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
@ -3377,13 +3365,6 @@ libphonenumber-js@^1.9.7:
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz#944f59a3618a8f85d9b619767a0b6fb87523f285" resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.37.tgz#944f59a3618a8f85d9b619767a0b6fb87523f285"
integrity sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg== integrity sha512-RnUR4XwiVhMLnT7uFSdnmLeprspquuDtaShAgKTA+g/ms9/S4hQU3/QpFdh3iXPHtxD52QscXLm2W2+QBmvYAg==
linkify-it@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
dependencies:
uc.micro "^1.0.1"
linkify-it@^3.0.1: linkify-it@^3.0.1:
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
@ -3505,17 +3486,6 @@ markdown-it@^12.3.2:
mdurl "^1.0.1" mdurl "^1.0.1"
uc.micro "^1.0.5" uc.micro "^1.0.5"
markdown-it@^8.0.0:
version "8.4.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"
integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==
dependencies:
argparse "^1.0.7"
entities "~1.1.1"
linkify-it "^2.0.0"
mdurl "^1.0.1"
uc.micro "^1.0.5"
mdast-util-definitions@^5.0.0: mdast-util-definitions@^5.0.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817"