simulator: random bet in proportion to prob, delete vue simulator

This commit is contained in:
mantikoros 2021-12-15 01:06:21 -06:00
parent 344b06124f
commit 104257001b
30 changed files with 9 additions and 2850 deletions

View File

@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@ -1,5 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local

View File

@ -1,8 +0,0 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"trailingComma": "es5",
"vueIndentScriptAndStyle": false,
"semi": false
}

View File

@ -1,3 +0,0 @@
{
"recommendations": ["johnsoncodehk.volar"]
}

View File

@ -1,35 +0,0 @@
# Austin's Starter Project Template
## Usage
1. Clone this repository
2. `yarn`
3. `yarn dev` to start development
4. Setup Firebase
## Setting up Firebase
1. Go to https://console.firebase.google.com/ and create a new project
1. Go to Project Settings and add Firebase to your web app
a. Copy firebaseConfig to `src/network/init.ts`
1. Create a Firestore Database
a. Create a new collection called `users`
b. Set up the security rules (see `src/network/example-rules.txt`)
1. Enable Authetication & Google auth
## Built on top of
- [VueJS](https://v3.vuejs.org/guide/introduction.html) on the frontend
- [Vite](https://vitejs.dev/) for bundling and serving
- [TailwindCSS](https://tailwindcss.com/) for styling
- [WindiCSS](https://windicss.org/) specifically for faster loading times
- [DaisyUI](https://daisyui.com/) for a default set of components
- [Firestore](https://firebase.google.com/docs/firestore) for the database
- [Firebase Auth](https://firebase.google.com/docs/auth) for login
### TODOs:
- [Netlify](https://www.netlify.com/) for hosting
- [Stripe](https://stripe.com/) for payments
- [Mailjet](https://www.mailjet.com/) for marketing & transactional emails

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mantic Markets Simulator</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,23 +0,0 @@
{
"name": "starter-project",
"version": "0.0.0",
"scripts": {
"dev": "vite --open",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"chart.js": "^3.6.1",
"daisyui": "^1.16.0",
"firebase": "^9.4.1",
"vue": "^3.2.16"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.9.3",
"typescript": "^4.4.3",
"vite": "^2.6.4",
"vite-plugin-windicss": "^1.4.12",
"vue-tsc": "^0.3.0",
"windicss": "^3.2.0"
}
}

View File

@ -1 +0,0 @@
dump

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 69.218 70.506" xmlns="http://www.w3.org/2000/svg" fill="none">
<defs>
<linearGradient data-v-fde0c5aa="" gradientTransform="rotate(25)" id="90bb4dbd-5a19-4e7a-8260-84b7646559c5" x1="0%" y1="0%" x2="100%" y2="0%">
<stop data-v-fde0c5aa="" offset="0" stop-color="#5A5A2D" stop-opacity="1"/>
<stop data-v-fde0c5aa="" offset="1" stop-color="#0DE30D" stop-opacity="1"/>
</linearGradient>
</defs>
<g data-v-fde0c5aa="" id="5c4df6fb-50cb-4581-b5fd-961d2467c672" stroke="none" fill="url(#90bb4dbd-5a19-4e7a-8260-84b7646559c5)" transform="matrix(0.801904, 0, 0, 0.801904, 7.879115, 8.276814)">
<path d="M29.347 10.688A16.012 16.012 0 0 1 34 10c1.604 0 3.168.238 4.652.688C41.374 3.578 47.976 0 58 0a2 2 0 1 1 0 4c-8.574 0-13.645 2.759-15.688 8.325a16.03 16.03 0 0 1 4.399 3.956C49.892 17.358 52 21.399 52 26c0 4.672-2.173 8.766-5.437 9.767C43.11 40.917 37.801 45 34 45s-9.11-4.083-12.563-9.233C18.173 34.767 16 30.672 16 26c0-4.6 2.108-8.642 5.29-9.72a16.03 16.03 0 0 1 4.398-3.955C23.645 6.76 18.573 4 9.999 4a2 2 0 1 1 0-4c10.024 0 16.627 3.578 19.348 10.688zM34 41c1.894 0 5.359-2.493 8.068-5.87C39.58 33.547 38 29.984 38 26c0-3.898 1.513-7.394 3.91-9.026A11.966 11.966 0 0 0 34 14c-2.972 0-5.76 1.087-7.91 2.974C28.488 18.606 30 22.102 30 26c0 3.983-1.58 7.546-4.068 9.13C28.642 38.508 32.106 41 34 41zm-11-9c1.408 0 3-2.547 3-6s-1.592-6-3-6-3 2.547-3 6 1.592 6 3 6zm22 0c1.408 0 3-2.547 3-6s-1.592-6-3-6-3 2.547-3 6 1.592 6 3 6zM6.883 66.673a2 2 0 0 1-3.297.741C1.13 64.96 0 60.813 0 55c0-6.052 3.982-12.206 11.734-18.548a2 2 0 0 1 3.029.604l15 28a2 2 0 1 1-3.526 1.888l-8.755-16.342c-4.18 2.733-7.74 8.065-10.599 16.07zm5.526-25.54C6.75 46.174 4 50.818 4 55c0 2.566.243 4.666.7 6.304 2.934-6.756 6.547-11.518 10.887-14.24l-3.178-5.932zm48.708 25.54c-2.86-8.006-6.418-13.338-10.599-16.071l-8.755 16.342a2 2 0 1 1-3.526-1.888l15-28a2 2 0 0 1 3.03-.604C64.018 42.794 68 48.948 68 55c0 5.813-1.13 9.96-3.585 12.414a2 2 0 0 1-3.298-.741zm-5.526-25.54l-3.178 5.932c4.34 2.721 7.954 7.483 10.887 14.24.457-1.639.7-3.739.7-6.305 0-4.18-2.75-8.825-8.409-13.868z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,15 +0,0 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import HelloWorld from './components/HelloWorld.vue'
import LoginExample from './components/LoginExample.vue'
import Simulator from './components/Simulator.vue'
import Header from './components/Header.vue'
</script>
<template>
<Header />
<Simulator />
</template>
<style></style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,40 +0,0 @@
<!-- Partial table row for a given entry -->
<template>
<!-- Invalid bid -->
<template v-if="!entry">
<td>N/A</td>
<td>N/A</td>
<td>N/A</td>
<td>N/A</td>
</template>
<!-- Seed bids -->
<template v-else-if="entry.yesBid && entry.noBid">
<td>N/A</td>
<td>{{ entry.prob.toFixed(2) }}</td>
<td>N/A</td>
<td>N/A</td>
</template>
<!-- Yes Bid -->
<template v-else-if="entry.yesBid">
<td>{{ entry.yesWeight.toFixed(2) }}</td>
<td>{{ entry.prob.toFixed(2) }}</td>
<td>{{ entry.yesPayout.toFixed(2) }}</td>
<td>{{ (entry.yesReturn * 100).toFixed(2) }}%</td>
</template>
<!-- No Bid -->
<template v-else-if="entry.noBid">
<td>{{ entry.noWeight.toFixed(2) }}</td>
<td>{{ entry.prob.toFixed(2) }}</td>
<td>{{ entry.noPayout.toFixed(2) }}</td>
<td>{{ (entry.noReturn * 100).toFixed(2) }}%</td>
</template>
</template>
<script setup lang="ts">
import { PropType } from '@vue/runtime-core'
import { Entry } from './entries'
defineProps({
entry: Object as PropType<Entry>,
})
</script>

View File

@ -1,56 +0,0 @@
<template>
<nav
class="
relative
mx-auto
flex
items-center
justify-between
px-12
bg-blue-gray-700
h-16
"
aria-label="Global"
>
<div class="flex items-center flex-1">
<div class="flex items-center justify-between w-full md:w-auto">
<a
href="https://mantic.markets"
class="inline-grid grid-flow-col align-items-center h-6 sm:h-10"
>
<img
class="w-auto h-6 sm:h-10 inline-block mr-3"
src="/logo-icon.svg"
/>
<span class="text-white font-logo">Mantic Markets</span>
</a>
</div>
<div class="space-x-8 md:flex md:ml-10">
<a
v-for="item in [
{
name: 'About',
href: 'https://mantic.notion.site/About-Mantic-Markets-09bdde9044614e62a27477b4b1bf77ea',
},
{ name: 'Simulator', href: 'https://simulator.mantic.markets' },
]"
:key="item.name"
:href="item.href"
class="text-base font-medium text-white hover:text-gray-300"
>
{{ item.name }}
</a>
</div>
</div>
</nav>
</template>
<style>
@import url('https://fonts.googleapis.com/css2?family=Major+Mono+Display&display=swap');
.font-logo {
font-family: 'Major Mono Display', monospace;
text-transform: lowercase;
font-size: 1.5rem;
}
</style>

View File

@ -1,54 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<p>
Recommended IDE setup:
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
+
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
</p>
<p>See <code>README.md</code> for more information.</p>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">
Vite Docs
</a>
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
</p>
<button type="button" class="btn btn-primary" @click="count++">
count is: {{ count }}
</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<style scoped>
a {
color: #42b983;
}
label {
margin: 0 0.5em;
font-weight: bold;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 4px;
color: #304455;
}
</style>

View File

@ -1,30 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import {
firebaseLogin,
firebaseLogout,
listenForLogin,
User,
} from '../network/users'
const user = ref({} as User)
listenForLogin((u) => {
user.value = u
})
function objectEmpty(obj: any) {
// Functional equivalent:
return Object.keys(obj).length === 0
}
</script>
<template>
<div v-if="objectEmpty(user)">
<p>Not logged in!</p>
<button class="btn btn-primary" @click="firebaseLogin">Login</button>
</div>
<div v-else>
<p>Logged in as {{ user.name }}</p>
<button class="btn btn-secondary" @click="firebaseLogout">Logout</button>
</div>
</template>

View File

@ -1,224 +0,0 @@
<template>
<div class="overflow-x-auto px-12 mt-8">
<!-- Two-column layout (on large screen sizes) -->
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
<!-- Left column -->
<div>
<h1 class="text-2xl font-bold text-gray-600 mb-8">
Dynamic Parimutuel Market Simulator
</h1>
<label>Simulation step: {{ steps }} </label>
<input
class="range"
type="range"
v-model.number="steps"
min="1"
:max="bids.length"
/>
<!-- Table to enter a new bid -->
<table class="table table-compact my-8 w-full">
<thead>
<tr>
<th>Order #</th>
<th>Type</th>
<th>Bid</th>
<th>Weight</th>
<th>Probability</th>
<th>Payout</th>
<th>Return</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<th>{{ steps + 1 }}</th>
<td>
<div
@click="toggleBidType"
class="badge clickable"
:class="newBidType == 'YES' ? 'badge-success' : 'badge-ghost'"
>
YES
</div>
<br />
<div
@click="toggleBidType"
class="badge clickable"
:class="newBidType == 'NO' ? 'badge-error' : 'badge-ghost'"
>
NO
</div>
</td>
<td>
<input
type="number"
v-model.number="newBid"
placeholder="0"
class="input input-bordered"
@focus="$event.target.select()"
@keyup.enter="submitBid"
/>
</td>
<EntryRow :entry="nextEntry" />
<td>
<button
class="btn btn-primary"
:disabled="newBid <= 0"
@click="submitBid"
>
Submit
</button>
</td>
</tr>
</tbody>
</table>
<!-- List of historical bids -->
<table class="table table-compact w-full">
<thead>
<tr>
<th>Order #</th>
<th>Type</th>
<th>Bid</th>
<th>Weight</th>
<th>Probability</th>
<th>Payout</th>
<th>Return</th>
</tr>
</thead>
<tbody>
<tr v-for="(entry, i) in entries">
<th>{{ i + 1 }}</th>
<template v-if="entry.yesBid && entry.noBid">
<td><div class="badge">SEED</div></td>
<td>{{ entry.yesBid }} / {{ entry.noBid }}</td>
</template>
<template v-else-if="entry.yesBid">
<td><div class="badge badge-success">YES</div></td>
<td>{{ entry.yesBid }}</td>
</template>
<template v-else>
<td><div class="badge badge-error">NO</div></td>
<td>{{ entry.noBid }}</td>
</template>
<EntryRow :entry="entry" />
</tr>
</tbody>
</table>
</div>
<!-- Right column -->
<div>
<h1 class="text-2xl font-bold text-gray-600 mb-8">
Probability of
<div class="badge badge-success text-2xl h-8 w-18">YES</div>
</h1>
<canvas id="simChart" width="400" height="400"></canvas>
</div>
</div>
</div>
</template>
<style scoped>
.clickable {
cursor: pointer;
}
div {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
}
</style>
<script setup lang="ts">
import Chart from 'chart.js/auto'
import { bids as sampleBids } from './sample-bids'
import { makeEntries } from './entries'
import { ref, computed } from '@vue/reactivity'
import { onMounted, watch } from '@vue/runtime-core'
import EntryRow from './EntryRow.vue'
// Copy over the sample bids to seed the simulation
const bids = sampleBids.slice()
// UI parameters
const steps = ref(10)
// Computed variables: stop the simulation at the appropriate number of steps
const entries = computed(() => makeEntries(bids.slice(0, steps.value)))
// Graph the probabilities over time
const probs = computed(() => entries.value.map((entry) => entry.prob))
onMounted(initChart)
watch(steps, renderChart)
let chart: Chart
function initChart() {
const ctx = document.getElementById('simChart')
chart = new Chart(ctx as any, {
type: 'line',
data: {
labels: [...Array(steps.value).keys()],
datasets: [
{
label: 'Implied probability',
data: probs.value,
borderColor: 'rgb(75, 192, 192)',
},
],
},
options: {
plugins: {
legend: {
display: false,
},
},
},
})
}
function renderChart() {
chart.data.labels = [...Array(steps.value).keys()]
chart.data.datasets[0].data = probs.value
chart.update()
}
// Add new data to the simulation, at the current step
const newBid = ref(0)
const newBidType = ref('YES')
function toggleBidType() {
newBidType.value = newBidType.value === 'YES' ? 'NO' : 'YES'
}
function makeBid(type: string, bid: number) {
return {
yesBid: newBidType.value == 'YES' ? newBid.value : 0,
noBid: newBidType.value == 'YES' ? 0 : newBid.value,
}
}
function submitBid() {
if (newBid.value <= 0) return
const bid = makeBid(newBidType.value, newBid.value)
bids.splice(steps.value, 0, bid)
steps.value++
newBid.value = 0
}
// Preview the next bid before it's added
const nextEntry = computed(() => {
if (newBid.value) {
const nextBid = makeBid(newBidType.value, newBid.value)
const fakeBids = [...bids.slice(0, steps.value), nextBid]
const entries = makeEntries(fakeBids)
return entries[entries.length - 1]
}
return null
})
</script>

View File

@ -1,60 +0,0 @@
type Bid = { yesBid: number; noBid: number }
// An entry has a yes/no for bid, weight, payout, return. Also a current probability
export type Entry = {
yesBid: number
noBid: number
yesWeight: number
noWeight: number
yesPayout: number
noPayout: number
yesReturn: number
noReturn: number
prob: number
}
export function makeEntries(bids: Bid[]): Entry[] {
const entries: Entry[] = []
let yesPot = 0
let noPot = 0
// First pass: calculate all the weights
for (const { yesBid, noBid } of bids) {
const yesWeight =
noPot * (Math.log(yesBid + yesPot) - Math.log(yesPot)) || 0
const noWeight = yesPot * (Math.log(noBid + noPot) - Math.log(noPot)) || 0
// Note: Need to calculate weights BEFORE updating pot
yesPot += yesBid
noPot += noBid
const prob = yesPot / (yesPot + noPot)
entries.push({
yesBid,
noBid,
yesWeight,
noWeight,
prob,
// To be filled in below
yesPayout: 0,
noPayout: 0,
yesReturn: 0,
noReturn: 0,
})
}
const YES_SEED = bids[0].yesBid
const NO_SEED = bids[0].noBid
const yesWeightsSum = entries.reduce((sum, entry) => sum + entry.yesWeight, 0)
const noWeightsSum = entries.reduce((sum, entry) => sum + entry.noWeight, 0)
// Second pass: calculate all the payouts
for (const entry of entries) {
const { yesBid, noBid, yesWeight, noWeight } = entry
// Payout: You get your initial bid back, as well as your share of the
// (noPot - seed) according to your yesWeight
entry.yesPayout = yesBid + (yesWeight / yesWeightsSum) * (noPot - NO_SEED)
entry.noPayout = noBid + (noWeight / noWeightsSum) * (yesPot - YES_SEED)
entry.yesReturn = (entry.yesPayout - yesBid) / yesBid
entry.noReturn = (entry.noPayout - noBid) / noBid
}
return entries
}

View File

@ -1,58 +0,0 @@
const data = `1,9
8,
,1
1,
,1
1,
,5
5,
,5
5,
,1
1,
100,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,
,10
10,`
// Parse data into Yes/No orders
// E.g. `8,\n,1\n1,` =>
// [{yesBid: 8, noBid: 0}, {yesBid: 0, noBid: 1}, {yesBid: 1, noBid: 0}]
export const bids = data.split('\n').map((line) => {
const [yesBid, noBid] = line.split(',')
return {
yesBid: parseInt(yesBid || '0'),
noBid: parseInt(noBid || '0'),
}
})

View File

@ -1,8 +0,0 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -1,5 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
import 'virtual:windi.css'
createApp(App).mount('#app')

View File

@ -1,22 +0,0 @@
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() {
// Hardcode admins, e.g.:
return request.auth.uid == 'mvLxCEDq7YSMmatuLZQSxRtsBeh2' || // akrolsmir@gmail.com
request.auth.uid == 'rYFCLWCzSnSjGuSjpLOtWJ1Ewof1' // abc.sinclair@gmail.com
}
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId || isAdmin();
allow create: if request.auth != null || isAdmin();
}
match /rooms/{document=**} {
allow read, create, update: if true;
}
}
}

View File

@ -1,21 +0,0 @@
import { initializeApp } from 'firebase/app'
const firebaseConfig = {
apiKey: 'AIzaSyB1p-PbBT1EcCfJoGtSeZbxlYVvagOoTaY',
authDomain: 'starter-project-7fba2.firebaseapp.com',
projectId: 'starter-project-7fba2',
storageBucket: 'starter-project-7fba2.appspot.com',
messagingSenderId: '751858706800',
appId: '1:751858706800:web:1a69cfbd58c7acbafd87b5',
measurementId: 'G-HPK27K51WM',
}
// Initialize Firebase
export const app = initializeApp(firebaseConfig)
try {
// Note: this is still throwing a console error atm...
import('firebase/analytics').then((analytics) => {
analytics.getAnalytics(app)
})
} catch (e) {
console.warn('Analytics were blocked')
}

View File

@ -1,43 +0,0 @@
import { app } from './init'
import { getFirestore, onSnapshot, doc, updateDoc } from 'firebase/firestore'
import { Unsubscribe } from '@firebase/util'
const db = getFirestore(app)
// Example: Listening for realtime changes to a document
let unsubscribe: Unsubscribe
export function listenRoom(roomId: string, onChange: (room: Room) => void) {
if (unsubscribe) {
unsubscribe()
}
const roomRef = doc(db, 'rooms', roomId)
unsubscribe = onSnapshot(roomRef, (snapshot) => {
const room = snapshot.data() as Room
if (room) {
onChange(room)
}
})
}
// Example: Pushing an update to a document
export async function createMessage(roomId: string, message: Message) {
const roomRef = doc(db, 'rooms', roomId)
await updateDoc(roomRef, {
[`messages.${message.id}`]: message,
[`lastKeystrokes.${message.author.id}`]: message.timestamp,
})
}
export function registerUser(roomId: string, user: Author) {
const roomRef = doc(db, 'rooms', roomId)
return updateDoc(roomRef, {
[`users.${user.id}`]: user,
})
}
export async function registerKeystroke(roomId: string, userId: string) {
const roomRef = doc(db, 'rooms', roomId)
await updateDoc(roomRef, {
[`lastKeystrokes.${userId}`]: Date.now(),
})
}

View File

@ -1,92 +0,0 @@
import { app } from './init'
import { getFirestore, doc, setDoc, getDoc } from 'firebase/firestore'
import { getAuth } from 'firebase/auth'
import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage'
import {
onAuthStateChanged,
GoogleAuthProvider,
signInWithPopup,
} from 'firebase/auth'
export type User = {
id: string
name: string
avatarUrl: string
// Not needed for chat view:
email: string
description: string
createTime: number
lastUpdateTime: number
}
const db = getFirestore(app)
export const auth = getAuth(app)
export async function getUser(userId: string) {
const docSnap = await getDoc(doc(db, 'users', userId))
return docSnap.data() as User
}
export async function setUser(userId: string, user: User) {
await setDoc(doc(db, 'users', userId), user)
}
const CACHED_USER_KEY = 'CACHED_USER_KEY'
export function listenForLogin(onUser: (user: User) => void) {
// Immediately load any persisted user object from browser cache.
const cachedUser = localStorage.getItem(CACHED_USER_KEY)
if (cachedUser) {
onUser(JSON.parse(cachedUser))
}
onAuthStateChanged(auth, async (user) => {
if (user) {
let fetchedUser = await getUser(user.uid)
if (!fetchedUser) {
// User just created an account; save them to our database.
fetchedUser = {
id: user.uid,
name: user.displayName || 'Default Name',
avatarUrl: user.photoURL || '',
email: user.email || 'default@blah.com',
description: '',
createTime: Date.now(),
lastUpdateTime: Date.now(),
}
await setUser(user.uid, fetchedUser)
}
onUser(fetchedUser)
// Persist to local storage, to reduce login blink next time.
// Note: Cap on localStorage size is ~5mb
localStorage.setItem(CACHED_USER_KEY, JSON.stringify(fetchedUser))
} else {
// User logged out; reset to the empty object
onUser({} as User)
}
})
}
export async function firebaseLogin() {
const provider = new GoogleAuthProvider()
signInWithPopup(auth, provider)
}
export async function firebaseLogout() {
auth.signOut()
localStorage.removeItem(CACHED_USER_KEY)
}
const storage = getStorage(app)
// Example: uploadData('avatars/ajfi8iejsf.png', data)
export async function uploadData(
path: string,
data: ArrayBuffer | Blob | Uint8Array
) {
const uploadRef = ref(storage, path)
// Uploaded files should be cached for 1 day, then revalidated
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
const metadata = { cacheControl: 'public, max-age=86400, must-revalidate' }
await uploadBytes(uploadRef, data, metadata)
return await getDownloadURL(uploadRef)
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@ -1,8 +0,0 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import WindiCSS from 'vite-plugin-windicss'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), WindiCSS()],
})

View File

@ -1,6 +0,0 @@
import { defineConfig } from 'vite'
import { transform } from 'windicss/helpers'
export default defineConfig({
plugins: [transform('daisyui')],
})

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,9 @@ import React, { useMemo, useState } from 'react'
import { DatumValue } from '@nivo/core'
import { ResponsiveLine } from '@nivo/line'
import { Entry, makeEntries } from '../../lib/simulator/entries'
import { Header } from '../../components/header'
import { Col } from '../../components/layout/col'
import { Entry, makeEntries } from '../lib/simulator/entries'
import { Header } from '../components/header'
import { Col } from '../components/layout/col'
function TableBody(props: { entries: Entry[] }) {
return (
@ -149,7 +149,11 @@ function NewBidTable(props: {
function randomBid() {
const bidType = Math.random() < 0.5 ? 'YES' : 'NO'
const amount = Math.round(Math.random() * 500)
const p = bidType === 'YES'
? nextEntry.prob
: 1 - nextEntry.prob
const amount = Math.round(p * Math.random() * 300) + 1
const bid = makeBid(bidType, amount)
bids.splice(steps, 0, bid)
@ -236,7 +240,7 @@ function NewBidTable(props: {
// Show a hello world React page
export default function Simulator() {
const [steps, setSteps] = useState(1)
const [bids, setBids] = useState([{ yesBid: 600, noBid: 400 }])
const [bids, setBids] = useState([{ yesBid: 550, noBid: 450 }])
const entries = useMemo(
() => makeEntries(bids.slice(0, steps)),