Re-implement the client
Not all website features are working, but this commit is mostly focused on an architecture that can be used in future projects
This commit is contained in:
parent
b8748509a4
commit
2bf57351ea
39 changed files with 5132 additions and 3635 deletions
|
@ -17,14 +17,10 @@ AllCops:
|
|||
- lib/**/*.rb
|
||||
- libexec/*
|
||||
- libexec/*/**
|
||||
- nanoc/rules/*
|
||||
- nanoc/lib/*
|
||||
- bin/*
|
||||
- test/*
|
||||
Exclude:
|
||||
- src/css/vendor/tail.css/bin/*
|
||||
- src/tmp/*
|
||||
- src/tmp/**/*
|
||||
|
||||
##
|
||||
# Enabled
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -4,3 +4,4 @@ gem "twenty-cli", path: "./cli"
|
|||
gem "twenty-server", path: "./server"
|
||||
gem "twenty-client", path: "./client"
|
||||
gem "listen"
|
||||
gem "server.rb", path: "../../libs/ruby/server.rb"
|
||||
|
|
16
Gemfile.lock
16
Gemfile.lock
|
@ -1,3 +1,10 @@
|
|||
PATH
|
||||
remote: ../../libs/ruby/server.rb
|
||||
specs:
|
||||
server.rb (0.2.2)
|
||||
puma (~> 6.3)
|
||||
rack (~> 3.0)
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
|
@ -24,7 +31,6 @@ PATH
|
|||
twenty-server (0.5.8)
|
||||
graphql (~> 2.2)
|
||||
sequel (~> 5.78)
|
||||
server.rb (~> 0.2.2)
|
||||
sqlite3 (~> 1.6)
|
||||
|
||||
GEM
|
||||
|
@ -43,11 +49,11 @@ GEM
|
|||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mini_portile2 (2.8.6)
|
||||
nio4r (2.7.1)
|
||||
nio4r (2.7.3)
|
||||
paint (2.3.0)
|
||||
puma (6.4.2)
|
||||
nio4r (~> 2.0)
|
||||
rack (3.0.10)
|
||||
rack (3.1.7)
|
||||
rake (13.2.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
|
@ -55,9 +61,6 @@ GEM
|
|||
ryo.rb (0.5.5)
|
||||
sequel (5.80.0)
|
||||
bigdecimal
|
||||
server.rb (0.2.2)
|
||||
puma (~> 6.3)
|
||||
rack (~> 3.0)
|
||||
sqlite3 (1.7.3)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
|
||||
|
@ -68,6 +71,7 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
listen
|
||||
rake (~> 13.2)
|
||||
server.rb!
|
||||
twenty!
|
||||
twenty-cli!
|
||||
twenty-client!
|
||||
|
|
33
README.md
33
README.md
|
@ -5,26 +5,29 @@ purpose. But it's also a place where I can experiment with
|
|||
a different stack for the development of [web] applications
|
||||
in Ruby. See **Design** for more info.
|
||||
|
||||
## Features
|
||||
|
||||
* Provides a command-line utility to start / stop a web server
|
||||
* Connect / disconnect a project from the command line
|
||||
* Designed to work offline
|
||||
* Lightweight stack
|
||||
* Easy to install, easy to use
|
||||
|
||||
## Design
|
||||
|
||||
* The server is powered by [rack](https://github.com/rack/rack) and [puma](https://github.com/puma/puma):
|
||||
- Accepts GraphQL requests at `/graphql`
|
||||
- Serves client (HTML, JS, CSS)
|
||||
- Dependencies: Sequel, SQLite3, ruby-graphql
|
||||
* The client is a statically compiled [nanoc](https://github.com/nanoc/nanoc) website:
|
||||
- Dependencies: webpack, typescript, react
|
||||
* The CLI controls the web server:
|
||||
* The [server/](server/) is powered by Ruby
|
||||
- [rack](https://github.com/rack/rack#readmne),
|
||||
[graphql-ruby](https://github.com/rmosolgo/graphql-ruby#readme),
|
||||
and [puma](https://github.com/puma/puma#readme)
|
||||
- The server provides the /graphql endpoint for client <-> server communication
|
||||
- The server serves static files (HTML, JS, CSS, ...) via [puma (Ruby HTTP server)](https://github.com/puma/puma#readme)
|
||||
- The /graphql endpoint enters the [graphql-ruby](https://github.com/rmosolgo/graphql-ruby#readme) stack
|
||||
* The [client/](client/) is powered by NodeJS
|
||||
- [webpack](https://webpack.js.org/),
|
||||
[typescript](https://www.typescriptlang.org/),
|
||||
[react](https://react.dev/),
|
||||
and [react-router](https://reactrouter.com/en/main)
|
||||
- The client produces a build/ directory
|
||||
- The client provides static files (HTML, JS, CSS, ...)
|
||||
- The client provides routes via [react-router](https://reactrouter.com/en/main)
|
||||
- The client communicates with the server via [@apollo/client (GraphQL client)](https://www.apollographql.com/docs/react/)
|
||||
* The [cli](cli/) is powered by Ruby
|
||||
- Start / stop web server
|
||||
- Run database migrations
|
||||
- Run developer console
|
||||
- Available as a RubyGem executable
|
||||
* Each component (server, client, cli) are separate packages
|
||||
in a monorepo
|
||||
* Easy to distribute as a RubyGem
|
||||
|
|
|
@ -22,14 +22,10 @@ AllCops:
|
|||
- lib/**/*.rb
|
||||
- libexec/*
|
||||
- libexec/*/**
|
||||
- nanoc/rules/*
|
||||
- nanoc/lib/*
|
||||
- bin/*
|
||||
- test/*
|
||||
Exclude:
|
||||
- src/css/vendor/tail.css/bin/*
|
||||
- src/tmp/*
|
||||
- src/tmp/**/*
|
||||
|
||||
##
|
||||
# Enabled
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
module.exports = {
|
||||
extends: ["standard-with-typescript", "standard-jsx", "prettier"],
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/member-delimiter-style": 2,
|
||||
"@typescript-eslint/semi": ["error", "always"],
|
||||
"@typescript-eslint/no-extra-semi": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/strict-boolean-expressions": 0,
|
||||
"@typescript-eslint/no-floating-promises": 0,
|
||||
"@typescript-eslint/prefer-nullish-coalescing": 0,
|
||||
"@typescript-eslint/restrict-template-expressions": 0,
|
||||
"@typescript-eslint/promise-function-async": 0,
|
||||
"@typescript-eslint/consistent-type-definitions": 0,
|
||||
"@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}],
|
||||
"@typescript-eslint/no-redeclare": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/member-delimiter-style": 0,
|
||||
"no-return-assign": 0,
|
||||
"no-useless-return": 0,
|
||||
"quotes": 0,
|
||||
"object-curly-spacing": 2,
|
||||
"n/no-callback-literal": 0,
|
||||
"import/no-absolute-path": 0
|
||||
},
|
||||
};
|
|
@ -22,14 +22,10 @@ AllCops:
|
|||
- lib/**/*.rb
|
||||
- libexec/*
|
||||
- libexec/*/**
|
||||
- nanoc/rules/*
|
||||
- nanoc/lib/*
|
||||
- bin/*
|
||||
- test/*
|
||||
Exclude:
|
||||
- src/css/vendor/tail.css/bin/*
|
||||
- src/tmp/*
|
||||
- src/tmp/**/*
|
||||
|
||||
##
|
||||
# Enabled
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
source "https://rubygems.org"
|
||||
gemspec
|
||||
|
||||
require 'rbconfig'
|
||||
case RbConfig::CONFIG['target_os']
|
||||
when /(?i-mx:bsd|dragonfly)/
|
||||
|
|
|
@ -6,78 +6,16 @@ PATH
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
colored (1.2)
|
||||
concurrent-ruby (1.2.3)
|
||||
cri (2.15.11)
|
||||
ddmetrics (1.1.0)
|
||||
ddplugin (1.0.3)
|
||||
diff-lcs (1.5.1)
|
||||
ffi (1.16.3)
|
||||
immutable-ruby (0.1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
sorted_set (~> 1.0)
|
||||
json (2.7.1)
|
||||
json_schema (0.21.0)
|
||||
language_server-protocol (3.17.0.3)
|
||||
lint_roller (1.1.0)
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
memo_wise (1.8.0)
|
||||
nanoc (4.12.19)
|
||||
addressable (~> 2.5)
|
||||
colored (~> 1.2)
|
||||
nanoc-checking (~> 1.0, >= 1.0.2)
|
||||
nanoc-cli (= 4.12.19)
|
||||
nanoc-core (= 4.12.19)
|
||||
nanoc-deploying (~> 1.0)
|
||||
parallel (~> 1.12)
|
||||
tty-command (~> 0.8)
|
||||
tty-which (~> 0.4)
|
||||
nanoc-checking (1.0.2)
|
||||
nanoc-cli (~> 4.12, >= 4.12.4)
|
||||
nanoc-core (~> 4.12, >= 4.12.4)
|
||||
nanoc-cli (4.12.19)
|
||||
cri (~> 2.15)
|
||||
diff-lcs (~> 1.3)
|
||||
nanoc-core (= 4.12.19)
|
||||
zeitwerk (~> 2.1)
|
||||
nanoc-core (4.12.19)
|
||||
concurrent-ruby (~> 1.1)
|
||||
ddmetrics (~> 1.0)
|
||||
ddplugin (~> 1.0)
|
||||
immutable-ruby (~> 0.1)
|
||||
json_schema (~> 0.19)
|
||||
memo_wise (~> 1.5)
|
||||
psych (>= 4.0, < 6.0)
|
||||
slow_enumerator_tools (~> 1.0)
|
||||
tty-platform (~> 0.2)
|
||||
zeitwerk (~> 2.1)
|
||||
nanoc-deploying (1.0.2)
|
||||
nanoc-checking (~> 1.0)
|
||||
nanoc-cli (~> 4.11, >= 4.11.15)
|
||||
nanoc-core (~> 4.11, >= 4.11.15)
|
||||
nanoc-webpack.rb (0.10.6)
|
||||
ryo.rb (~> 0.5)
|
||||
test-cmd.rb (~> 0.12.4)
|
||||
parallel (1.24.0)
|
||||
parser (3.3.0.5)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (5.0.4)
|
||||
racc (1.7.3)
|
||||
rainbow (3.1.1)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rbtree (0.4.6)
|
||||
regexp_parser (2.9.0)
|
||||
rexml (3.2.8)
|
||||
strscan (>= 3.0.9)
|
||||
|
@ -98,12 +36,6 @@ GEM
|
|||
rubocop (>= 1.48.1, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ryo.rb (0.5.5)
|
||||
set (1.1.0)
|
||||
slow_enumerator_tools (1.1.0)
|
||||
sorted_set (1.0.3)
|
||||
rbtree
|
||||
set (~> 1.0)
|
||||
standard (1.35.1)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.0)
|
||||
|
@ -116,16 +48,8 @@ GEM
|
|||
standard-performance (1.3.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop-performance (~> 1.20.2)
|
||||
stringio (3.1.0)
|
||||
strscan (3.1.0)
|
||||
test-cmd.rb (0.12.4)
|
||||
tty-color (0.6.0)
|
||||
tty-command (0.10.1)
|
||||
pastel (~> 0.8)
|
||||
tty-platform (0.3.0)
|
||||
tty-which (0.5.0)
|
||||
unicode-display_width (2.5.0)
|
||||
zeitwerk (2.6.13)
|
||||
|
||||
PLATFORMS
|
||||
amd64-freebsd-14
|
||||
|
@ -133,9 +57,6 @@ PLATFORMS
|
|||
x86_64-openbsd
|
||||
|
||||
DEPENDENCIES
|
||||
listen (~> 3.8)
|
||||
nanoc (~> 4.12)
|
||||
nanoc-webpack.rb (~> 0.10.6)
|
||||
standard (~> 1.35)
|
||||
twenty-client!
|
||||
|
||||
|
|
26
client/Rules
26
client/Rules
|
@ -1,26 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "nanoc-webpack"
|
||||
|
||||
def require_rules(rules, locals = {}, target = binding)
|
||||
locals.each { target.local_variable_set(_1, _2) }
|
||||
path = File.join(Dir.getwd, rules)
|
||||
target.eval(
|
||||
if File.readable?(path)
|
||||
File.read(path)
|
||||
elsif File.readable?("#{path}.rb")
|
||||
File.read("#{path}.rb")
|
||||
elsif File.readable?("#{path}.rules")
|
||||
File.read("#{path}.rules")
|
||||
else
|
||||
raise LoadError, "#{path} is not readable"
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
require_rules "nanoc/rules/assets"
|
||||
require_rules "nanoc/rules/react"
|
||||
|
||||
compile("/**/*") { write(nil) }
|
||||
layout "/**/*", :erb
|
|
@ -16,9 +16,6 @@ Gem::Specification.new do |gem|
|
|||
gem.description = "#{gem.summary}. " \
|
||||
"Static content (HTML, CSS, JS). " \
|
||||
"See https://rubygems.org/gems/twenty for context."
|
||||
gem.add_development_dependency "nanoc", "~> 4.12"
|
||||
gem.add_development_dependency "nanoc-webpack.rb", "~> 0.10.6"
|
||||
gem.add_development_dependency "listen", "~> 3.8"
|
||||
gem.add_development_dependency "standard", "~> 1.35"
|
||||
gem.metadata = { "source_code_uri" => "https://github.com/0x1eef/twenty#readme" }
|
||||
end
|
||||
|
|
|
@ -16,9 +16,6 @@ Gem::Specification.new do |gem|
|
|||
gem.description = "#{gem.summary}. " \
|
||||
"Static content (HTML, CSS, JS). " \
|
||||
"See https://rubygems.org/gems/<%= parent %> for context."
|
||||
gem.add_development_dependency "nanoc", "~> 4.12"
|
||||
gem.add_development_dependency "nanoc-webpack.rb", "~> 0.10.6"
|
||||
gem.add_development_dependency "listen", "~> 3.8"
|
||||
gem.add_development_dependency "standard", "~> 1.35"
|
||||
gem.metadata = { "source_code_uri" => "https://github.com/0x1eef/<%= parent %>#readme" }
|
||||
end
|
||||
|
|
16
client/eslint.config.mjs
Normal file
16
client/eslint.config.mjs
Normal file
|
@ -0,0 +1,16 @@
|
|||
import eslint from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import prettier from 'eslint-plugin-prettier/recommended';
|
||||
|
||||
export default tseslint.config(
|
||||
{ignores: ["src/js/types/schema.ts"]},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
prettier,
|
||||
{
|
||||
|
||||
rules: {
|
||||
'@typescript-eslint/no-require-imports': 0
|
||||
},
|
||||
}
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
# A list of file extensions that Nanoc will consider to be textual rather than
|
||||
# binary. If an item with an extension not in this list is found, the file
|
||||
# will be considered as binary.
|
||||
text_extensions: [ 'adoc', 'asciidoc', 'atom', 'coffee', 'css', 'erb', 'haml', 'handlebars', 'hb', 'htm', 'html', 'js', 'less', 'markdown', 'md', 'ms', 'mustache', 'php', 'rb', 'rdoc', 'sass', 'scss', 'slim', 'tex', 'txt', 'xhtml', 'xml', 'ts', 'tsx' ]
|
||||
|
||||
prune:
|
||||
auto_prune: true
|
||||
|
||||
lib_dirs: ['nanoc/lib']
|
||||
output_dir: build/
|
||||
|
||||
data_sources:
|
||||
- type: filesystem
|
||||
encoding: utf-8
|
||||
content_dir: src/
|
||||
layouts_dir: src/layouts
|
||||
|
||||
server:
|
||||
unix:
|
||||
path: /tmp/github.com.0x1eef.twenty
|
||||
tcp:
|
||||
host: 127.0.0.1
|
||||
port: 7777
|
|
@ -1,10 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
compile("/fonts/*.ttf") do
|
||||
write(item.identifier.to_s)
|
||||
end
|
||||
|
||||
compile("/favicon.svg") do
|
||||
write(item.identifier.to_s)
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
{
|
||||
"Task" => ["tasks/new", "tasks/edit"],
|
||||
"Tasks" => ["/", "tasks"],
|
||||
"Projects" => ["projects"]
|
||||
}.each do |component, paths|
|
||||
compile "/html/react.html.erb", rep: component do
|
||||
filter(:erb, locals: {component: "react-#{component.downcase}", src: "/js/main.js"})
|
||||
paths.each do |path|
|
||||
(path == "/") ? write("/index.html") : write("/#{path}/index.html")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
compile "/js/main/main.tsx" do
|
||||
buildenv = ENV["buildenv"] || "development"
|
||||
filter(
|
||||
:webpack,
|
||||
argv: ["--mode", buildenv, "--config", "webpack.#{buildenv}.js"],
|
||||
depend_on: %w[/js/components /js/hooks /js/types /js/lib /css]
|
||||
)
|
||||
write("/js/main.js")
|
||||
end
|
8206
client/package-lock.json
generated
8206
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,35 +1,41 @@
|
|||
{
|
||||
"name": "twenty",
|
||||
"devDependencies": {
|
||||
"@apollo/client": "^3.3.21",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.0.1",
|
||||
"@types/luxon": "^3.3.7",
|
||||
"@types/react": "^18.0.18",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/showdown": "^2.0.6",
|
||||
"classnames": "^2.3.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"esbuild-loader": "^4.1.0",
|
||||
"eslint": "^8.26.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"graphql": "^16.8.1",
|
||||
"luxon": "^3.4.4",
|
||||
"prettier": "^2.7.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.49.2",
|
||||
"sass": "^1.77.8",
|
||||
"sass-loader": "^16.0.0",
|
||||
"showdown": "^2.1.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"ts-standard": "^12.0.1",
|
||||
"tslib": "^2.2.0",
|
||||
"typescript": "^4.8.2",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-merge": "^5.10.0"
|
||||
"@apollo/client": "^3.3",
|
||||
"@graphql-codegen/cli": "^5.0",
|
||||
"@graphql-codegen/typescript": "^4.0",
|
||||
"@graphql-codegen/typescript-resolvers": "^4.0",
|
||||
"@types/luxon": "^3.3",
|
||||
"@types/react": "^18.0",
|
||||
"@types/react-dom": "^18.0",
|
||||
"@types/showdown": "^2.0",
|
||||
"classnames": "^2.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^12.0",
|
||||
"css-loader": "^7.1",
|
||||
"esbuild-loader": "^4.1",
|
||||
"eslint": "^9.8",
|
||||
"eslint-config-prettier": "^9.1",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"graphql": "^16.8",
|
||||
"html-webpack-plugin": "^5.6",
|
||||
"luxon": "^3.4",
|
||||
"prettier": "^3.3",
|
||||
"react": "^18.2",
|
||||
"react-dom": "^18.2",
|
||||
"react-hook-form": "^7.49",
|
||||
"react-router-dom": "^6.25.1",
|
||||
"sass": "^1.77",
|
||||
"sass-loader": "^16.0",
|
||||
"showdown": "^2.1",
|
||||
"style-loader": "^4.0",
|
||||
"tslib": "^2.2",
|
||||
"typescript": "^5.5",
|
||||
"typescript-eslint": "^8.0.0-alpha.58",
|
||||
"webpack": "^5.93",
|
||||
"webpack-cli": "^5.1",
|
||||
"webpack-dev-server": "^5.0",
|
||||
"webpack-merge": "^6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"eslint": "npm exec eslint -- --fix src/js/",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<link rel="icon" href="/favicon.svg"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="<%= component %> w-full wrapper font-sans"></div>
|
||||
<script src="<%= src %>"></script>
|
||||
<div class="w-full wrapper font-sans react-app"></div>
|
||||
<script src="<%= mainjs %>"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,8 +1,12 @@
|
|||
import { PropsWithChildren } from "react";
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import React from "react";
|
||||
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
|
||||
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
|
||||
import { AppContext } from "~/Context";
|
||||
import { Tasks } from "~/components/Tasks";
|
||||
import { Task } from "~/components/Task";
|
||||
|
||||
export function App({ children }: PropsWithChildren<{}>) {
|
||||
export function App() {
|
||||
const client = new ApolloClient({
|
||||
uri: "/graphql",
|
||||
cache: new InMemoryCache(),
|
||||
|
@ -16,6 +20,24 @@ export function App({ children }: PropsWithChildren<{}>) {
|
|||
const cookies = Object.fromEntries(
|
||||
document.cookie.split(";").map(e => e.split("=")),
|
||||
);
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Tasks />,
|
||||
},
|
||||
{
|
||||
path: "/tasks",
|
||||
element: <Tasks />
|
||||
},
|
||||
{
|
||||
path: "/tasks/new",
|
||||
element: <Task />
|
||||
},
|
||||
{
|
||||
path: "/tasks/edit",
|
||||
element: <Task />
|
||||
}
|
||||
]);
|
||||
/* allowlist: param keys acceptable as cookie keys */
|
||||
const allowlist = ["projectId"];
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
|
@ -27,9 +49,12 @@ export function App({ children }: PropsWithChildren<{}>) {
|
|||
document.cookie = `${key}=${value}; path=/`;
|
||||
}
|
||||
});
|
||||
loadDevMessages();
|
||||
loadErrorMessages();
|
||||
return (
|
||||
<AppContext.Provider value={{ params, cookies }}>
|
||||
<ApolloProvider client={client}>{children}</ApolloProvider>
|
||||
</AppContext.Provider>
|
||||
<ApolloProvider client={client}>
|
||||
<AppContext.Provider value={{ params, cookies }}/>
|
||||
<RouterProvider router={router} />
|
||||
</ApolloProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from "react";
|
||||
import type { Task } from "~/types/schema";
|
||||
import classnames from "classnames";
|
||||
import { DateTime } from "luxon";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext } from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { AppContext } from "~/Context";
|
||||
import { Maybe } from "~/types/schema";
|
||||
import { ProjectSelect } from "~/components/ProjectSelect";
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from "react";
|
||||
import { useProjects } from "~/hooks/queries/useProjects";
|
||||
import { Project, Maybe } from "~/types/schema";
|
||||
import { Select, Option } from "~/components/Select";
|
||||
|
@ -47,5 +48,4 @@ export function ProjectSelect({ onChange, selected }: Props) {
|
|||
}}
|
||||
/>
|
||||
);
|
||||
1;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ReactNode, useState, useEffect } from "react";
|
||||
import React, { ReactNode, useState, useEffect } from "react";
|
||||
import { Filter } from "./Filter";
|
||||
|
||||
const LI_CLASSNAME = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
export type Tab = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState, useContext } from "react";
|
||||
import React, { useEffect, useState, useContext } from "react";
|
||||
import { AppContext } from "~/Context";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useCreateTask } from "~/hooks/mutations/useCreateTask";
|
||||
|
@ -35,7 +35,11 @@ export function Task() {
|
|||
const res = await createTask({ variables: { input } });
|
||||
const payload = res?.data?.createTask;
|
||||
const { errors } = payload;
|
||||
errors.length ? alert(errors) : (location.href = "/tasks");
|
||||
if (errors.length) {
|
||||
alert(errors);
|
||||
} else {
|
||||
location.href = "/tasks";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -78,7 +82,11 @@ export function Task() {
|
|||
labels={["Editor", "Preview"]}
|
||||
defaultLabel={taskId ? "preview" : "editor"}
|
||||
onChange={(tab: Tab) => {
|
||||
tab.id === "editor" ? setIsEditable(true) : setIsEditable(false);
|
||||
if (tab.id === "editor") {
|
||||
setIsEditable(true);
|
||||
} else {
|
||||
setIsEditable(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isEditable ? (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from "react";
|
||||
import { Task, TaskStatus } from "~/types/schema";
|
||||
import { useUpdateTask } from "~/hooks/mutations/useUpdateTask";
|
||||
import { GET_TASKS } from "~/hooks/queries/useTasks";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useContext } from "react";
|
||||
import React, { useEffect, useContext } from "react";
|
||||
import { AppContext } from "~/Context";
|
||||
import { NavBar } from "~/components/NavBar";
|
||||
import { Group } from "~/components/Group";
|
||||
|
|
|
@ -1,36 +1,9 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "~/components/App";
|
||||
import { Tasks } from "~/components/Tasks";
|
||||
import { Projects } from "~/components/Projects";
|
||||
import { Task } from "~/components/Task";
|
||||
import "@css/main.scss";
|
||||
|
||||
(function () {
|
||||
const components = {
|
||||
Task: () => (
|
||||
<App>
|
||||
<Task />
|
||||
</App>
|
||||
),
|
||||
Tasks: () => (
|
||||
<App>
|
||||
<Tasks />
|
||||
</App>
|
||||
),
|
||||
Projects: () => (
|
||||
<App>
|
||||
<Projects />
|
||||
</App>
|
||||
),
|
||||
};
|
||||
const ents = Object.entries(components);
|
||||
for (let i = 0; i < ents.length; i++) {
|
||||
const [component, getJSX] = ents[i];
|
||||
const root = document.querySelector(`.react-${component.toLowerCase()}`);
|
||||
if (root) {
|
||||
ReactDOM.createRoot(root).render(getJSX());
|
||||
break;
|
||||
}
|
||||
}
|
||||
const root = document.querySelector(".react-app");
|
||||
ReactDOM.createRoot(root).render(<App/>);
|
||||
})();
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const webpack = require("webpack");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
entry: path.resolve(__dirname, "src/js/main/main.tsx"),
|
||||
output: {
|
||||
path: path.resolve(__dirname, "build"),
|
||||
filename: "static/js/main.js"
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': [path.resolve('src/js')],
|
||||
'@css': [path.resolve('src/css')]
|
||||
"~": [path.resolve(__dirname, "src/js")],
|
||||
"@css": [path.resolve(__dirname, "src/css")]
|
||||
},
|
||||
extensions: ['.ts', '.tsx', '.scss']
|
||||
extensions: [".ts", ".tsx", ".scss"]
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'esbuild-loader',
|
||||
loader: "esbuild-loader",
|
||||
options: {
|
||||
loader: 'tsx',
|
||||
target: 'es2015'
|
||||
loader: "tsx",
|
||||
target: "es2015"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -31,8 +39,9 @@ module.exports = {
|
|||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
React: 'react',
|
||||
}),
|
||||
new CleanWebpackPlugin(),
|
||||
new CopyWebpackPlugin({patterns: [
|
||||
{from: "./src/favicon.svg", to: "favicon.ico"}
|
||||
]}),
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
const { merge } = require('webpack-merge');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const common = require('./webpack.common.js');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = merge(
|
||||
common,
|
||||
{mode: "development"}
|
||||
{ mode: "development", devServer: {static: './build'} },
|
||||
{ plugins: [new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
templateParameters: {
|
||||
mainjs: '/static/js/main.js'
|
||||
}
|
||||
})]},
|
||||
|
||||
)
|
||||
|
||||
console.log(module.exports)
|
||||
|
|
|
@ -3,18 +3,17 @@
|
|||
cwd = File.realpath File.join(__dir__, "..", "..", "..", "client")
|
||||
desc "Start web server"
|
||||
task :server, [:protocol] do |_t, args|
|
||||
nanoc = Ryo.from_yaml(path: File.join(cwd, "nanoc.yaml"))
|
||||
h = args.to_h
|
||||
p = h[:protocol] || "tcp"
|
||||
n = File.basename File.dirname(cwd)
|
||||
Process.setproctitle "rake server[#{p}] [#{n}]"
|
||||
if p == "unix"
|
||||
Twenty::Command::Up
|
||||
.new(["-u", nanoc.server.unix.path])
|
||||
.new(["-u", "/tmp/www/twenty.al-ridwan.home.network"])
|
||||
.run
|
||||
else
|
||||
Twenty::Command::Up
|
||||
.new(["-b", nanoc.server.tcp.host, "-p", nanoc.server.tcp.port])
|
||||
.new(["-b", "127.0.0.1", "-p", "2222"])
|
||||
.run
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,14 +22,10 @@ AllCops:
|
|||
- lib/**/*.rb
|
||||
- libexec/*
|
||||
- libexec/*/**
|
||||
- nanoc/rules/*
|
||||
- nanoc/lib/*
|
||||
- bin/*
|
||||
- test/*
|
||||
Exclude:
|
||||
- src/css/vendor/tail.css/bin/*
|
||||
- src/tmp/*
|
||||
- src/tmp/**/*
|
||||
|
||||
##
|
||||
# Enabled
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
source "https://rubygems.org"
|
||||
gem 'server.rb', path: '../../../libs/ruby/server.rb'
|
||||
gemspec
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
PATH
|
||||
remote: ../../../libs/ruby/server.rb
|
||||
specs:
|
||||
server.rb (0.2.2)
|
||||
puma (~> 6.3)
|
||||
rack (~> 3.0)
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
twenty-server (0.5.8)
|
||||
graphql (~> 2.2)
|
||||
sequel (~> 5.78)
|
||||
server.rb (~> 0.2.2)
|
||||
sqlite3 (~> 1.6)
|
||||
|
||||
GEM
|
||||
|
@ -52,9 +58,6 @@ GEM
|
|||
ruby-progressbar (1.13.0)
|
||||
sequel (5.78.0)
|
||||
bigdecimal
|
||||
server.rb (0.2.2)
|
||||
puma (~> 6.3)
|
||||
rack (~> 3.0)
|
||||
sqlite3 (1.7.3)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
sqlite3 (1.7.3-aarch64-linux)
|
||||
|
@ -91,6 +94,7 @@ PLATFORMS
|
|||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
server.rb!
|
||||
standard (~> 1.35)
|
||||
test-unit (~> 3.5.7)
|
||||
twenty-server!
|
||||
|
|
|
@ -2,30 +2,54 @@
|
|||
|
||||
module Twenty::Rack
|
||||
module GraphQL
|
||||
extend self
|
||||
STATIC = [%r|/static|, %r|/favicon.ico|]
|
||||
|
||||
##
|
||||
# Extends {Server::Dir Server::Dir} (a static file
|
||||
# Rack application) with a /graphql endpoint
|
||||
#
|
||||
# @param [Hash] env
|
||||
# Environment hash
|
||||
#
|
||||
# @return [Array<Integer, Hash, #each>]
|
||||
# @return [[Integer, Hash, #each]
|
||||
# Returns a response
|
||||
def call(env)
|
||||
req = Rack::Request.new(env)
|
||||
if req.post? &&
|
||||
req.path == "/graphql"
|
||||
if req.post?
|
||||
graphql(req)
|
||||
elsif req.get? || req.head?
|
||||
file(req)
|
||||
else
|
||||
[404, {}, "".each_line]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def graphql(req)
|
||||
if req.path =~ %r|/graphql|
|
||||
params = JSON.parse(req.body.string)
|
||||
result = Twenty::GraphQL::Schema.execute(
|
||||
body = Twenty::GraphQL::Schema.execute(
|
||||
params["query"],
|
||||
variables: params["variables"],
|
||||
context: {}
|
||||
)
|
||||
[200, {"content-type" => "application/json"}, [result.to_json]]
|
||||
).to_json
|
||||
head = { "content-length" => body.bytesize, "content-type" => "application/json" }
|
||||
[200, head, body.each_line]
|
||||
else
|
||||
super(env)
|
||||
body = { errors: ["Request path was not found"] }.to_json
|
||||
head = { "content-length" => body.bytesize, "content-type" => "application/json" }
|
||||
[404, {}, body.each_line]
|
||||
end
|
||||
end
|
||||
|
||||
def file(req)
|
||||
if STATIC.find { req.path =~ _1 }
|
||||
dir = Server::Dir.new(Twenty.build)
|
||||
dir.call(req.env)
|
||||
else
|
||||
path = File.join(Twenty.build, "index.html")
|
||||
body = File.binread(path)
|
||||
head = { "content-length" => body.bytesize, "content-type" => "text/html" }
|
||||
[200, head, body.each_line]
|
||||
end
|
||||
end
|
||||
Server::Dir.prepend(self)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,9 +7,11 @@ module Twenty::Rack
|
|||
##
|
||||
# @param [Hash, #to_h] options
|
||||
# Hash of server options
|
||||
#
|
||||
# @return [Thread]
|
||||
def self.server(options = {})
|
||||
Server.dir(Twenty.build, options.to_h)
|
||||
Server.new Rack::Builder.app {
|
||||
use Server::ETag
|
||||
run Twenty::Rack::GraphQL
|
||||
}, Server.prepare(options)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
|
|||
gem.add_runtime_dependency "sequel", "~> 5.78"
|
||||
gem.add_runtime_dependency "sqlite3", "~> 1.6"
|
||||
gem.add_runtime_dependency "graphql", "~> 2.2"
|
||||
gem.add_runtime_dependency "server.rb", "~> 0.2.2"
|
||||
#gem.add_runtime_dependency "server.rb", "~> 0.2"
|
||||
gem.add_development_dependency "test-unit", "~> 3.5.7"
|
||||
gem.add_development_dependency "standard", "~> 1.35"
|
||||
gem.metadata = { "source_code_uri" => "https://github.com/0x1eef/twenty#readme" }
|
||||
|
|
|
@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
|
|||
gem.add_runtime_dependency "sequel", "~> 5.78"
|
||||
gem.add_runtime_dependency "sqlite3", "~> 1.6"
|
||||
gem.add_runtime_dependency "graphql", "~> 2.2"
|
||||
gem.add_runtime_dependency "server.rb", "~> 0.2.2"
|
||||
#gem.add_runtime_dependency "server.rb", "~> 0.2"
|
||||
gem.add_development_dependency "test-unit", "~> 3.5.7"
|
||||
gem.add_development_dependency "standard", "~> 1.35"
|
||||
gem.metadata = { "source_code_uri" => "https://github.com/0x1eef/<%= parent %>#readme" }
|
||||
|
|
Loading…
Reference in a new issue