🎉 Commits project

This commit is contained in:
Daniel Svitan 2024-03-04 18:35:01 +01:00
commit 75aed63005
20 changed files with 448 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
bun.lockb
package-lock.json
.vscode/

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/png" href="/binary.png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Binary</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<style>
html, body {
margin: 0;
padding: 0;
}
</style>

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "binary",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.4.19"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.18",
"postcss": "^8.4.35",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.1",
"typescript": "^5.2.2",
"vite": "^5.1.4",
"vue-tsc": "^1.8.27"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
public/binary.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

3
public/chevron.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#d1fae5">
<path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z"/>
</svg>

After

Width:  |  Height:  |  Size: 180 B

123
src/App.vue Normal file
View File

@ -0,0 +1,123 @@
<template>
<div class="flex items-center justify-center w-screen h-screen bg-gray-900">
<div class="flex items-center justify-center flex-col gap-y-2 w-72">
<div class="flex gap-x-2">
<button v-if="level > 0" @click="changeLevel(-1)">
<img src="/chevron.svg" alt="Arrow" class="rotate-90" />
</button>
<p class="text-emerald-100 text-xl font-bold">
{{ levels[level].name }}
</p>
<button v-if="level < levels.length - 1" @click="changeLevel(+1)">
<img src="/chevron.svg" alt="Arrow" class="-rotate-90" />
</button>
</div>
<div v-if="problem" class="flex">
<div class="flex items-end justify-center flex-col h-full gap-y-2 p-2">
<Number
:number="problem.a"
:options="{ spacing: +spacing, fill: fill }"
class="mx-2"
/>
<Number
:number="problem.b"
:options="{
spacing: +spacing,
fill: fill,
prefix: problem.operator,
}"
class="mx-2"
/>
<div class="w-full h-px bg-emerald-100" />
<Number
:data-shown="shown"
:number="problem.answer"
:options="{ spacing: +spacing, fill: fill }"
class="mx-2 data-[shown=false]:opacity-0"
/>
</div>
</div>
<div class="flex w-full items-center justify-between mt-4">
<button @click="() => (openSettings = !openSettings)" class="ml-4">
<img
:data-open="openSettings"
src="/chevron.svg"
alt="Chevron"
class="data-[open=true]:rotate-180"
/>
</button>
<button
v-if="shown"
@click="next"
class="text-emerald-100 border border-emerald-100 rounded-full px-4 py-1"
>
Next
</button>
<button
v-else
@click="show"
class="text-emerald-100 border border-emerald-100 rounded-full px-4 py-1"
>
Show
</button>
</div>
<div v-if="openSettings" class="flex gap-x-2 mt-4">
<label for="spacing" class="text-emerald-100">Spacing:</label>
<select
v-model="spacing"
name="spacing"
id="spacing"
class="text-emerald-100 bg-transparent"
>
<option value="2">2</option>
<option value="3">3</option>
<option value="4" selected>4</option>
</select>
</div>
<div v-if="openSettings" class="flex gap-x-2">
<input v-model="fill" type="checkbox" id="fill" name="fill" checked />
<label for="fill" class="text-emerald-100">Fill</label>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { levels, Problem } from "./composables/levels.ts";
import Number from "./components/Number.vue";
const level = ref(0);
const problem = ref<Problem | undefined>();
const shown = ref(false);
const openSettings = ref(false);
const spacing = ref("4");
const fill = ref(true);
function next() {
problem.value = levels[level.value].generate();
shown.value = false;
}
function show() {
shown.value = true;
}
function changeLevel(by: number) {
level.value += by;
next();
}
onMounted(next);
</script>

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

11
src/components/Bit.vue Normal file
View File

@ -0,0 +1,11 @@
<template>
<div v-if="bit === '1'" class="w-2 h-8 bg-emerald-100 rounded-t-full" />
<div v-else-if="bit === '0'" class="w-2 h-3 bg-emerald-100 rounded-t-full" />
<p v-else class="text-emerald-100 text-3xl">{{ bit }}</p>
</template>
<script setup lang="ts">
defineProps<{
bit: string;
}>();
</script>

