Dear Vue.js: it's not you, it's me: I'm too good for you.
This commit is contained in:
@@ -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
34
src/frontend/api/tasks.ts
Normal 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)
|
||||
);
|
||||
25
src/frontend/components/App.tsx
Normal file
25
src/frontend/components/App.tsx
Normal 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>
|
||||
|
||||
36
src/frontend/components/TaskList.tsx
Normal file
36
src/frontend/components/TaskList.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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"));
|
||||
14
src/frontend/store/index.ts
Normal file
14
src/frontend/store/index.ts
Normal 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;
|
||||
}
|
||||
16
src/frontend/store/tasks/actions.ts
Normal file
16
src/frontend/store/tasks/actions.ts
Normal 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
|
||||
};
|
||||
}
|
||||
31
src/frontend/store/tasks/reducers.ts
Normal file
31
src/frontend/store/tasks/reducers.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/frontend/store/tasks/types.ts
Normal file
44
src/frontend/store/tasks/types.ts
Normal 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;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "../../tslint.json",
|
||||
"rules": {
|
||||
"no-implicit-dependencies": [true, "dev"]
|
||||
"no-implicit-dependencies": [true, "dev", ["@kredens/frontend"]]
|
||||
}
|
||||
}
|
||||
19
src/frontend/vue-shim.d.ts
vendored
19
src/frontend/vue-shim.d.ts
vendored
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user