Skip to content

Commit cab04f9

Browse files
committed
feat(new tools): Dice Roller, Cin Flipper, Card Picker and Fortune Wheel
Fix #1493 and #28
1 parent 08d977b commit cab04f9

File tree

10 files changed

+274
-0
lines changed

10 files changed

+274
-0
lines changed

src/tools/card-picker/card-picker.vue

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script setup lang="ts">
2+
import cards from '@younestouati/playing-cards-standard-deck';
3+
import { computedRefreshable } from '@/composable/computedRefreshable';
4+
import { useCopy } from '@/composable/copy';
5+
import { useQueryParamOrStorage } from '@/composable/queryParams';
6+
import { randIntFromInterval } from '@/utils/random';
7+
8+
const cardKeys = Object.keys(cards);
9+
10+
const numberOfCards = useQueryParamOrStorage({ name: 'cards', storageName: 'card-picker:n', defaultValue: 5 });
11+
const [cardPicked, refreshCardPicked] = computedRefreshable(() =>
12+
Array.from({ length: numberOfCards.value },
13+
() => cardKeys[randIntFromInterval(0, 51)]));
14+
15+
const cardPickedString = computed(() => cardPicked.value.join(', '));
16+
17+
const { copy } = useCopy({ source: cardPickedString, text: 'Cards Picked copied to the clipboard' });
18+
</script>
19+
20+
<template>
21+
<c-card>
22+
<div>
23+
<img v-for="(card, index) in cardPicked" :key="index" style="width:30px" mr-1 :src="`data:image/svg+xml;base64,${cards[card]}`">
24+
</div>
25+
<div flex justify-center gap-3>
26+
<c-button @click="copy()">
27+
Copy deck
28+
</c-button>
29+
<c-button @click="refreshCardPicked">
30+
Refresh deck
31+
</c-button>
32+
</div>
33+
</c-card>
34+
</template>

src/tools/card-picker/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PlayCard } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Card Picker',
6+
path: '/card-picker',
7+
description: 'Generate a deck of playing cards',
8+
keywords: ['card', 'deck', 'picker'],
9+
component: () => import('./card-picker.vue'),
10+
icon: PlayCard,
11+
createdAt: new Date('2025-02-09'),
12+
});
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script setup lang="ts">
2+
import { computedRefreshable } from '@/composable/computedRefreshable';
3+
import { randIntFromInterval } from '@/utils/random';
4+
5+
const [coinFlip, refreshCoinFlip] = computedRefreshable(() => randIntFromInterval(0, 1) === 1 ? 'Heads' : 'Tails');
6+
</script>
7+
8+
<template>
9+
<div>
10+
<c-card title="Flip result">
11+
{{ coinFlip }}
12+
</c-card>
13+
<div flex justify-center>
14+
<c-button @click="refreshCoinFlip">
15+
Re flip
16+
</c-button>
17+
</div>
18+
</div>
19+
</template>

src/tools/coin-flipper/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Coin } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Coin Flipper',
6+
path: '/coin-flipper',
7+
description: 'Flip some coins',
8+
keywords: ['coin', 'flipper'],
9+
component: () => import('./coin-flipper.vue'),
10+
icon: Coin,
11+
createdAt: new Date('2025-02-09'),
12+
});

src/tools/dice-roller/dice-roller.vue

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<script setup lang="ts">
2+
import { DiceRoll } from '@dice-roller/rpg-dice-roller';
3+
import { computedRefreshable } from '@/composable/computedRefreshable';
4+
import { useCopy } from '@/composable/copy';
5+
import { useQueryParamOrStorage } from '@/composable/queryParams';
6+
7+
const dicesNotations = useQueryParamOrStorage({ name: 'dices', storageName: 'dice-roll:dices', defaultValue: '3d6' });
8+
const [diceRollResult, refreshDiceRollResult] = computedRefreshable(() => {
9+
try {
10+
return {
11+
error: '',
12+
roll: new DiceRoll(dicesNotations.value),
13+
};
14+
}
15+
catch (e: any) {
16+
return {
17+
error: e.toString(),
18+
roll: null,
19+
};
20+
}
21+
});
22+
23+
const diceRollString = computed(() => diceRollResult.value.roll?.output);
24+
25+
const { copy } = useCopy({ source: diceRollString, text: 'Dice Roll copied to the clipboard' });
26+
</script>
27+
28+
<template>
29+
<c-card>
30+
<c-input-text
31+
v-model:value="dicesNotations"
32+
label="Dice Roll Notations"
33+
placeholder="Dice configuration"
34+
mb-2
35+
/>
36+
<n-p>
37+
For more information about Dice Notation, see <n-a href="https://dice-roller.github.io/documentation/guide/notation/" target="_blank">
38+
here
39+
</n-a>
40+
</n-p>
41+
42+
<c-alert v-if="diceRollResult.error">
43+
{{ diceRollResult.error }}
44+
</c-alert>
45+
46+
<c-card v-if="!diceRollResult.error" title="Roll Result">
47+
<input-copyable :value="diceRollResult.roll.output" readonly placeholder="Dice Roll results" />
48+
<input-copyable :value="diceRollResult.roll.output" label="Total" readonly placeholder="Dice Roll total" />
49+
</c-card>
50+
51+
<c-card v-if="!diceRollResult.error" title="Dice Notation Stats">
52+
<input-copyable :value="diceRollResult.roll.minTotal" readonly label="Min Total" />
53+
<input-copyable :value="diceRollResult.roll.maxTotal" readonly label="Max Total" />
54+
<input-copyable :value="diceRollResult.roll.averageTotal" readonly label="Avg Total" />
55+
</c-card>
56+
57+
<div flex justify-center gap-3>
58+
<c-button @click="copy()">
59+
Copy roll result
60+
</c-button>
61+
<c-button @click="refreshDiceRollResult">
62+
Refresh roll
63+
</c-button>
64+
</div>
65+
</c-card>
66+
</template>