15
src/components/Group.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<div
class="flex items-end gap-x-1 border-b-[0.375rem] border-b-emerald-100 border-solid w-fit rounded-md"
>
<Bit v-for="bit in group" :bit="bit" />
</div>
</template>
<script setup lang="ts">
import Bit from "./Bit.vue";
defineProps<{
group: string[];
}>();
</script>

59
src/components/Number.vue Normal file
View File

@ -0,0 +1,59 @@
<template>
<div class="flex gap-x-2 items-end justify-end w-fit h-[2.375rem]">
<p v-if="coefficient === -1" class="text-emerald-100 text-3xl">-</p>
<p v-if="options.prefix" class="text-emerald-100 text-3xl">
{{ options.prefix }}
</p>
<Group v-for="group in binary" :group="group" />
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import Group from "./Group.vue";
interface Options {
spacing: number | undefined;
fill: boolean | undefined;
prefix: string | undefined;
}
const props = defineProps<{
number: number;
options: Options;
}>();
const coefficient = computed(() => {
return Math.abs(props.number) / props.number; // 1 or -1 based on the number
});
const binary = computed(() => {
const string = Number(Math.abs(props.number)).toString(2);
const spacing = props.options.spacing || 4;
const fill = props.options.fill ?? true;
const result: string[][] = [];
let chunk: string[] = [];
for (let i = 0; i < string.length; i++) {
if (i % spacing === 0 && i !== 0) {
result.push(chunk.reverse());
chunk = [];
}
chunk.push(string[string.length - i - 1]);
}
if (chunk.length !== 0) {
if (fill) {
while (chunk.length !== spacing) {
chunk.push("0");
}
}
result.push(chunk.reverse());
}
return result.reverse();
});
</script>

80
src/composables/levels.ts Normal file
View File

@ -0,0 +1,80 @@
export interface Level {
name: string;
generate: () => Problem;
}
export interface Problem {
a: number;
operator: string;
b: number;
answer: number;
}
export function random(max: number) {
return Math.floor(Math.random() * max);
}
function AdditionLevel(level: 1 | 2 | 3): Level {
return {
name: `Addition Level ${level}`,
generate: () => {
const a = random(2 ** (4 * level));
const b = random(2 ** (4 * level));
const answer = a + b;
return { a, operator: "+", b, answer };
},
};
}
function SubtractionLevel(level: 1 | 2 | 3): Level {
return {
name: `Subtraction Level ${level}`,
generate: () => {
const a = random(2 ** (4 * level));
const b = random(2 ** (4 * level));
const answer = a - b;
return { a, operator: "-", b, answer };
},
};
}
function MultiplicationLevel(level: 1 | 2): Level {
return {
name: `Multiplication Level ${level}`,
generate: () => {
const a = random(2 ** (4 * level));
const b = random(2 ** (4 * level));
const answer = a * b;
return { a, operator: "*", b, answer };
},
};
}
function DivisionLevel(level: 1 | 2): Level {
return {
name: `Division Level ${level}`,
generate: () => {
const a = random(2 ** (4 * level));
const b = random(2 ** (4 * level));
const answer = a / b;
return { a, operator: "/", b, answer };
},
};
}
export const levels: Level[] = [
AdditionLevel(1),
SubtractionLevel(1),
AdditionLevel(2),
SubtractionLevel(2),
AdditionLevel(3),
SubtractionLevel(3),
MultiplicationLevel(1),
DivisionLevel(1),
MultiplicationLevel(2),
DivisionLevel(2),
];

3
src/index.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

5
src/main.ts Normal file
View File

@ -0,0 +1,5 @@
import { createApp } from "vue";
import App from "./App.vue";
import "./index.css";
createApp(App).mount("#app");

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

8
tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

25
tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

11
tsconfig.node.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

7
vite.config.ts Normal file
View File

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