This commit is contained in:
2026-06-24 15:10:50 +02:00
commit a3e7512f95
212 changed files with 212927 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
node_modules
Dockerfile
.git
android
DatabaseStructure.drawio
capacitor.config.json
ionic.config.json
+11
View File
@@ -0,0 +1,11 @@
REACT_APP_FIREBASE_API_KEY=
REACT_APP_FIREBASE_AUTH_DOMAIN=
REACT_APP_FIREBASE_PROJECT_ID=
REACT_APP_FIREBASE_STORAGE_BUCKET=
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=
REACT_APP_FIREBASE_APP_ID=
REACT_APP_GA_TRACKING_ID_WEB=
REACT_APP_ADMIN_UID=
REACT_APP_FB_PAGE_ID=
REACT_APP_FB_APP_ID=
+28
View File
@@ -0,0 +1,28 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.yarn
# testing
/coverage
# production
/build
/android
/dist
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local Netlify folder
.netlify
+1
View File
@@ -0,0 +1 @@
nodejs 16.1.0
+2
View File
@@ -0,0 +1,2 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.6.4.cjs
+146
View File
@@ -0,0 +1,146 @@
# Contributing to Debaters' toolkit
Looking forward to contribute to Debaters' toolkit? Thanks! Here's a step-by-step guide as to how to rebuild this project from scratch.
## Setting up Firebase
1. Go to [Firebase Console](https://console.firebase.google.com/) and click on 'Add Project'. Remember to switch off 'Enable Google Analytics for this project' after naming your project.
2. Click on the </> button in 'Project Overview' to create a new web app. Don't check 'Also set up Firebase Hosting for this app.'
3. After clicking 'Register App', you will find something like this:
```sh
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "XXXXXXXXXXXXXXXXXX-XXXX_XXXXX-XXXXXXXXX",
authDomain: "XXXX.firebaseapp.com",
projectId: "XXXX",
storageBucket: "XXXX.appspot.com",
messagingSenderId: "XXXXXXXXXXXXX",
appId: "1:XXXXXXXXXXXXX:web:XXXXXXXXXXXXXXXXXXXXXX"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
```
Copy the values inside firebaseConfig (without quotation marks) and paste them correspondingly to the [.env.example](./.env.example) file in the root directory of the project such that the first 6 lines of [.env.example](./.env.example) looks something like this:
```sh
REACT_APP_FIREBASE_API_KEY=XXXXXXXXXXXXXXXXXX-XXXX_XXXXX-XXXXXXXXX
REACT_APP_FIREBASE_AUTH_DOMAIN=XXXX.firebaseapp.com
REACT_APP_FIREBASE_PROJECT_ID=XXXX
REACT_APP_FIREBASE_STORAGE_BUCKET=XXXX.appspot.com
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=XXXXXXXXXXXXX
REACT_APP_FIREBASE_APP_ID=1:XXXXXXXXXXXXX:web:XXXXXXXXXXXXXXXXXXXXXX
```
4. After adding a web app to your Firebase project, in the [Firebase Console](https://console.firebase.google.com/), click on 'Create database'. Select 'start in production mode' and then select a Cloud Firestore location closest to your geolocation.
5. Go to the Authentication tab in the [Firebase Console](https://console.firebase.google.com/) and click on 'Get started'. Click on 'Email/Password' and enable it. Don't enable 'Email link (passwordless sign-in)'.
6. In the 'Users' section in 'Authentication', click on 'Add user'. Provide an email and password for this user, which will be used as the admin account. Copy the User UID afterwards and paste it into 'REACT_APP_ADMIN_UID' inside [.env.example](./.env.example).
7. Go to the 'Rules' section in 'Firestore Database'. Change the rules to the following:
```sh
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /requests/{requestID} {
allow read: if request.auth.uid == "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
allow write: if true;
}
match /motions/{motionID} {
allow read: if true;
allow write: if request.auth.uid == "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
}
match /tournaments/{tournamentID} {
allow read: if true;
allow write: if request.auth.uid == "XXXXXXXXXXXXXXXXXXXXXXXXXXXX";
}
}
}
```
Replace XXXXXXXXXXXXXXXXXXXXXXXXXXXX with the UID you just copied and then click on 'Publish'.
8. Go to the 'Data' section in 'Firestore Database'. Create 3 collections named 'motions', 'requests', and 'tournaments' under 'Collection ID'. When asked to create a first document, click on 'Auto ID' and then click save. Remember to delete the first document for all 3 collections.
## Setting up Google Analytics (optional)
This part is optional, so if you don't want to set up Google Analytics, delete 'REACT_APP_GA_TRACKING_ID_WEB' inside [.env.example](./.env.example).
Visit [Analytics](https://analytics.google.com/) and login with your Google account. If you have never created a Google Analytics account, click on 'Start measuring'. Otherwise, click on the settings button the bottom of the left bar and click on '+ Create Account' under the 'Admin' tab.
Account setup:
Give the account a name e.g. 'Debaters' toolkit'. The 4 checkboxes are optional.
Property setup:
Name the new property 'Main' and select your time zone and currency. Click on 'Show advanced options' and select 'Create a Universal Analytics'. Set the 'Website URL' to a valid URL, but if you intend to host the app somewhere then enter the desired URL here. Select 'Create a Universal Analytics property only'.
About your business:
This part is optional, and you can skip it by clicking on 'Create'.
Copy the Tracking ID (something like UA-123456789-1) and paste it into 'REACT_APP_GA_TRACKING_ID_WEB' inside [.env.example](./.env.example).
## Setting up Messenger Chat (optional)
This part is optional, so if you don't want to set up Google Analytics, delete 'REACT_APP_FB_PAGE_ID' and 'REACT_APP_FB_APP_ID' inside [.env.example](./.env.example).
Follow [this tutorial](https://www.youtube.com/watch?v=8e_4KIj4jBs) to set up your Facebook page and create a new App on [Meta for Developers](https://developers.facebook.com/). Copy your Facebook page ID and the newly created app's ID and paste them into REACT_APP_FB_PAGE_ID and REACT_APP_FB_APP_ID inside [.env.example](./.env.example), correspondingly.
## Starting the development server
Open up a terminal in the root folder of the project and do the following:
```sh
cp .env.example .env
```
Follow [this guide](https://yarnpkg.com/getting-started/migration) to install yarn v3.
You can start the development server directly by installing the dependencies using 'npm install' or 'yarn install' and then 'npm start' or 'yarn start', but this requires manual npm/yarn configuration. The recommended approach is to run the development server inside a Docker container.
Install [Docker](https://docs.docker.com/get-docker/) if you haven't and open Docker upon finishing the installation.
Then, build the Docker image:
```sh
docker build -t debaters-toolkit .
```
Run the container:
```sh
docker run --name debaters-toolkit -d -p 3000:3000 debaters-toolkit
```
Now you can open your browser and go to http://localhost:3000.
## Electron
You can also run Debaters' toolkit as an Electron application. First, switch to the 'electron' branch.
Then, to start the Electron development server:
```sh
yarn dev
```
To build the .exe installer:
```sh
yarn build
yarn electron-pack
```
You will then find the .exe installer inside the 'dist' folder.
## Android
It is also possible to build Debaters' toolkit as a native Android application. First, switch to the 'android' branch.
Then, to generate native Android source code:
```sh
ionic capacitor add android
```
A new 'android' folder will be created.
To update the icons of the Android app, open up your terminal:
```sh
rm -rf android/app/src/main/res
cp -r res android/app/src/main
```
After that, open android/app/src/main/Android Manifest.xml and set android:icon to "@mipmap/debaters_toolkit", android:roundIcon to "@mipmap/debaters_toolkit_round".
Finally, open the 'android' folder with [Android Studio](https://developer.android.com/studio) and build the APK.
+1
View File
@@ -0,0 +1 @@
<mxfile host="Electron" modified="2021-07-24T23:37:45.309Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.9.9 Chrome/85.0.4183.121 Electron/10.1.5 Safari/537.36" etag="oqSAt6ub512BhyQgOUx7" version="13.9.9" type="device"><diagram id="8DyQJfGScnb9QvXwjAmv" name="Page-1">7Zxbc6M2FMc/jR+3AxaX+NVO6ux0b53MTjb7skONDOpixAr51k9fMOIq4dCEIOQ6L0EHCYuj31+XI9kTsNgclsSJ/I/YhcFkqrmHCbidTKe6CazkX2o5ZpaZZWQGjyCXZSoND+gfyIwas26RC+NaRopxQFFUN65wGMIVrdkcQvC+nm2Ng/qnRo4HOcPDygl46yNyqZ9Zb0yttN9D5Pn5J+sau7Nx8szMEPuOi/cVE7ibgAXBmGZXm8MCBqnzcr9k5X5vuVtUjMCQdinwdT3zYj26/7798uePONAW7mfyjj1l5wRb9sIbTBEOY1ZneswdQfA2dGH6LG0C5nsfUfgQOav07j5p+sTm002QpPTkkq9b/kGQUHiomFhdlxBvICXHJAu7a+aOZOAAltyXraDnrvUrLWCarPFZw3vFk0vfJBfMPf/BVVOb8wl0E1ZYEhPqYw+HTnBXWud1r5V5PmAcMV/9DSk9MvCdLcV1T8IDot/S4r+ZLPXEHpZe3x6qiSNLtHo/xluygufekSnMIR6kZ/KBLF/6/mfbksDAoWhX11L/LcNBvMYk0Z90hnWzzrAugNgQMJzbevcU4DxFEyRCZ5O8o3zJWw3Fmx0V34O3lo/IX3xbbO//CJdfdxZywveewFsyeoCXq9lQUs0G5/UjdIh0OEcn5Zkqw1Hic3KsFEqTT/nz0kRZ7JR6/TBmdgRfHxX4Jgf+qbmkk28bDfQNHn3rjfplMfo3V/TbfGMrib7NoR84obdN12ay6beskdE/NVSh/+UUz5Scucw4itOZtXSCdW1sUxdlCB6+/87jXop14Hm1K+yjcI3jALnyBQDA8124OegEBlwF0EpSHuVVTAB8JHWFQ5q+vmz8m3EV+fibV/xbOQJq4i+KLEZoJR1+Qx8b/NYV/laK1Fy96vzytQyrJ/b3t/JlcPO8DOxBZaDMbtrwMkhzKSiDvNrVKA4Kf0pn3+zA/lsNAcJ9pekV/TbXjH7yf67WFfBdtEMxwqF0+G+m8sKXu6fA/hQ5ePVx/Xj8sXk6zI9LwaYqgb+2MBbsPz/jHSeOshM/a3RIPTpv8Y3Ag+2xMr2x2SGIlYncZb2Vu/Lp66V3Fg0lP9t7CJ0l2PdrZ3CAzuJcJbkjV6PAX2vGigccK8X865xfrvy3OUuwfBof//xiKY0UPwgjxVIkYI9NAv+T7ZJeJKAL1k7j00BeS2HE4JNo01DKXKh55kmghGJzsSqFwti/45SJnvXJtPlKVlnRLxid4lFF+zZ6ulmj1TINsVKNhiuq8Yq25GdCwpN+MtCf2ubIBgFlzjz1Cb59meDzUyDxST8Z5BsSN8uF3poqswLuk/zZZZLPH5Fq+aqGDPRBh/nOsOgr0+m/dOYfU4J/wgUOMDm9H9BOf/2Kqfji4KjXBEVst8MhWBnqMDvspA8aGgXKDAzjVofgsO0I1cGPHK17LFLUMRubOi7+jOEg6gBKjB2AHzvEx69kSMPqEFAdVhoXH1AdRhpKhFrzWnKnUqwgqfP8L5JcebTwjGSt2B2WID1pJUmWPz6RLfDKn/AAd/8C</diagram></mxfile>
+8
View File
@@ -0,0 +1,8 @@
FROM node:16.13.1
WORKDIR /app
COPY package.json ./
RUN yarn set version berry
COPY .yarnrc.yml ./
RUN yarn install
COPY . .
CMD ["yarn", "start"]
+16
View File
@@ -0,0 +1,16 @@
# Debaters' toolkit [![MIT - License](https://img.shields.io/badge/License-MIT-2ea44f)](https://opensource.org/licenses/MIT) [![v - 1.4.6](https://img.shields.io/badge/v-1.4.6-blue)](https://gitea.elliot-at-zuri.ch/admin/debaters-toolkit/releases/tag/1.4.6) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](./CONTRIBUTING.md) [![Donate - Patreon](https://img.shields.io/badge/Donate-Patreon-fc444c)](https://www.patreon.com/user?u=1234567)
Debaters' toolkit is a helpful online toolkit for debaters. Users can search for or generate random debate motions based on various criteria (e.g. tournament, topic, language, etc.), calculate break for BP, AP, and WSDC, and do time keeping for various debate formats without having to download any software. This software contains open-source third-party materials used in full accordance with related licenses and intellectual property laws.
## Sources for our motions
- [Hellomotions' open-source database](https://hellomotions.com/)
- [Puzzles Ams's Motion Database 2020-2021](https://docs.google.com/spreadsheets/d/1e11Rh2G_Bb9mNARLhnA6WjqgDO3Np6QpYnasVqXkGZY/edit#gid=1678651727)
- [Motions for Vietnam Debate Community | Moji Debate](https://drive.google.com/drive/folders/1OX39izeTiz8DMFWhrw9v3qpk8fg3_ylV)
## Installation
Open the app with a PWA-capable browser and add it to your homescreen. You can also install the native version of the app on Windows and Android using the setup files in the [release package](https://gitea.elliot-at-zuri.ch/admin/debaters-toolkit/releases).
## Notice of Discontinuation
As of October 22nd, 2023, this project is no longer maintained. Many features no longer work correctly, and some other features have been disabled entirely. This repository now exists solely as a historical archive.
Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

+28746
View File
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
{
"name": "debaters-toolkit",
"description": "A toolkit that helps you generate debate motions, calculate break chances and do timekeeping.",
"author": "[Quy Anh] «Elliot» Nguyen",
"version": "0.0.0",
"private": true,
"dependencies": {
"@capacitor/android": "^3.3.3",
"@capacitor/cli": "^3.3.3",
"@capacitor/core": "^3.3.3",
"@mui/icons-material": "^5.2.4",
"@mui/lab": "^5.0.0-alpha.60",
"@mui/material": "^5.2.4",
"@mui/styles": "^5.2.3",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"capacitor": "^0.5.5",
"core-js": "^3.19.2",
"cross-env": "^7.0.3",
"dotenv": "^10.0.0",
"electron-is-dev": "^2.0.0",
"eslint-webpack-plugin": "^3.1.1",
"firebase": "^8.6.7",
"grpc": "^1.24.11",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-dev-utils": "^11.0.4",
"react-device-detect": "^2.1.2",
"react-dom": "^18.1.0",
"react-download-link": "^2.3.0",
"react-ga": "^3.3.0",
"react-ga4": "^1.4.1",
"react-helmet": "^6.1.0",
"react-messenger-customer-chat": "^0.8.0",
"react-responsive": "^8.2.0",
"react-router": "^6.2.1",
"react-router-dom": "^6.2.1",
"react-scripts": "4.0.3",
"react-select": "^5.2.1",
"react-textarea-autosize": "^8.3.3",
"readdirp": "^3.6.0",
"resolve-url-loader": "^4.0.0",
"sass": "^1.52.1",
"styled-jsx": "^4.0.1",
"svgo": "^2.4.0",
"web-vitals": "^1.1.2",
"webpack": "4.44.2"
},
"main": "public/electron.js",
"build": {
"appId": "com.ajdx3b1qk49oi3vsnuw5k34x.debaters-toolkit",
"files": [
"build/**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "assets"
},
"extends": null,
"win": {
"icon": "public/media/icons/electron.png"
},
"linux": {
"icon": "public/media/icons/electron.png"
},
"mac": {
"icon": "public/media/icons/electron.png"
}
},
"homepage": "./",
"scripts": {
"start": "react-scripts start",
"build": "PUBLIC_URL=. react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"dev": "concurrently -k \"cross-env BROWSER=none npm start\" \"npm:electron\"",
"electron": "wait-on tcp:3000 && electron .",
"electron-pack": "electron-builder -c.extraMetadata.main=build/electron.js",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"packageManager": "yarn@3.6.4",
"devDependencies": {
"@types/react-select": "^5.0.1",
"concurrently": "^6.4.0",
"electron": "^16.0.2",
"electron-builder": "^22.14.5",
"gh-pages": "^6.0.0",
"wait-on": "^6.0.0",
"yarn": "^1.22.19"
}
}
+1
View File
@@ -0,0 +1 @@
/* /index.html 200
+40
View File
@@ -0,0 +1,40 @@
const { app, BrowserWindow } = require('electron')
const path = require('path')
const isDev = require('electron-is-dev')
let mainWindow
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
icon: path.join(__dirname, 'media/icons/electron.png'),
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
})
mainWindow.loadURL(
isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname, '..', 'build', 'index.html')}`
)
mainWindow.on('closed', () => (mainWindow = null))
}
app.disableHardwareAcceleration()
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (mainWindow === null) {
createWindow()
}
})
+50
View File
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/media/icons/favicon.ico" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="%PUBLIC_URL%/media/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="%PUBLIC_URL%/media/icons/favicon-16x16.png"
/>
<link
rel="apple-touch-icon"
href="%PUBLIC_URL%/media/icons/apple-touch-icon.png"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike '/favicon.ico' or 'favicon.ico', '%PUBLIC_URL%/favicon.ico' will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<link
href="https://cdn.jsdelivr.net/gh/hung1001/font-awesome-pro@4cac1a6/css/all.css"
rel="stylesheet"
type="text/css"
/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="app"></div>
</body>
</html>
+113
View File
@@ -0,0 +1,113 @@
{
"name": "Debaters' toolkit",
"short_name": "Debaters' toolkit",
"categories": [
"education",
"sports",
"utilities"
],
"description": "Generate a random debate motion, search our database of over 7000 motions, calculate break chances or do debate timekeeping.",
"screenshots": [
{
"src": "media/screenshots/1.png",
"sizes": "1800x2040",
"type": "image/png"
},
{
"src": "media/screenshots/2.png",
"sizes": "1800x2040",
"type": "image/png"
},
{
"src": "media/screenshots/3.png",
"sizes": "1800x2040",
"type": "image/png"
},
{
"src": "media/screenshots/4.png",
"sizes": "1800x2040",
"type": "image/png"
},
{
"src": "media/screenshots/5.png",
"sizes": "1800x2040",
"type": "image/png"
}
],
"icons": [
{
"src": "media/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "media/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "Motion Generator",
"short_name": "Generator",
"description": "Generate a random debate motion",
"url": "/generator",
"icons": [
{ "src": "/media/icons/generator/36x36.png", "sizes": "36x36" },
{ "src": "/media/icons/generator/48x48.png", "sizes": "48x48" },
{ "src": "/media/icons/generator/72x72.png", "sizes": "72x72" },
{ "src": "/media/icons/generator/96x96.png", "sizes": "96x96" },
{ "src": "/media/icons/generator/144x144.png", "sizes": "144x144" },
{ "src": "/media/icons/generator/192x192.png", "sizes": "192x192" }
]
},
{
"name": "Motion Database",
"short_name": "Database",
"description": "Search for motions from the database",
"url": "/database",
"icons": [
{ "src": "/media/icons/database/36x36.png", "sizes": "36x36" },
{ "src": "/media/icons/database/48x48.png", "sizes": "48x48" },
{ "src": "/media/icons/database/72x72.png", "sizes": "72x72" },
{ "src": "/media/icons/database/96x96.png", "sizes": "96x96" },
{ "src": "/media/icons/database/144x144.png", "sizes": "144x144" },
{ "src": "/media/icons/database/192x192.png", "sizes": "192x192" }
]
},
{
"name": "Break Calculator",
"short_name": "Calculator",
"description": "Calculate break chances in AP, WSDC, or BP tournaments",
"url": "/break_calculator",
"icons": [
{ "src": "/media/icons/break-calculator/36x36.png", "sizes": "36x36" },
{ "src": "/media/icons/break-calculator/48x48.png", "sizes": "48x48" },
{ "src": "/media/icons/break-calculator/72x72.png", "sizes": "72x72" },
{ "src": "/media/icons/break-calculator/96x96.png", "sizes": "96x96" },
{ "src": "/media/icons/break-calculator/144x144.png", "sizes": "144x144" },
{ "src": "/media/icons/break-calculator/192x192.png", "sizes": "192x192" }
]
},
{
"name": "Debate Timekeeper",
"short_name": "Timekeeper",
"description": "Do debate timekeeping",
"url": "/keeper",
"icons": [
{ "src": "/media/icons/timekeeper/36x36.png", "sizes": "36x36" },
{ "src": "/media/icons/timekeeper/48x48.png", "sizes": "48x48" },
{ "src": "/media/icons/timekeeper/72x72.png", "sizes": "72x72" },
{ "src": "/media/icons/timekeeper/96x96.png", "sizes": "96x96" },
{ "src": "/media/icons/timekeeper/144x144.png", "sizes": "144x144" },
{ "src": "/media/icons/timekeeper/192x192.png", "sizes": "192x192" }
]
}
],
"id": "/",
"start_url": "/",
"display": "standalone",
"theme_color": "#FFFFFF",
"background_color": "#FFFFFF",
"android_package_name": "com.debaterstoolkit.android"
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+38
View File
@@ -0,0 +1,38 @@
import React from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import {
HomePage,
MotionGenerator,
AdminPage,
BreakCalculator,
MotionDatabase,
SubmitNewMotion,
DebateKeeper,
About
} from './pages'
import { NavBar } from './core/components'
import { useStyles } from './appStyle'
export default function App() {
const classes = useStyles()
return (
<BrowserRouter>
<div className={classes['App']}>
<NavBar />
<Routes>
<Route exact={true} path='/' element={<HomePage />} />
<Route exact={true} path='generator' element={<MotionGenerator />} />
<Route exact={true} path='admin' element={<AdminPage />} />
<Route exact={true} path='database' element={<MotionDatabase />} />
<Route exact={true} path='new_motion' element={<SubmitNewMotion />} />
<Route exact={true} path='break_calculator' element={<BreakCalculator />} />
<Route path='keeper' element={<DebateKeeper />}>
<Route path=':format' element={<DebateKeeper />} />
</Route>
<Route exact={true} path='about' element={<About />} />
<Route path='*' element={<Navigate to='/' />} />
</Routes>
</div>
</BrowserRouter>
)
}
+38
View File
@@ -0,0 +1,38 @@
import { isBrowser } from 'react-device-detect'
import { makeStyles } from '@mui/styles'
const style = !isBrowser
? {
'App': {
fontFamily: "'Lora', serif",
width: '100vw',
height: '100vh',
overflowX: 'hidden'
}
}
: {
'App': {
fontFamily: "'Lora', serif",
width: '100vw',
height: '100vh',
overflowX: 'hidden',
'&::-webkit-scrollbar': {
backgroundColor: '#F1F1F1 !important',
width: '0.75rem',
},
'&::-webkit-scrollbar-thumb': {
borderRadius: '8px',
backgroundColor: '#C1C1C1',
minHeight: '24px',
border: '3px solid #F1F1F1',
'&:hover': {
backgroundColor: '#A8A8A8',
},
'&:active': {
backgroundColor: '#787878',
}
}
}
}
export const useStyles = makeStyles(style)
@@ -0,0 +1,90 @@
import { useState, useEffect } from "react"
import Select from 'react-select'
import { customTheme } from "../../constants"
import { isObject } from '../../helpers/isObject'
import './style.css'
export const EditableSelector = (props) => {
const { defaultSelectValue, onUpdate, style, options, defaultValue, multi, components, styles, placeholder, isSearchable } = props
const [value, setValue] = useState(defaultValue)
useEffect(() => {
setValue(defaultValue)
}, [defaultValue])
useEffect(() => {
onUpdate(value)
}, [value])
const updateValue = (val) => {
if (multi) {
if (Array.isArray(defaultValue)) {
if (val != undefined && val != null) {
if (val.length == 0) {
setValue([])
}
else {
let tempValue = []
val.forEach(item => {
tempValue.push(item.value)
})
setValue(tempValue)
}
}
}
else if (isObject(defaultValue)) {
if (val != undefined && val != null) {
if (val.length == 0) {
setValue({})
}
else {
let newValue = {}
val.forEach(item => {
Object.keys(item.value).forEach(key => {
newValue[`${key}`] = item.value[`${key}`]
})
})
setValue(newValue)
}
}
}
}
else {
if (val == undefined || val == null) {
setValue("")
}
else {
setValue(val.value)
}
}
}
return (
<>
{
multi ?
<Select className="editable_selector"
isMulti={true}
theme={customTheme}
style={style}
options={options}
defaultValue={defaultSelectValue}
onChange={updateValue}
components={components}
styles={styles}
isSearchable={isSearchable} //false
placeholder={placeholder}
/>
:
<Select className="editable_selector"
isMulti={false}
theme={customTheme}
style={style}
options={options}
defaultValue={defaultSelectValue}
onChange={updateValue}
isClearable={true}
components={components}
styles={styles}
placeholder={placeholder}
isSearchable={isSearchable}
/>
}
</>
)
}
@@ -0,0 +1,7 @@
.editable_selector {
width: 100%;
}
.editable_selector input {
font-family: "Lora", serif;
font-size: 0.7rem !important;
}
@@ -0,0 +1,25 @@
import { useState, useEffect, useRef } from "react"
import _ from "lodash"
import './style.css'
export const EditableText = (props) => {
const { defaultValue, onUpdate, style } = props;
const [value, setValue] = useState(defaultValue)
const onUpdateWithDebounce = _.debounce(onUpdate, 500)
const inputRef = useRef(null)
const unfocusInput = (e) => {
if (e.keyCode == 13) inputRef.current.blur();
}
useEffect(() => {
setValue(defaultValue)
}, [defaultValue])
useEffect(() => {
onUpdateWithDebounce(value);
}, [value])
useEffect(() => {
inputRef && inputRef.current &&
inputRef.current.addEventListener('keyup', unfocusInput);
}, [inputRef])
return (
<input ref={inputRef} className="editable_text" type="text" spellCheck={false} value={value} style={style} onChange={(e) => setValue(e.target.value)} />
)
}
@@ -0,0 +1,13 @@
.editable_text {
border: 1px solid transparent;
outline: none;
width: 100%;
/* display: flex;
justify-content: center;
align-items: center; */
font-family: "Lora", serif;
}
/* .editable_text:focus {
border: 1px solid black;
outline: none;
} */
@@ -0,0 +1,26 @@
import { useState, useEffect, useRef } from "react"
import TextareaAutosize from "react-textarea-autosize"
import _ from "lodash"
import './style.css'
export const EditableTextArea = (props) => {
const { defaultValue, onUpdate, style } = props;
const [value, setValue] = useState(defaultValue)
const onUpdateWithDebounce = _.debounce(onUpdate, 500)
const inputRef = useRef(null)
const unfocusInput = (e) => {
if (e.keyCode == 13) inputRef.current.blur();
}
useEffect(() => {
setValue(defaultValue)
}, [defaultValue])
useEffect(() => {
onUpdateWithDebounce(value);
}, [value])
useEffect(() => {
inputRef && inputRef.current &&
inputRef.current.addEventListener('keyup', unfocusInput);
}, [inputRef])
return (
<TextareaAutosize spellCheck={false} ref={inputRef} className="editable_text_area" type="text" value={value} style={style} onChange={(e) => setValue(e.target.value)} />
)
}
@@ -0,0 +1,13 @@
.editable_text_area {
border: 1px solid transparent;
outline: none;
width: 100%;
height: 100%;
font-family: "Lora", serif;
resize: none;
overflow: hidden;
}
/* .editable_text_area:focus {
border: 1px solid black;
outline: none;
} */
@@ -0,0 +1,68 @@
import { isBrowser } from 'react-device-detect'
import { useStylesPC } from './stylePC'
import { useStylesMobile } from './styleMobile'
export const InformationContainer = () => {
const classesPC = useStylesPC()
const classesMobile = useStylesMobile()
return (
<div
className={
isBrowser
? classesPC.informationContainer
: classesMobile.informationContainer
}
>
<div className='topLane'>
<button>
<a href='/about'>ABOUT</a>
</button>
</div>
<div className='midLane'>
{/* I no longer use Facebook. */}
<a href='about:blank'>
<button>
<i className='fab fa-facebook-square' />
</button>
</a>
{/* I no longer use Twitter. */}
<a href='about:blank'>
<button>
<i className='fab fa-twitter-square' />
</button>
</a>
<a href='https://gitea.elliot-at-zuri.ch/admin'>
<button>
<i className='fab fa-github-square' />
</button>
</a>
{/* I no longer use Patreon. */}
<a href='about:blank'>
<button>
<i className='fab fa-patreon' />
</button>
</a>
</div>
<div className='botLane'>
<div className='introText'>
<div>
Debaters' toolkit is an open-source software licensed under the{' '}
<a href='https://choosealicense.com/licenses/mit/'>
<span>MIT license</span>
</a>{' '}
that aims to be useful to all debaters. Our motions are collected
from various sources. While we strive to update the database as
regularly as possible, we cannot warrant absolute correctness for
all motions. If you have any issue with our content or detect any
bug in our app, please contact us at{' '}
<a href='mailto: quyanh.nguyen@helsinki'>
<span>quyanh.nguyen@helsinki</span>
</a>
.
</div>
<div className='aboutSubHeader'>© 2021 [Quy Anh] «Elliot» Nguyen.</div>
</div>
</div>
</div>
)
}
@@ -0,0 +1,128 @@
.informationContainer {
width: 100%;
display: flex;
flex-direction: column;
background-color: #282a35 !important;
height: 38vh;
min-height: 14.3rem;
font-family: "Source Sans Pro", sans-serif;
margin-top: auto;
.topLane {
width: 100%;
height: 20%;
display: flex;
justify-content: center;
align-items: center;
button {
padding: 0.3rem;
border-radius: 5px;
border: 1px solid white;
background-color: transparent;
font-weight: 500;
a {
text-decoration: none;
color: white;
}
}
button:hover {
background-color: white;
a {
color: black;
}
}
}
.midLane {
width: 100%;
height: 20%;
display: flex;
justify-content: center;
align-items: center;
button {
background-color: #282a35;
border: 1px solid white;
border-radius: 5px;
padding: 0.4rem;
margin-left: 0.2rem;
margin-right: 0.2rem;
i {
color: white;
font-size: 2rem;
}
}
button:hover {
background-color: white;
i {
color: black;
}
}
}
.botLane {
width: 100%;
height: 60%;
display: flex;
justify-content: center;
align-items: center;
.introText {
color: white !important;
display: flex;
flex-direction: column;
align-items: center;
width: 90%;
div {
margin: 0.5rem;
text-align: center;
a {
color: white;
span {
text-decoration: underline;
}
span:hover {
color: #4caf50;
}
}
}
}
}
}
@media only screen and (max-width: 379px) {
.informationContainer {
height: 47vh;
.topLane {
height: 12%;
}
.midLane {
height: 10%;
}
.botLane {
height: 78%;
margin-bottom: 0.5rem;
}
}
}
@media only screen and (max-width: 425px) and (min-width: 380px) {
.informationContainer {
height: 40vh;
.topLane {
height: 17%;
}
.midLane {
height: 13%;
}
.botLane {
height: 70%;
margin-bottom: 0.5rem;
}
}
}
@media only screen and (max-width: 768px) and (min-width: 426px) {
.informationContainer {
height: 25vh;
.topLane {
}
.midLane {
}
.botLane {
}
}
}
@@ -0,0 +1,126 @@
import { makeStyles } from '@mui/styles'
export const useStylesMobile = makeStyles({
'informationContainer': {
width: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#282a35 !important',
height: '38vh',
minHeight: '14.3rem',
fontFamily: '"Source Sans Pro", sans-serif',
marginTop: 'auto',
'& .topLane': {
width: '100%',
height: '20%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'& button': {
padding: '0.3rem',
borderRadius: '5px',
border: '1px solid white',
backgroundColor: 'transparent',
fontWeight: 500,
'& a': {
textDecoration: 'none',
color: 'white'
},
'&:hover': {
backgroundColor: 'white',
'& a': {
color: 'black'
}
}
}
},
'& .midLane': {
width: '100%',
height: '20%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'& button': {
backgroundColor: '#282a35',
border: '1px solid white',
borderRadius: '5px',
padding: '0.4rem',
marginLeft: '0.2rem',
marginRight: '0.2rem',
'& i': {
color: 'white',
fontSize: '2rem'
},
'&:hover': {
backgroundColor: 'white',
'& i': {
color: 'black'
}
}
}
},
'& .botLane': {
width: '100%',
height: '60%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'& .introText': {
color: 'white !important',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '90%',
'& div': {
margin: '0.5rem',
textAlign: 'center',
'& a': {
color: 'white',
'& span': {
textDecoration: 'underline',
'&:hover': {
color: '#4caf50'
}
}
}
}
}
}
},
'@media only screen and (max-width: 379px)': {
'informationContainer': {
height: '47vh',
'& .topLane': {
height: '12%'
},
'& .midLane': {
height: '10%',
},
'& .botLane': {
height: '78%',
marginBottom: '0.5rem'
}
}
},
'@media only screen and (max-width: 425px) and (min-width: 380px)': {
'informationContainer': {
height: '40vh',
'& .topLane': {
height: '17%',
},
'& .midLane': {
height: '13%',
},
'& .botLane': {
height: '70%',
marginBottom: '0.5rem'
}
}
},
'@media only screen and (max-width: 768px) and (min-width: 426px)': {
'informationContainer': {
height: '25vh'
}
}
})
@@ -0,0 +1,138 @@
import { makeStyles } from '@mui/styles'
export const useStylesPC = makeStyles({
'informationContainer': {
width: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#282a35 !important',
height: '38vh',
minHeight: '14.3rem',
fontFamily: '"Source Sans Pro", sans-serif',
marginTop: 'auto',
'& .topLane': {
width: '100%',
height: '20%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'& button': {
padding: '0.3rem',
borderRadius: '5px',
border: '1px solid white',
backgroundColor: 'transparent',
fontWeight: 500,
'& a': {
textDecoration: 'none',
color: 'white'
},
'&:hover': {
backgroundColor: 'white',
'& a': {
color: 'black'
}
}
}
},
'& .midLane': {
width: '100%',
height: '20%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'& button': {
backgroundColor: '#282a35',
border: '1px solid white',
borderRadius: '5px',
padding: '0.4rem',
marginLeft: '0.2rem',
marginRight: '0.2rem',
'& i': {
color: 'white',
fontSize: '2rem'
},
'&:hover': {
backgroundColor: 'white',
'& i': {
color: 'black'
}
}
}
},
'& .botLane': {
width: '100%',
height: '60%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'& .introText': {
color: 'white !important',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '90%',
fontSize: '1rem',
'& div': {
margin: '0.5rem',
textAlign: 'center',
'& a': {
color: 'white',
'& span': {
textDecoration: 'underline',
'&:hover': {
color: '#4caf50'
}
}
}
}
}
}
},
'@media only screen and (max-width: 379px)': {
'informationContainer': {
height: '47vh',
'& .topLane': {
height: '12%'
},
'& .midLane': {
height: '10%',
},
'& .botLane': {
height: '78%',
marginBottom: '0.5rem',
'& .introText': {
fontSize: '0.8rem'
}
}
}
},
'@media only screen and (max-width: 425px) and (min-width: 380px)': {
'informationContainer': {
height: '40vh',
'& .topLane': {
height: '17%',
},
'& .midLane': {
height: '13%',
},
'& .botLane': {
height: '70%',
marginBottom: '0.5rem',
'& .introText': {
fontSize: '0.8rem'
}
}
}
},
'@media only screen and (max-width: 768px) and (min-width: 426px)': {
'informationContainer': {
height: '25vh',
'& .botLane': {
'& .introText': {
fontSize: '0.8rem'
}
}
}
}
})
+44
View File
@@ -0,0 +1,44 @@
import './style.css'
import { useState, useEffect } from 'react';
export const Message = (props) => {
const { status, successMessage, failureMessage } = props;
const [show, setShow] = useState(false)
useEffect(() => {
if (status != undefined) {
setShow(true)
}
}, [status])
useEffect(() => {
const resetShow = () => { setShow(false) }
if (show) setTimeout(resetShow, 1500)
return(() => {
clearTimeout(resetShow)
})
}, [show])
return (
<>
{show &&
<div className='message'>
{
<div>{status == true ? <i className="fas fa-check-circle statusIcon" id="successIcon" color="#abe491" /> : <i className="fas fa-times-circle statusIcon" id="failureIcon" color="#e49191" />}</div>
}
<div className="messageBox">
{
<div>{
status == true ?
<div>
<div>{successMessage}</div>
</div>
:
<div>
<div>{failureMessage}</div>
</div>
}</div>
}
</div>
</div>
}
</>
)
}
+31
View File
@@ -0,0 +1,31 @@
.successLineOne {
color: #abe491;
}
.successLineTwo {
color: #b9b9b9;
font-size: 0.8rem;
}
.failureLineOne {
color: #e49191;
}
.failureLineTwo {
color: #b9b9b9;
font-size: 0.8rem;
}
.message {
font-weight: bolder;
font-family: "Source Sans Pro", sans-serif;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
}
#successIcon {
color: #abe491;
}
#failureIcon {
color: #e49191;
}
.statusIcon {
margin-right: 0.6rem;
}
+58
View File
@@ -0,0 +1,58 @@
import { useState, useEffect } from 'react'
import { NavBarItem } from './navBarItem'
import { isBrowser } from 'react-device-detect'
import { useStylesPC } from './stylePC'
import { useStylesMobile } from './styleMobile'
const navBarConfig = [
{
tabID: 'home', to: '/', specificTabName: "home", children:
<>
<i className="fas fa-home" />
</>
},
{
tabID: 'motionGenerator', to: '/generator', children:
<>
<div>Motion </div>
<div>Generator</div>
</>
},
{
tabID: 'database', to: '/database', children:
<>
<div>Motion </div>
<div>Database</div>
</>
},
{
tabID: 'breakCalc', to: '/break_calculator', children:
<>
<div>Break</div>
<div>Calculator</div>
</>
},
{
tabID: 'keeper', to: '/keeper/bp', children:
<>
<div>Timekeeper</div>
</>
}
]
export const NavBar = () => {
const [activeTab, setActiveTab] = useState(`/`)
useEffect(() => {
setActiveTab(window.location.pathname)
}, [window.location.pathname])
const classesPC = useStylesPC()
const classesMobile = useStylesMobile()
return (
<div className={isBrowser ? classesPC.navBar : classesMobile.navBar}>
{navBarConfig.map(config => {
return (
<NavBarItem specificTabName={config.specificTabName} isActive={activeTab === config.to} to={config.to} tabID={config.tabID} setActiveTab={setActiveTab}>{config.children}</NavBarItem>
)
})}
</div>
)
}
+10
View File
@@ -0,0 +1,10 @@
import { Link } from 'react-router-dom'
export const NavBarItem = (props) => {
const { to, tabID, setActiveTab, children, isActive, specificTabName } = props // setActiveTab(to)
return (
<Link to={to} className={`anchor ${specificTabName} ${isActive ? 'active' : ''}`} id={tabID} onClick={() => { setActiveTab(to) }}>
{children}
</Link>
)
}
+77
View File
@@ -0,0 +1,77 @@
import { makeStyles } from '@mui/styles'
export const useStylesMobile = makeStyles({
'navBar': {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#282a35',
fontFamily: '"Source Sans Pro", sans-serif',
height: '7vh',
width: '100%',
'& .anchor': {
color: 'white',
textDecoration: 'none',
display: 'flex',
fontWeight: 'bolder',
fontSize: '0.8rem',
height: '100%',
width: '24vw',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
'&:hover': {
backgroundColor: '#000000'
}
},
'& .home': {
width: '4vw !important',
'& i': {
fontSize: '1.2rem'
}
},
'& .active': {
backgroundColor: '#000000'
}
},
'@media only screen and (max-width: 379px)': {
'navBar': {
'& .anchor': {
fontSize: '0.7rem',
width: '22vw',
},
'& .home': {
width: '12vw !important',
'& i': {
fontSize: '1rem !important'
}
}
}
},
'@media only screen and (max-width: 425px) and (min-width: 380px)': {
'navBar': {
'& .anchor': {
fontSize: '0.8rem',
width: '22vw'
},
'& .home': {
width: '12vw !important',
}
}
},
'@media only screen and (max-width: 768px) and (min-width: 426px)': {
'navBar': {
'& .anchor': {
fontSize: '1.1rem',
width: '22.5vw',
},
'& .home': {
width: '10vw !important',
'& i': {
fontSize: '2rem !important'
}
}
}
}
})
+81
View File
@@ -0,0 +1,81 @@
import { makeStyles } from '@mui/styles'
export const useStylesPC = makeStyles({
'navBar': {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#282a35',
fontFamily: '"Source Sans Pro", sans-serif',
height: '7vh',
minHeight: '2.63rem', /**/
width: '100%',
minWidth: '48.125rem', /**/
'& .anchor': {
color: 'white',
textDecoration: 'none',
display: 'flex',
fontWeight: 'bolder',
fontSize: '0.8rem !important',
height: '100%',
width: '24vw',
minWidth: '13.5rem', /**/
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
'&:hover': {
backgroundColor: '#000000'
}
},
'& .home': {
width: '4vw !important',
minWidth: '3.375rem !important', /**/
'& i': {
fontSize: '1.2rem !important'
}
},
'& .active': {
backgroundColor: '#000000'
}
},
'@media only screen and (max-width: 379px)': {
'navBar': {
'& .anchor': {
fontSize: '0.7rem',
width: '22vw',
},
'& .home': {
width: '12vw !important',
'& i': {
fontSize: '1rem !important'
}
}
}
},
'@media only screen and (max-width: 425px) and (min-width: 380px)': {
'navBar': {
'& .anchor': {
fontSize: '0.8rem',
width: '22vw'
},
'& .home': {
width: '12vw !important'
}
}
},
'@media only screen and (max-width: 768px) and (min-width: 426px)': {
'navBar': {
'& .anchor': {
fontSize: '1.1rem',
width: '22.5vw',
},
'& .home': {
width: '10vw !important',
'& i': {
fontSize: '2rem'
}
}
}
}
})
@@ -0,0 +1,8 @@
import { components } from 'react-select'
export const ClearIndicator = ({ children, ...props }) => {
return (
<components.ClearIndicator {...props}>
{children}
</components.ClearIndicator>
);
};
@@ -0,0 +1,8 @@
import { components } from 'react-select'
export const DropdownIndicator = ({ children, ...props }) => {
return (
<components.DropdownIndicator {...props}>
{children}
</components.DropdownIndicator>
);
};
@@ -0,0 +1,6 @@
import { components } from 'react-select'
export const Input = props => {
return (
<components.Input {...props} />
);
};
@@ -0,0 +1,6 @@
import { components } from 'react-select'
export const MultiValueContainer = props => {
return (
<components.MultiValueContainer {...props} />
);
};
@@ -0,0 +1,6 @@
import { components } from 'react-select'
export const Option = props => {
return (
<components.Option {...props} />
);
};
@@ -0,0 +1,4 @@
import { components } from 'react-select'
export const Placeholder = props => {
return <components.Placeholder {...props} />;
};
@@ -0,0 +1,8 @@
import { components } from 'react-select'
export const SelectContainer = ({ children, ...props }) => {
return (
<components.SelectContainer {...props}>
{children}
</components.SelectContainer>
);
};
@@ -0,0 +1,4 @@
import { components } from 'react-select'
export const SingleValue = ({ children, ...props }) => (
<components.SingleValue {...props}>{children}</components.SingleValue>
);
@@ -0,0 +1,4 @@
import { components } from 'react-select'
export const ValueContainer = ({ children, ...props }) => (
<components.ValueContainer {...props}>{children}</components.ValueContainer>
);
@@ -0,0 +1,9 @@
export * from './ClearIndicator'
export * from './DropdownIndicator'
export * from './MultiValueContainer'
export * from './SelectContainer'
export * from './ValueContainer'
export * from './Placeholder'
export * from './Option'
export * from './SingleValue'
export * from './Input'
+47
View File
@@ -0,0 +1,47 @@
export const Table = (props) => {
const { dataSource, columns, showActions, names, ref } = props
return (
<>
{
dataSource.length != 0 ?
<table className={names.tableName} ref={ref}>
<tr className={names.headerName}>
{
columns.map(columnItem => {
if (columnItem.type != "action") {
return (
<th className={names.headerCellName} style={{
width: columnItem.width
}}>{columnItem.name}</th>
)
}
})
}
{
showActions && <th width={columns[columns.length - 1].width} className={names.emptyHeaderCellName}></th>
}
</tr>
{
dataSource.map(item => {
return (
<tr className={names.rowName}>
{
columns.map(columnItem => {
return (
<td style={{
width: columnItem.width
}} className={`${names.rowCellName} ${columnItem.type == "action" ? `${names.actionCellName}` : ""}`}>{columnItem.render(item)}</td>
)
})
}
</tr>
)
})
}
</table>
: <></>
}
</>
)
}
+7
View File
@@ -0,0 +1,7 @@
export * from './NavBar'
export * from './EditableSelector'
export * from './EditableText'
export * from './EditableTextArea'
export * from './InformationContainer'
export * from './Message'
export * from './Table'
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
[
{
"name": "BY Online Debate Open",
"format": "BP",
"year": "2021"
},
{
"name": "Uhuru Worlds",
"format": "BP",
"year": "2021"
},
{
"name": "Cambridge Asia BP",
"format": "BP",
"year": "2021"
},
{
"name": "Asian English Olympics",
"format": "BP",
"year": "2021"
},
{
"name": "Beihang International Winter Online BP Open",
"format": "BP",
"year": "2021"
},
{
"name": "Philippines Queer Open",
"format": "BP",
"year": "2021"
},
{
"name": "UMT Parliamentary Debate Open",
"format": "BP",
"year": "2021"
},
{
"name": "Trouvaille Debate Open",
"format": "BP",
"year": "2021"
},
{
"name": "DAV IR Cup",
"format": "BP",
"year": "2021"
},
{
"name": "HWS Round Robin",
"format": "BP",
"year": "2021"
},
{
"name": "Korea WUDC",
"format": "BP",
"year": "2021"
},
{
"name": "DTU Parliamentary Debate",
"format": "AP",
"year": "2021"
},
{
"name": "Vietnam University Debating Championship (VUDC)",
"format": "AP",
"year": "2021"
},
{
"name": "NEU Debate Open",
"format": "AP",
"year": "2021"
},
{
"name": "Cogic Debate Online (CODO)",
"format": "AP",
"year": "2021"
},
{
"name": "The Anime Open",
"format": "AP",
"year": "2021"
},
{
"name": "Netflix International Debate",
"format": "AP",
"year": "2021"
},
{
"name": "Da Nang Debate Open",
"format": "AP",
"year": "2021"
},
{
"name": "Asian Online Debating Championship (AODC) - WSDC",
"format": "WSDC",
"year": "2021"
},
{
"name": "Oldham Cup International League",
"format": "WSDC",
"year": "2021"
},
{
"name": "Nanjing Debate Open",
"format": "WSDC",
"year": "2021"
},
{
"name": "The Tabate",
"format": "WSDC",
"year": "2021"
},
{
"name": "FLSS Debate Tournament",
"format": "WSDC",
"year": "2021"
},
{
"name": "Hanoi Debate Tournament (HDT)",
"format": "WSDC",
"year": "2021"
},
{
"name": "Lychee Debate Open",
"format": "WSDC",
"year": "2021"
},
{
"name": "Canopus Debate Championship",
"format": "WSDC",
"year": "2021"
},
{
"name": "Gấu Debate Tournament",
"format": "BP",
"year": "2021"
},
{
"name": "Vietname BP Championship (BP)",
"format": "BP",
"year": "2020"
},
{
"name": "Gấu Online Debating Championship",
"format": "BP",
"year": "2020"
},
{
"name": "Beihang International Online Debating Championship",
"format": "BP",
"year": "2020"
},
{
"name": "Japan BP",
"format": "BP",
"year": "2020"
},
{
"name": "Melbourne Mini",
"format": "BP",
"year": "2020"
},
{
"name": "PKU Pro-Am",
"format": "BP",
"year": "2020"
},
{
"name": "Asian Online Debating Championship (AODC) - WSDC",
"format": "WSDC",
"year": "2020"
},
{
"name": "Taiwan Online Debate Open",
"format": "AP",
"year": "2020"
},
{
"name": "Northern Coast Debate Open",
"format": "AP",
"year": "2020"
},
{
"name": "Hòa Vang Debate Online",
"format": "AP",
"year": "2020"
},
{
"name": "Teen X Debate Online",
"format": "AP",
"year": "2020"
},
{
"name": "Gấu Online Debate Open",
"format": "AP",
"year": "2020"
},
{
"name": "Hong Kong Debate Open",
"format": "WSDC",
"year": "2020"
},
{
"name": "UPenn World Schools Online Debating Tournament",
"format": "WSDC",
"year": "2020"
},
{
"name": "The Debaters VTV7",
"format": "",
"year": "2020"
},
{
"name": "WSDC",
"format": "WSDC",
"year": "2019"
},
{
"name": "Vietnam Schools Debating Championship (VSDC)",
"format": "WSDC",
"year": "2019"
},
{
"name": "Ka Paio Debate Open",
"format": "WSDC",
"year": "2019"
},
{
"name": "Ka Paio Online Debate Open",
"format": "WSDC",
"year": "2020"
},
{
"name": "WSDC",
"format": "WSDC",
"year": "2018"
}
]
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
[
{
"name": "Hong Kong Parliamentary Debating Society (HKPDS)",
"format": "BP",
"year": "2020"
},
{
"name": "Asian Online Debating Championship (AODC) - BP",
"format": "BP",
"year": "2020"
},
{
"name": "6th Shanghai International Debate Open (SIDO)",
"format": "BP",
"year": "2020"
},
{
"name": "Trường Teen",
"format": "",
"year": "2020"
},
{
"name": "Cogic Debate Online (CODO)",
"format": "AP",
"year": "2020"
},
{
"name": "Spring KNC",
"format": "AP",
"year": "2020"
},
{
"name": "CNH Debate Open (CDO)",
"format": "WSDC",
"year": "2020"
},
{
"name": "Hanoi Debate Tournament (HDT)",
"format": "WSDC",
"year": "2020"
},
{
"name": "Southern Debate Open (SDO)",
"format": "AP",
"year": "2020"
},
{
"name": "Hong Kong Schools Debate Open (HKSDO)",
"format": "WSDC",
"year": "2020"
},
{
"name": "DAV Debate Open (DDO)",
"format": "AP",
"year": "2020"
},
{
"name": "Vietnam Debate Online (VNDO)",
"format": "WSDC",
"year": "2020"
},
{
"name": "Vietnam Debate Online (VNDO) - BP",
"format": "BP",
"year": "2020"
},
{
"name": "Nghe Tinh Debate Open (NTDO)",
"format": "WSDC",
"year": "2020"
},
{
"name": "Vietnam BP Championship (VBC)",
"format": "BP",
"year": "2021"
},
{
"name": "Pre VBC",
"format": "BP",
"year": "2021"
},
{
"name": "Online WSDC",
"format": "WSDC",
"year": "2020"
},
{
"name": "6th Oldham Cup",
"format": "WSDC",
"year": "2020"
},
{
"name": "MOE Invitational Debating Championship (MIDC)",
"format": "WSDC",
"year": "2020"
}
]
+10
View File
@@ -0,0 +1,10 @@
export function customTheme(theme) {
return {
...theme,
colors: {
...theme.colors,
primary25: 'grey',
primary: 'black',
}
}
}
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
export const formats = [
{ value: 'BP', label: 'BP' },
{ value: 'AP', label: 'AP' },
{ value: 'WSDC', label: 'WSDC' },
{ value: 'Others', label: 'Others' }
]
+19
View File
@@ -0,0 +1,19 @@
export * as englishIDs from './englishIDs.json'
export * as vietnameseIDs from './vietnameseIDs.json'
export * from './topics'
export * from './topicsForMotions'
export * from './formats'
export * from './languages'
export * from './customTheme'
export * from './tourneys.json'
export * from './PAtourneys.json'
export * from './MOJItourneys.json'
export * as tournamentsFromDatabase from './tournamentsFromDatabase.json'
export * as tournamentOptions from './tournamentOptions.json'
export * from './tournamentData.json'
export * from './motions.json'
export * from './motionDataRaw.json'
export * as motionsFromDatabase from './motionsFromDatabase.json'
export * from './PAmotions.json'
export * from './MOJImotions.json'
export * from './tableClassNames'
+4
View File
@@ -0,0 +1,4 @@
export const languages = [
{ value: 'English', label: 'English' },
{ value: 'Vietnamese', label: 'Vietnamese' }
]
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More