src/tools/dice-roller/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Dice } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Dice Roller',
6+
path: '/dice-roller',
7+
description: 'RPG Dice Roller using Dice Notation',
8+
keywords: ['dice', 'rng', 'rpg', 'roller'],
9+
component: () => import('./dice-roller.vue'),
10+
icon: Dice,
11+
createdAt: new Date('2025-02-09'),
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test.describe('Tool - Fortune wheel', () => {
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto('/fortune-wheel');
6+
});
7+
8+
test('Has correct title', async ({ page }) => {
9+
await expect(page).toHaveTitle('Fortune wheel - IT Tools');
10+
});
11+
12+
test('', async ({ page }) => {
13+
14+
});
15+
});
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<script setup lang="ts">
2+
import { FortuneWheel } from 'vue3-fortune-wheel';
3+
import type { Data, ImgParams } from 'vue3-fortune-wheel';
4+
5+
const base = import.meta.env.BASE_URL;
6+
7+
const choices = useQueryParamOrStorage({ name: 'choices', storageName: 'fortune-wheel:chs', defaultValue: '' });
8+
const spinDuration = useQueryParamOrStorage({ name: 'duration', storageName: 'fortune-wheel:dur', defaultValue: 5 });
9+
10+
const colors = [
11+
{ bgColor: '#FF5733', color: '#FFFFFF' },
12+
{ bgColor: '#2E86C1', color: '#FFFFFF' },
13+
{ bgColor: '#28B463', color: '#FFFFFF' },
14+
{ bgColor: '#F1C40F', color: '#000000' },
15+
{ bgColor: '#8E44AD', color: '#FFFFFF' },
16+
{ bgColor: '#E74C3C', color: '#FFFFFF' },
17+
{ bgColor: '#1ABC9C', color: '#000000' },
18+
{ bgColor: '#34495E', color: '#FFFFFF' },
19+
{ bgColor: '#D35400', color: '#FFFFFF' },
20+
{ bgColor: '#2C3E50', color: '#FFFFFF' },
21+
];
22+
23+
const wheel = ref<InstanceType<typeof FortuneWheel> | null>(null);
24+
const data = computed(() => {
25+
return (choices.value || '').split('\n')
26+
.filter(s => s && s !== '')
27+
.map((choice, index) => ({ id: index + 1, value: choice, ...colors[index % colors.length] }));
28+
});
29+
30+
const logo: ImgParams = {
31+
src: `${base}logo.png`,
32+
width: 100,
33+
height: 100,
34+
};
35+
36+
const randomGift = computed(() => {
37+
return Math.floor(Math.random() * data.value.length) + 1;
38+
});
39+
40+
const result = ref<Data>(null);
41+
function done(result: Data) {
42+
result.value = result;
43+
}
44+
45+
function restart() {
46+
wheel.value?.spin();
47+
}
48+
</script>
49+
50+
<template>
51+
<div>
52+
<n-form-item label="Spin Duration (seconds)">
53+
<n-input-number v-model:value="spinDuration" :min="1" />
54+
</n-form-item>
55+
56+
<c-input-text
57+
v-model:value="choices"
58+
label="Wheel choices (one per line)"
59+
placeholder="Wheel choices (one per line)"
60+
/>
61+
62+
<n-divider />
63+
64+
<FortuneWheel
65+
ref="wheel"
66+
v-model="randomGift"
67+
middle-circle
68+
:img-params="logo"
69+
:anim-duration="spinDuration * 1000"
70+
:data="data"
71+
@done="done"
72+
/>
73+
74+
<c-button @click="restart()">
75+
Restart
76+
</c-button>
77+
78+
<n-divider />
79+
80+
<c-card v-if="result" title="Result">
81+
<input-copyable :value="result.value" readonly />
82+
</c-card>
83+
</div>
84+
</template>

src/tools/fortune-wheel/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ArrowsShuffle } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Fortune wheel',
6+
path: '/fortune-wheel',
7+
description: '',
8+
keywords: ['fortune', 'wheel'],
9+
component: () => import('./fortune-wheel.vue'),
10+
icon: ArrowsShuffle,
11+
createdAt: new Date('2025-02-24'),
12+
});

src/tools/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { tool as base64FileConverter } from './base64-file-converter';
22
import { tool as base64StringConverter } from './base64-string-converter';
33
import { tool as basicAuthGenerator } from './basic-auth-generator';
44
import { tool as emailNormalizer } from './email-normalizer';
5+
import { tool as fortuneWheel } from './fortune-wheel';
6+
import { tool as cardPicker } from './card-picker';
7+
import { tool as coinFlipper } from './coin-flipper';
8+
import { tool as diceRoller } from './dice-roller';
59

610
import { tool as asciiTextDrawer } from './ascii-text-drawer';
711

@@ -169,6 +173,10 @@ export const toolsByCategory: ToolCategory[] = [
169173
{
170174
name: 'Math',
171175
components: [mathEvaluator, etaCalculator, percentageCalculator],
176+
diceRoller,
177+
coinFlipper,
178+
cardPicker,
179+
fortuneWheel,
172180
},
173181
{
174182
name: 'Measurement',

0 commit comments

Comments
 (0)