Skip to content

Commit 8ea5c0c

Browse files
authored
Merge pull request #443 from TaloDev/develop
Release 0.66.0
2 parents d33c27b + eb72468 commit 8ea5c0c

16 files changed

+400
-138
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Talo is available to use via our [Godot plugin](https://github.com/TaloDev/godot
1919
- 🔧 [Steamworks integration](https://trytalo.com/steamworks-integration): Hook into Steamworks for authentication and ownership checks.
2020
- 🗣️ [Game feedback](https://trytalo.com/feedback): Collect and manage feedback from your players.
2121
- 🔔 [Player presence](https://trytalo.com/players#presence): See if players are online and set custom statuses.
22+
- 🤝 [Player relationships](https://trytalo.com/player-relationships): Easily add friends, followers and social systems to your game.
2223

2324
## Links
2425

package-lock.json

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"react-dom": "^18.3.1",
3131
"react-focus-lock": "^2.13.6",
3232
"react-hook-form": "^7.66.0",
33-
"react-router-dom": "^6.23.1",
33+
"react-router-dom": "^6.30.3",
3434
"react-select": "^5.8.0",
3535
"react-useportal": "^1.0.19",
3636
"recharts": "^2.15.2",
@@ -78,7 +78,7 @@
7878
"lint-staged": {
7979
"*.{ts,js,tsx,jsx}": "eslint --fix"
8080
},
81-
"version": "0.65.1",
81+
"version": "0.66.0",
8282
"engines": {
8383
"node": "22.x"
8484
},

src/components/ButtonGroup.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Button from './Button'
2+
import clsx from 'clsx'
3+
4+
export type ButtonGroupOption<T> = {
5+
id: T
6+
label: string
7+
}
8+
9+
type ButtonGroupProps<T> = {
10+
options: ButtonGroupOption<T>[]
11+
selected: T | null
12+
onChange: (value: T) => void
13+
}
14+
15+
export default function ButtonGroup<T extends string>({
16+
options,
17+
selected,
18+
onChange
19+
}: ButtonGroupProps<T>) {
20+
const buttonClassName = 'text-sm md:text-base border-2 border-l-0 py-1 px-1 md:py-1.5 md:px-2 border-indigo-500 rounded-none first:rounded-l first:border-l-2 last:rounded-r hover:bg-gray-900 ring-inset'
21+
22+
return (
23+
<div>
24+
{options.map((option) => (
25+
<Button
26+
type='button'
27+
key={option.id}
28+
variant='bare'
29+
className={clsx(buttonClassName, { 'bg-indigo-500 hover:bg-indigo-500': option.id === selected })}
30+
onClick={() => onChange(option.id)}
31+
>
32+
{option.label}
33+
</Button>
34+
))}
35+
</div>
36+
)
37+
}

src/components/Footer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default function Footer() {
3232
<li><Link to='https://trytalo.com/stats?utm_source=dashboard&utm_medium=footer'>Game stats</Link></li>
3333
<li><Link to='https://trytalo.com/feedback?utm_source=dashboard&utm_medium=footer'>Game feedback</Link></li>
3434
<li><Link to='https://trytalo.com/live-config?utm_source=dashboard&utm_medium=footer'>Live config</Link></li>
35+
<li><Link to='https://trytalo.com/player-relationships?utm_source=dashboard&utm_medium=footer'>Player relationships</Link></li>
3536
<li><Link to='https://trytalo.com/open-source?utm_source=dashboard&utm_medium=footer'>Open source</Link></li>
3637
</ul>
3738
</div>

src/components/RecoveryCodes.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function RecoveryCodes({
5454
return (
5555
<Container>
5656
<AlertBanner
57-
className='!items-start'
57+
className='items-start!'
5858
text={'Keep these codes safe. If you lose access to your authenticator and don\'t have any recovery codes, you will lose access to your account.'}
5959
/>
6060

@@ -64,6 +64,7 @@ function RecoveryCodes({
6464

6565
<div className='md:flex space-y-4 md:space-y-0 md:space-x-4'>
6666
<Button
67+
type='button'
6768
className='justify-center md:w-auto'
6869
variant={codesCopied ? 'green' : undefined}
6970
onClick={onCopyCodesClick}
@@ -73,6 +74,7 @@ function RecoveryCodes({
7374
</Button>
7475

7576
<Button
77+
type='button'
7678
className='justify-center md:w-auto'
7779
onClick={onDownloadCodesClick}
7880
icon={<IconDownload />}
@@ -82,6 +84,7 @@ function RecoveryCodes({
8284

8385
{showCreateButton &&
8486
<Button
87+
type='button'
8588
className='justify-center md:w-auto'
8689
onClick={onCreateRecoveryCodesClick}
8790
>
Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { TimePeriod } from '../utils/useTimePeriod'
2-
import Button from './Button'
3-
import clsx from 'clsx'
2+
import ButtonGroup, { ButtonGroupOption } from './ButtonGroup'
43

5-
export type LabelledTimePeriod = {
6-
id: TimePeriod
7-
label: string
8-
}
4+
export type LabelledTimePeriod = ButtonGroupOption<TimePeriod>
95

106
type TimePeriodPickerProps = {
117
periods: LabelledTimePeriod[]
@@ -14,20 +10,16 @@ type TimePeriodPickerProps = {
1410
}
1511

1612
export default function TimePeriodPicker({ periods, onPick, selectedPeriod }: TimePeriodPickerProps) {
17-
const buttonClassName = 'text-sm md:text-base border-2 border-l-0 py-1 px-1 md:py-1.5 md:px-2 border-indigo-500 rounded-none first:rounded-l first:border-l-2 last:rounded-r hover:bg-gray-900 ring-inset'
18-
1913
return (
20-
<div>
21-
{periods.map((period) => (
22-
<Button
23-
key={period.id}
24-
variant='bare'
25-
className={clsx(buttonClassName, { 'bg-indigo-500 hover:bg-indigo-500': period.id === selectedPeriod })}
26-
onClick={() => onPick(period)}
27-
>
28-
{period.label}
29-
</Button>
30-
))}
31-
</div>
14+
<ButtonGroup
15+
options={periods}
16+
selected={selectedPeriod}
17+
onChange={(id) => {
18+
const period = periods.find((p) => p.id === id)
19+
if (period) {
20+
onPick(period)
21+
}
22+
}}
23+
/>
3224
)
3325
}

src/components/events/EventsFiltersSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function EventsFiltersSection({ eventNames, error }: Props) {
3131
}, [eventNames, filteredSelectedEvents, selectedEventNames, setSelectedEventNames])
3232

3333
return (
34-
<div className='justify-between items-start'>
34+
<div>
3535
<div className='mb-4 md:mb-8'>
3636
<TimePeriodPicker
3737
periods={timePeriods}

src/components/saves/SaveDataNode.tsx

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
2-
import { NodeDataRow } from '../../utils/useNodeGraph'
1+
import { memo, useCallback, useContext, useEffect, useMemo, useRef } from 'react'
32
import { Handle, Position } from '@xyflow/react'
43
import { useSetRecoilState } from 'recoil'
54
import saveDataNodeSizesState from '../../state/saveDataNodeSizesState'
65
import clsx from 'clsx'
6+
import Button from '../Button'
7+
import { IconCopy } from '@tabler/icons-react'
8+
import ToastContext from '../toast/ToastContext'
9+
import { NodeDataRow } from '../../utils/nodeGraphHelpers'
10+
11+
const MIN_NODE_WIDTH = 'min-w-[200px]'
12+
const MAX_NODE_WIDTH = 'max-w-[600px]'
713

814
type SaveDataNodeProps = {
915
id: string
10-
data: { rows: NodeDataRow[], search: string, formatVersion: string }
16+
data: {
17+
rows: NodeDataRow[]
18+
search: string
19+
formatVersion: string
20+
isHovered: boolean
21+
}
1122
}
1223

1324
function SaveDataNode({ id, data }: SaveDataNodeProps ) {
@@ -30,7 +41,8 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
3041
}, [id, setNodeSizes])
3142

3243
const valueIsNumber = useCallback((value: unknown) => {
33-
return !isNaN(Number(value))
44+
const str = String(value)
45+
return str !== '' && !isNaN(Number(value))
3446
}, [])
3547

3648
const valueIsBoolean = useCallback((value: unknown) => {
@@ -51,6 +63,9 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
5163

5264
const renderItem = useCallback((item: string) => {
5365
if (valueIsString(item)) {
66+
if (item === '') {
67+
return '""'
68+
}
5469
if (data.formatVersion === 'godot.v2' && item.includes('"')) {
5570
return item
5671
}
@@ -65,15 +80,39 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
6580
})
6681
}, [data.search, data.rows])
6782

83+
const valueRow = useMemo(() => {
84+
return data.rows.find((row) => row.item.startsWith('value'))
85+
}, [data.rows])
86+
87+
const canShowCopyButton = data.isHovered && valueRow
88+
89+
const toast = useContext(ToastContext)
90+
91+
const copyValue = useCallback(() => {
92+
if (valueRow) {
93+
const value = valueRow.item.split(': ')[1] || ''
94+
navigator.clipboard.writeText(value)
95+
toast.trigger('Value copied to clipboard')
96+
}
97+
}, [valueRow, toast])
98+
6899
return (
69-
<div ref={ref} className={clsx('py-4 px-8 rounded bg-gray-900 border-2 border-gray-500 transition-all', {
70-
'bg-opacity-30': data.search !== '' && !searchMatches
71-
})}>
72-
<div className={clsx('transition-opacity -mx-4', { 'opacity-30': data.search !== '' && !searchMatches })}>
100+
<div
101+
ref={ref}
102+
className={clsx(
103+
'py-4 px-8 rounded bg-gray-900 border-2 border-gray-500 transition-all',
104+
MIN_NODE_WIDTH,
105+
MAX_NODE_WIDTH,
106+
{
107+
'bg-opacity-30': data.search !== '' && !searchMatches
108+
}
109+
)}
110+
>
111+
<div className={clsx('relative transition-opacity -mx-4', { 'opacity-30': data.search !== '' && !searchMatches })}>
73112
{data.rows.map((row, idx) => (
74113
<div
75114
key={idx}
76-
className={clsx('text-sm text-white font-mono text-nowrap', {
115+
className={clsx('text-sm text-white font-mono wrap-break-word', {
77116
'text-center': data.rows.length === 1
78117
})}
79118
>
@@ -83,15 +122,15 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
83122
<span className='text-indigo-300'>{row.item.split(' ')[1]}</span>
84123
</>
85124
}
86-
{row.type !== 'array' && /(.*: .*)\w+/.test(row.item) &&
125+
{row.type !== 'array' && row.item.includes(': ') &&
87126
<>
88-
<span className='font-medium'>{row.item.split(' ')[0]} </span>
89-
<span className={composeClassNames(row.item.split(' ')[1])}>
90-
{renderItem(row.item.split(': ')[1])}
127+
<span className='font-medium'>{row.item.split(': ')[0]}: </span>
128+
<span className={composeClassNames(row.item.split(': ')[1] || '')}>
129+
{renderItem(row.item.split(': ')[1] || '')}
91130
</span>
92131
</>
93132
}
94-
{row.type !== 'array' && !/(.*: .*)\w+/.test(row.item) &&
133+
{row.type !== 'array' && !row.item.includes(': ') &&
95134
<span
96135
className={composeClassNames(row.item)}>
97136
{renderItem(row.item)}
@@ -100,6 +139,15 @@ function SaveDataNode({ id, data }: SaveDataNodeProps ) {
100139
</div>
101140
))}
102141

142+
{canShowCopyButton &&
143+
<Button
144+
type='button'
145+
className='absolute right-0 top-0 w-auto!'
146+
icon={<IconCopy size={20} />}
147+
onClick={copyValue}
148+
/>
149+
}
150+
103151
<Handle type='target' position={Position.Top} className='invisible mt-1' />
104152
<Handle type='source' position={Position.Bottom} className='invisible' />
105153
</div>

0 commit comments

Comments
 (0)