mirror of
https://github.com/bs-community/blessing-skin-plugins.git
synced 2025-01-08 11:37:27 +08:00
rewrite components with Svelte
This commit is contained in:
parent
707d043a76
commit
aa39ff0a37
29
plugins/invitation-codes/assets/CodeField.svelte
Normal file
29
plugins/invitation-codes/assets/CodeField.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
let code = ''
|
||||
|
||||
let off: () => void
|
||||
onMount(() => {
|
||||
off = globalThis.blessing.event.on(
|
||||
'beforeFetch',
|
||||
(request: { data: Record<string, string> }) => {
|
||||
request.data.invitationCode = code
|
||||
},
|
||||
)
|
||||
})
|
||||
onDestroy(() => off())
|
||||
</script>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder={globalThis.blessing.t('invitation-codes.placeholder')}
|
||||
required
|
||||
bind:value={code}
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<i class="fas fa-receipt" />
|
||||
</div>
|
||||
</div>
|
11
plugins/invitation-codes/assets/register.ts
Normal file
11
plugins/invitation-codes/assets/register.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import CodeField from './CodeField.svelte'
|
||||
|
||||
globalThis.blessing.event.on('mounted', () => {
|
||||
const div = document.createElement('div')
|
||||
div.className = 'input-group mb-3'
|
||||
new CodeField({ target: div })
|
||||
|
||||
setTimeout(() => {
|
||||
document.querySelector('.input-group:nth-child(4)')?.after(div)
|
||||
}, 0)
|
||||
})
|
@ -1,50 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { event, t } from 'blessing-skin'
|
||||
|
||||
const CodeField: React.FC = () => {
|
||||
const [code, setCode] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const off = event.on(
|
||||
'beforeFetch',
|
||||
(request: { data: Record<string, string> }) => {
|
||||
request.data.invitationCode = code
|
||||
},
|
||||
)
|
||||
|
||||
return off
|
||||
}, [code])
|
||||
|
||||
const handleCodeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCode(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('invitation-codes.placeholder')}
|
||||
required
|
||||
value={code}
|
||||
onChange={handleCodeChange}
|
||||
></input>
|
||||
<div className="input-group-append">
|
||||
<div className="input-group-text">
|
||||
<i className="fas fa-receipt"></i>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
event.on('mounted', () => {
|
||||
const div = document.createElement('div')
|
||||
div.className = 'input-group mb-3'
|
||||
ReactDOM.render(<CodeField />, div)
|
||||
|
||||
setTimeout(() => {
|
||||
document.querySelector('.input-group:nth-child(4)')?.after(div)
|
||||
}, 0)
|
||||
})
|
@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
type CodeRecord = {
|
||||
id: number
|
||||
sharer: number
|
||||
code: string
|
||||
url: string
|
||||
}
|
||||
|
||||
let records: CodeRecord[] = []
|
||||
let sharer = 0
|
||||
let sharee = 0
|
||||
|
||||
onMount(async () => {
|
||||
;({ records, sharer, sharee } = await globalThis.blessing.fetch.get(
|
||||
'/user/reg-links',
|
||||
))
|
||||
})
|
||||
|
||||
async function handleDeleteClick({ id }: CodeRecord) {
|
||||
await globalThis.blessing.fetch.del(`/user/reg-links/${id}`)
|
||||
records = records.filter((record) => record.id !== id)
|
||||
}
|
||||
|
||||
async function handleGenerateClick() {
|
||||
const {
|
||||
data: { record },
|
||||
}: { data: { record: CodeRecord } } = await globalThis.blessing.fetch.post(
|
||||
'/user/reg-links',
|
||||
)
|
||||
records = [...records, record]
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card card-primary card-outline">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">分享注册链接</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
分享注册链接,当新用户使用此链接时,您将获得 {sharer} 积分。 同时新用户可获得
|
||||
{sharee} 积分。
|
||||
</p>
|
||||
{#if records.length > 0}
|
||||
<p>可用的链接:</p>
|
||||
<ul class="break-word">
|
||||
{#each records as record (record.id)}
|
||||
<li>
|
||||
<span class="mr-1">{record.url}</span>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
on:click={() => handleDeleteClick(record)}
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
还没有已生成的链接。
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" on:click={handleGenerateClick}>
|
||||
生成新链接
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.break-word {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
5
plugins/share-registration-link/assets/generate.ts
Normal file
5
plugins/share-registration-link/assets/generate.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import RegistrationLinksList from './RegistrationLinksList.svelte'
|
||||
|
||||
new RegistrationLinksList({
|
||||
target: document.querySelector('#registration-links')!,
|
||||
})
|
@ -1,84 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { fetch } from 'blessing-skin'
|
||||
|
||||
type CodeRecord = {
|
||||
id: number
|
||||
sharer: number
|
||||
code: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const RegistrationLinksList = () => {
|
||||
const [records, setRecords] = React.useState<CodeRecord[]>([])
|
||||
const [sharer, setSharer] = React.useState(0)
|
||||
const [sharee, setSharee] = React.useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
const getLinks = async () => {
|
||||
const response: {
|
||||
records: CodeRecord[]
|
||||
sharer: number
|
||||
sharee: number
|
||||
} = await fetch.get('/user/reg-links')
|
||||
setRecords(response.records)
|
||||
setSharer(response.sharer)
|
||||
setSharee(response.sharee)
|
||||
}
|
||||
getLinks()
|
||||
}, [])
|
||||
|
||||
const handleDeleteClick = async (record: CodeRecord) => {
|
||||
const { id } = record
|
||||
await fetch.del(`/user/reg-links/${id}`)
|
||||
setRecords((records) => records.filter((record) => record.id !== id))
|
||||
}
|
||||
|
||||
const handleGenerateClick = async () => {
|
||||
const {
|
||||
data: { record },
|
||||
}: { data: { record: CodeRecord } } = await fetch.post('/user/reg-links')
|
||||
setRecords((records) => [...records, record])
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card card-primary card-outline">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">分享注册链接</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>
|
||||
分享注册链接,当新用户使用此链接时,您将获得 {sharer} 积分。
|
||||
同时新用户可获得 {sharee} 积分。
|
||||
</p>
|
||||
{records.length > 0 ? (
|
||||
<>
|
||||
<p>可用的链接:</p>
|
||||
<ul style={{ wordWrap: 'break-word' }}>
|
||||
{records.map((record) => (
|
||||
<li key={record.id}>
|
||||
<span className="mr-1">{record.url}</span>
|
||||
<a href="#" onClick={() => handleDeleteClick(record)}>
|
||||
删除
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
'还没有已生成的链接。'
|
||||
)}
|
||||
</div>
|
||||
<div className="card-footer">
|
||||
<button className="btn btn-primary" onClick={handleGenerateClick}>
|
||||
生成新链接
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<RegistrationLinksList />,
|
||||
document.querySelector('#registration-links'),
|
||||
)
|
100
plugins/single-player-limit/assets/BindPlayer.svelte
Normal file
100
plugins/single-player-limit/assets/BindPlayer.svelte
Normal file
@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
const { base_url, fetch, notify, t } = globalThis.blessing
|
||||
|
||||
type Player = {
|
||||
pid: number
|
||||
name: string
|
||||
uid: number
|
||||
tid_skin: number
|
||||
tid_cape: number
|
||||
last_modified: string
|
||||
}
|
||||
|
||||
let isLoading = false
|
||||
let isSubmitting = false
|
||||
|
||||
let players: string[] = []
|
||||
let selected = ''
|
||||
|
||||
onMount(async () => {
|
||||
isLoading = true
|
||||
try {
|
||||
const data = await fetch.get<Player[]>('/user/player/list')
|
||||
players = data.map(({ name }) => name)
|
||||
selected = players[0]
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
isSubmitting = true
|
||||
try {
|
||||
const {
|
||||
code,
|
||||
message,
|
||||
}: {
|
||||
code: number
|
||||
message: string
|
||||
} = await fetch.post('/user/player/bind', {
|
||||
player: selected,
|
||||
})
|
||||
if (code === 0) {
|
||||
await notify.showModal({
|
||||
mode: 'alert',
|
||||
text: message,
|
||||
})
|
||||
location.assign(`${base_url}/user`)
|
||||
} else {
|
||||
notify.showModal({ mode: 'alert', text: message })
|
||||
}
|
||||
} finally {
|
||||
isSubmitting = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isLoading}
|
||||
<p>Loading...</p>
|
||||
{:else}
|
||||
<form method="post" on:submit|preventDefault={handleSubmit}>
|
||||
{#if players.length > 0}
|
||||
<p>{t('single-player-limit.bindExistedPlayer')}</p>
|
||||
<div class="mb-3">
|
||||
{#each players as player (player)}
|
||||
<label class="d-block mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
class="mr-2"
|
||||
checked={selected === player}
|
||||
on:change={() => (selected = player)}
|
||||
/>
|
||||
{player}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p>{t('single-player-limit.bindNewPlayer')}</p>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control mb-3"
|
||||
placeholder={t('general.player.player-name')}
|
||||
on:change={(e) => (selected = e.currentTarget.value)}
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
class="btn btn-primary float-right"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{#if isSubmitting}
|
||||
<i class="fas fa-spinner fa-spin mr-1" />
|
||||
{t('general.wait')}
|
||||
{:else}
|
||||
{t('general.submit')}
|
||||
{/if}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
@ -1,103 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { fetch, notify, base_url, t } from 'blessing-skin'
|
||||
|
||||
type Player = {
|
||||
pid: number
|
||||
name: string
|
||||
uid: number
|
||||
tid_skin: number
|
||||
tid_cape: number
|
||||
last_modified: string
|
||||
}
|
||||
|
||||
const BindPlayer: React.FC = () => {
|
||||
const [players, setPlayers] = React.useState<string[]>([])
|
||||
const [selected, setSelected] = React.useState('')
|
||||
const [isLoading, setIsLoading] = React.useState(false)
|
||||
const [isPending, setIsPending] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const getPlayers = async () => {
|
||||
setIsLoading(true)
|
||||
const data = await fetch.get<Player[]>('/user/player/list')
|
||||
const players = data.map((player) => player.name)
|
||||
setPlayers(players)
|
||||
setSelected(players[0])
|
||||
setIsLoading(false)
|
||||
}
|
||||
getPlayers()
|
||||
}, [])
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault()
|
||||
setIsPending(true)
|
||||
|
||||
const {
|
||||
code,
|
||||
message,
|
||||
}: {
|
||||
code: number
|
||||
message: string
|
||||
} = await fetch.post('/user/player/bind', { player: selected })
|
||||
if (code === 0) {
|
||||
await notify.showModal({ mode: 'alert', text: message })
|
||||
window.location.href = `${base_url}/user`
|
||||
} else {
|
||||
notify.showModal({ mode: 'alert', text: message })
|
||||
}
|
||||
|
||||
setIsPending(false)
|
||||
}
|
||||
|
||||
return isLoading ? (
|
||||
<p>Loading...</p>
|
||||
) : (
|
||||
<form method="post" onSubmit={handleSubmit}>
|
||||
{players.length > 0 ? (
|
||||
<>
|
||||
<p>{t('single-player-limit.bindExistedPlayer')}</p>
|
||||
<div className="mb-3">
|
||||
{players.map((player) => (
|
||||
<label key={player} className="d-block mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
className="mr-2"
|
||||
checked={selected === player}
|
||||
onChange={() => setSelected(player)}
|
||||
/>
|
||||
{player}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>{t('single-player-limit.bindNewPlayer')}</p>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control mb-3"
|
||||
placeholder={t('general.player.player-name')}
|
||||
onChange={(e) => setSelected(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-primary float-right"
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending ? (
|
||||
<>
|
||||
<i className="fas fa-spinner fa-spin mr-1"></i>
|
||||
{t('general.wait')}
|
||||
</>
|
||||
) : (
|
||||
t('general.submit')
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
ReactDOM.render(<BindPlayer />, document.querySelector('#form'))
|
3
plugins/single-player-limit/assets/bind.ts
Normal file
3
plugins/single-player-limit/assets/bind.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import BindPlayer from './BindPlayer.svelte'
|
||||
|
||||
new BindPlayer({ target: document.querySelector('#form')! })
|
@ -37,7 +37,7 @@ return function (
|
||||
$user->save();
|
||||
});
|
||||
|
||||
Hook::addScriptFileToPage($plugin->assets('BindPlayer.js'), ['user/player/bind']);
|
||||
Hook::addScriptFileToPage($plugin->assets('bind.js'), ['user/player/bind']);
|
||||
Hook::addRoute(function () {
|
||||
Route::namespace('SinglePlayerLimit')
|
||||
->middleware(['web', 'authorize'])
|
||||
|
Loading…
Reference in New Issue
Block a user