rewrite components with Svelte

This commit is contained in:
Pig Fang 2023-02-05 15:26:28 +08:00
parent 707d043a76
commit aa39ff0a37
No known key found for this signature in database
GPG Key ID: A8198F548DADA9E2
10 changed files with 224 additions and 238 deletions

View 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>

View 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)
})

View File

@ -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)
})

View File

@ -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>

View File

@ -0,0 +1,5 @@
import RegistrationLinksList from './RegistrationLinksList.svelte'
new RegistrationLinksList({
target: document.querySelector('#registration-links')!,
})

View File

@ -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'),
)

View 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}

View File

@ -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'))

View File

@ -0,0 +1,3 @@
import BindPlayer from './BindPlayer.svelte'
new BindPlayer({ target: document.querySelector('#form')! })

View File

@ -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'])