Dear Vue.js: it's not you, it's me: I'm too good for you.

This commit is contained in:
2019-10-24 01:00:18 +02:00
parent bff5c60a4e
commit feda28ec47
18 changed files with 643 additions and 230 deletions

View File

@@ -1,13 +0,0 @@
<template>
<div>
<h1>Hmm...</h1>
<p> There's a message: {{ message }}.</p>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
props: ["message"]
});
</script>

34
src/frontend/api/tasks.ts Normal file
View File

@@ -0,0 +1,34 @@
import { DateTime } from "luxon";
export interface Task {
id: number;
name: string;
due: DateTime;
}
const tasks: Task[] = [...Array(5).keys()].map(n => ({
id: n,
name: `Task ${n}`,
due: DateTime.local().plus({ weeks: n })
}));
export const getTasks = async (): Promise<Task[]> =>
new Promise<Task[]>((resolve, reject) =>
setTimeout(() => resolve(tasks), 3000)
);
export const schedule = async (task: Omit<Task, "id">): Promise<Task> =>
new Promise<Task>((resolve, reject) =>
setTimeout(() => {
const id = Math.max(...tasks.map(t => t.id)) + 1;
tasks[id] = { ...task, id };
resolve(tasks[id]);
}, 3000)
);
export const unschedule = async (taskId: number): Promise<void> =>
new Promise<void>((resolve, reject) =>
setTimeout(() => {
delete tasks[taskId];
}, 3000)
);

View File

@@ -0,0 +1,25 @@
import * as React from "react";
import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom";
import TaskList from "./TaskList";
export default () => <Router>
<div>
<nav>
<ul>
<li><a href="/auth">Login</a></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/">Tasks</Link></li>
</ul>
</nav>
<Switch>
<Route path="/about">
About things, yay!
</Route>
<Route path="/">
<TaskList/>
</Route>
</Switch>
</div>
</Router>

View File

@@ -0,0 +1,36 @@
import { AppState } from "@kredens/frontend/store";
import { deleteTask, scheduleTask } from "@kredens/frontend/store/tasks/actions";
import { Task, TaskScheduleType } from "@kredens/frontend/store/tasks/types";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
export default () => {
const [taskName, setTaskName] = useState("");
const tasks = useSelector<AppState, { [key: string]: Task }>(state => state.tasks.items);
const dispatch = useDispatch();
const onTaskAddClick = () => {
dispatch(scheduleTask(Math.random().toString(36), {
name: taskName,
schedule: { type: TaskScheduleType.Plain }
}))
};
const onTaskDeleteClick = (id: string) => {
dispatch(deleteTask(id))
};
return (
<>
<ul>
{
Object.entries(tasks).map(([id, task]) => (
<li key={id}>{task.name} <button onClick={() => onTaskDeleteClick(id)}>Delete</button></li>
))
}
</ul>
<input value={taskName} onChange={ev => setTaskName(ev.target.value)} />
<button onClick={onTaskAddClick}>Add!</button>
</>
);
}

View File

@@ -13,19 +13,19 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import Vue from "vue";
import App from "./App.vue";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./components/App";
import configureStore from "./store";
export const app = new Vue({
components: {
App
},
el: "#body",
render(createElement) {
return createElement(App, {
props: {
message: "blab"
}
});
}
});
const store = configureStore();
const Root = () => (
<Provider store={store}>
<App />
</Provider>
);
ReactDOM.render(<Root />, document.getElementById("body"));

View File

@@ -0,0 +1,14 @@
import { combineReducers, createStore } from "redux";
import { tasksReducer } from "./tasks/reducers";
const rootReducer = combineReducers({
tasks: tasksReducer
});
export type AppState = ReturnType<typeof rootReducer>;
export default function configureStore() {
const store = createStore(rootReducer);
return store;
}

View File

@@ -0,0 +1,16 @@
import { Task, TasksAction, TasksActionType } from "./types";
export function scheduleTask(id: string, task: Task): TasksActionType {
return {
type: TasksAction.SCHEDULE_TASK,
id,
task
};
}
export function deleteTask(id: string): TasksActionType {
return {
type: TasksAction.DELETE_TASK,
id
};
}

View File

@@ -0,0 +1,31 @@
import { TasksAction, TasksActionType, TasksState } from "./types";
const initialState: TasksState = {
items: {}
};
export function tasksReducer(
state = initialState,
action?: TasksActionType
): TasksState {
switch (action.type) {
case TasksAction.SCHEDULE_TASK:
return {
...state,
items: {
...state.items,
[action.id]: action.task
}
};
case TasksAction.DELETE_TASK:
return {
...state,
items: Object.entries(state.items)
.filter(([key]) => key !== action.id)
.reduce((res, [key, task]) => ({ ...res, [key]: task }), {})
};
default: {
return state;
}
}
}

View File

@@ -0,0 +1,44 @@
export enum TaskScheduleType {
Once,
Plain
}
export interface TaskScheduleOnce {
type: TaskScheduleType.Once;
due: string;
}
export interface TaskSchedulePlain {
type: TaskScheduleType.Plain;
}
export type TaskSchedule = TaskScheduleOnce | TaskSchedulePlain;
export interface Task {
name: string;
schedule: TaskSchedule;
}
export interface TasksState {
items: {
[key: string]: Task;
};
}
export enum TasksAction {
SCHEDULE_TASK = "SCHEDULE_TASK",
DELETE_TASK = "DELETE_TASK"
}
interface ScheduleTaskAction {
type: typeof TasksAction.SCHEDULE_TASK;
id: string;
task: Task;
}
interface DeleteTaskAction {
type: typeof TasksAction.DELETE_TASK;
id: string;
}
export type TasksActionType = ScheduleTaskAction | DeleteTaskAction;

View File

@@ -1,6 +1,6 @@
{
"extends": "../../tslint.json",
"rules": {
"no-implicit-dependencies": [true, "dev"]
"no-implicit-dependencies": [true, "dev", ["@kredens/frontend"]]
}
}

View File

@@ -1,19 +0,0 @@
// Copyright (C) 2019 ModZero <modzero@modzero.xyz>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}

View File

@@ -17,9 +17,9 @@ import express from "express";
import authRouter from "./auth";
import homeRouter from "./home";
const router = express.Router();
const router = express.Router({ strict: true });
router.use("/", homeRouter);
router.use("/auth", authRouter);
router.use("/auth$", authRouter);
router.use("/*", homeRouter);
export default router;