Compare commits

...

874 Commits

Author SHA1 Message Date
3498915234 added min-height 2022-09-30 22:39:24 +02:00
fa22d4596e implement comment handlers 2022-09-30 22:35:06 +02:00
e781efc5ae added Post model 2022-09-30 22:32:56 +02:00
b88dcddf56 implement Comments logic to server 2022-09-30 22:32:31 +02:00
1e6a076b34 fix PostCreator keydown handler logic 2022-09-30 22:32:20 +02:00
7c015f04af implement CommentCreator logic 2022-09-30 21:49:30 +02:00
6baeeb794f advise if user is not logged in 2022-09-30 16:26:52 +02:00
6524520436 added search item to sidebar 2022-09-30 16:26:36 +02:00
ce07ba04f5 bump version 2022-09-30 15:56:08 +02:00
bdd4c57bd7 use model register 2022-09-30 15:54:14 +02:00
0d55c5ddf7 implement register 2022-09-30 15:53:58 +02:00
400274bb4e handle app.openRegister 2022-09-30 15:53:44 +02:00
9744c172ea remove administration pages 2022-09-30 15:53:16 +02:00
4a8f80c75c fix: submit form when next is called but has no more steps 2022-09-30 15:52:59 +02:00
efe9035fc0 update gitignore 2022-09-30 00:33:13 +02:00
0dd8182c05 improve release script 2022-09-30 00:32:56 +02:00
bab6ae0a99 added 7zip-min 2022-09-30 00:32:48 +02:00
5a8226b149 update .gitignore 2022-09-30 00:02:34 +02:00
ed7932e9c3 bump version 2022-09-29 23:58:47 +02:00
7e2765cf97 added release script 2022-09-29 23:58:28 +02:00
383c8af40e added example.env 2022-09-29 23:14:51 +02:00
a2943b3dbe added authPath 2022-09-29 23:14:44 +02:00
9c20aa82f9 added electron-builder 2022-09-29 23:14:33 +02:00
bdb6c10597 remove desktop package 2022-09-29 23:14:22 +02:00
ec5836d5a4 Added package description 2022-09-29 22:01:00 +02:00
ee2c8952e8 non logged users navs to login on "/" route 2022-09-29 22:00:51 +02:00
fd69c27bd5 fix login style 2022-09-29 21:44:13 +02:00
e61937622f fix const 2022-09-29 21:42:57 +02:00
abd6f24135 update linebridge 2022-09-29 21:01:35 +02:00
d013cf104b fix paths 2022-09-29 19:55:30 +02:00
21690676e1 implement desktop versions 2022-09-29 16:59:56 +02:00
f1b9a36885 added min-height 2022-09-29 16:03:52 +02:00
a59d9a4671 update .gitignore 2022-09-29 14:14:48 +02:00
1e67cb7c80 fix width 2022-09-29 14:14:01 +02:00
9be707f843 support disabled creators 2022-09-29 13:39:28 +02:00
83afd0b0e6 added inline todo's 2022-09-29 13:33:51 +02:00
f07e0060ea fix title word break 2022-09-29 13:33:42 +02:00
df7a661d4d format 2022-09-29 13:28:38 +02:00
4fec089f72 fix uploader colors 2022-09-29 12:49:18 +02:00
6320f76f3b added Playlist Creator 2022-09-29 12:31:14 +02:00
cd0f121c65 show message on playlist 2022-09-29 12:31:07 +02:00
7c041aa68f fix buttons style 2022-09-29 11:53:20 +02:00
36af24d555 fix primary button disabled color 2022-09-29 11:46:38 +02:00
c6030368bf fix default button color & margin 2022-09-29 11:39:17 +02:00
562ec6561a move creators to folders 2022-09-29 10:10:23 +02:00
98929af2de remove unwanted log 2022-09-29 10:08:06 +02:00
9edfa9a12b clear default theme 2022-09-29 10:07:40 +02:00
1a751c7c30 fix colors 2022-09-28 23:42:03 +02:00
160b2312ba clean some 2022-09-28 23:41:53 +02:00
95d2154e07 Merge branch 'master' of https://github.com/ragestudio/comty 2022-09-26 15:54:46 +02:00
0088b2f371 added Creator 2022-09-26 15:54:42 +02:00
srgooglo
7fb3997d66
Merge pull request #65 from ragestudio/posts-comments
0.20.0
2022-09-26 15:53:45 +02:00
83af4ca70a implement Modal to default layout 2022-09-26 15:48:36 +02:00
494ded2069 added Modal to layout 2022-09-26 15:48:20 +02:00
2ef7259767 handle onPost 2022-09-26 15:46:29 +02:00
630e64b4d7 bump version 2022-09-21 22:12:32 +02:00
ef881800a4 handle custom events on click 2022-09-21 22:12:18 +02:00
79cb7aa2a9 fix text color 2022-09-21 22:12:08 +02:00
aa6a24dce6 format 2022-09-21 22:11:59 +02:00
511e0a9444 added minio dep 2022-09-21 19:09:31 +02:00
71c37a1e5c use StorageClient 2022-09-21 19:09:22 +02:00
0a55547e3e added PlaylistController 2022-09-21 19:07:48 +02:00
d1d5cc15ae implement s3 2022-09-21 19:07:39 +02:00
6050aab515 added videoTranscode lib 2022-09-21 19:03:43 +02:00
1c6a777e58 added toBoolean 2022-09-21 19:02:47 +02:00
c1e38700c6 added StorageClient 2022-09-21 19:02:35 +02:00
a9467902dd implement AudioPlayer core 2022-09-20 00:52:33 +02:00
1d96dab892 implement media player 2022-09-20 00:52:20 +02:00
0386efefd1 added post types 2022-09-20 00:51:56 +02:00
aea5601acd support post types 2022-09-20 00:51:37 +02:00
f1cd2b2ced show no data 2022-09-20 00:51:26 +02:00
9a7dde3f7e update antd 2022-09-20 00:51:17 +02:00
7f7aa51912 added no_song asset 2022-09-20 00:51:09 +02:00
db309eff08 cover expands on click 2022-09-19 15:23:46 +02:00
3b07c8f25a fullfill nullish 2022-09-19 15:23:30 +02:00
srgooglo
c6953bdc6a
Merge pull request #64 from ragestudio/posts-comments
Posts comments
2022-09-16 15:06:47 +02:00
d95898dfa3 added CommentsCards 2022-09-16 15:03:15 +02:00
79f22acd62 update exports 2022-09-16 15:03:00 +02:00
7afb980e77 added CommentsCards to components 2022-09-16 15:02:52 +02:00
6711a2c2c9 improve fullmode post layout 2022-09-16 15:02:40 +02:00
5b8ca313fe expose CommentsControllers to api 2022-09-16 15:02:22 +02:00
6b10e4f778 implement CommentsController basics 2022-09-16 15:02:11 +02:00
decf416dd9 update schema 2022-09-16 15:01:50 +02:00
4439b6693c added CommentCreator 2022-09-16 15:01:36 +02:00
6b26762faa added text-color 2022-09-16 15:00:56 +02:00
d89c2a1c7a added customRequest to api core 2022-09-13 17:49:23 +02:00
01339a175f fix endpoint 2022-09-09 20:38:27 +02:00
4913dedb30 added PostSave methods 2022-09-09 20:38:18 +02:00
adeb82629e added withOptionalAuthentication middleware 2022-09-09 18:57:31 +02:00
ff5f156ed0 fix signLifetime 2022-09-09 17:48:46 +02:00
4a2c852547 app now not automatly open login drawer 2022-09-09 17:44:06 +02:00
97e959f9d7 reimplement authentification process and regeneration system 2022-09-09 17:42:21 +02:00
12938553d7 fix endpoints 2022-09-09 17:41:39 +02:00
b29f51332c update linebridge 2022-09-09 17:41:06 +02:00
eb1f349cb4 fix DeletePost method 2022-09-09 13:12:19 +02:00
5ad8dbed87 reimplement PostController 2022-09-09 12:23:24 +02:00
7cf549d65f update comment schema 2022-09-08 20:41:02 +02:00
7ce1677b30 implement delete and put methods 2022-09-08 20:40:44 +02:00
54867d7eb1 fix style 2022-09-08 20:35:24 +02:00
6234cae12c format 2022-09-08 19:08:57 +02:00
07d99cd68d added notifications page 2022-09-08 19:05:38 +02:00
a7f47ea059 remove streams page 2022-09-08 19:05:28 +02:00
45d646e609 added more cards 2022-09-08 19:05:20 +02:00
c3d8b47c61 update font-family for h1,h2 2022-09-08 19:05:12 +02:00
927be26ece update administration 2022-09-08 19:04:26 +02:00
429a12cbf4 added Notification bottom sidebar item 2022-09-08 19:04:15 +02:00
f3d1029502 update export 2022-09-08 19:03:39 +02:00
c78141a622 added TODO 2022-09-08 19:03:33 +02:00
ff6ba48f8a added HashtagTrendings component 2022-09-08 19:03:24 +02:00
3a6a7b3fcc remove unnecesary sidebar keys 2022-09-08 19:03:05 +02:00
aaf5454b3a added panels to home 2022-09-08 18:41:04 +02:00
44db4fc2c7 added LivestreamsBrowser 2022-09-08 18:40:54 +02:00
d14af06f09 improve account page 2022-09-08 16:52:54 +02:00
1c6e460d25 remove justify-content 2022-09-08 16:52:06 +02:00
71491750d2 expose BadgesController to api 2022-09-08 16:51:25 +02:00
fc751a101d added react-loadable dep 2022-09-08 16:51:12 +02:00
f11f30a7dc fix badge schema 2022-09-08 16:50:54 +02:00
83dc6bb2f0 added BadgesController 2022-09-08 16:50:44 +02:00
4d4b9b3050 format 2022-09-07 17:44:11 +02:00
1d42463c85 remove unwanted log 2022-09-07 17:43:57 +02:00
49e43a37d2 added author to schema 2022-09-07 17:35:25 +02:00
48bfa5da4d fix new_featured_wallpaper 2022-09-07 17:35:17 +02:00
b9381982d2 added login page 2022-09-07 17:35:04 +02:00
861e2485d9 fix style 2022-09-07 16:31:34 +02:00
c030bafb8a remove no_session event 2022-09-07 16:31:21 +02:00
7224f3dec7 fix copyright 2022-09-07 16:31:02 +02:00
aeffda6ea1 split author and copyright 2022-09-07 16:30:53 +02:00
27333be7ac implement featuredWallpaper endpoints 2022-09-07 15:18:56 +02:00
e39225b338 added featuredWallpaper model 2022-09-07 15:18:42 +02:00
80aae3b17b added Space Grotesk to fonts 2022-09-07 15:18:12 +02:00
90b126db68 moved backgroundDecorator to layout 2022-09-07 15:17:53 +02:00
d2e3c6008f remove antd.Layout 2022-09-07 15:17:29 +02:00
f25a5a9541 added Footer component 2022-09-07 15:17:12 +02:00
54242ada92 added footerLinks to config 2022-09-07 15:17:02 +02:00
aee251c692 fix withAuthentication 2022-09-07 13:51:20 +02:00
7b311834ab fix session.created logic 2022-09-07 13:32:45 +02:00
b67e3a6db0 handle transitions behavior when not exist an transitionLayer 2022-09-07 13:27:31 +02:00
f43a20548e improve sessions.created behavior 2022-09-07 13:26:37 +02:00
0726833e9b added login layout 2022-09-07 13:26:19 +02:00
be8bc6f633 improve layouts 2022-09-07 13:17:17 +02:00
0ff3ac3016 remove unwanted logs 2022-09-07 12:43:42 +02:00
f50d3aeb24 add page title 2022-09-07 12:29:22 +02:00
ef5ef4c90f improve router and page transition logic 2022-09-07 12:29:06 +02:00
1c534a3946 update debug profiles 2022-09-07 12:28:21 +02:00
c8155ad435 added post page 2022-09-07 11:15:54 +02:00
f1c2c38700 open post when double click 2022-09-07 11:15:38 +02:00
7b655a9a65 added fullmode 2022-09-07 11:15:25 +02:00
adbb5fca95 fix router setLocation 2022-09-07 11:14:36 +02:00
94228561b1 fix middlewares withAuthentication 2022-09-05 04:40:15 +02:00
eb75941d52 declare middlewares every endpoint 2022-09-04 03:28:01 +02:00
3d034e9ccf added administration page 2022-09-03 04:58:02 +02:00
54f8cbfb53 remove capacitor storage support 2022-09-03 04:54:08 +02:00
a1a1a460a6 clean 2022-09-03 04:53:59 +02:00
da6c1837cf check permissions route 2022-09-03 04:53:46 +02:00
e1ca4f5b31 added permissions core 2022-09-03 04:53:29 +02:00
7f144e8053 update routes 2022-09-03 04:53:19 +02:00
eab2c8d91f move explore page to home 2022-09-03 03:47:41 +02:00
480d5eefd6 index move to mainPath 2022-09-03 03:46:44 +02:00
89a5f9ffae attach to content api 2022-09-03 03:45:34 +02:00
2000562659 added extension tab settings 2022-09-03 03:34:50 +02:00
75e35b234d check if data is array 2022-09-03 03:27:03 +02:00
aa9211e75c split controller init 2022-08-05 20:04:02 +02:00
9290fc0a5d fix route 2022-08-05 20:03:43 +02:00
dfcf086d6c remove serverStatus component 2022-08-04 17:35:12 +02:00
2cb806fde9 remove websocketApi 2022-08-04 17:30:17 +02:00
f44b07b8e6 update linebridge dep 2022-08-04 17:29:29 +02:00
503c1f0772 added auth_api 2022-08-04 17:06:24 +02:00
0ece8d3cab update to linebridge 0.12.0 2022-08-04 17:06:00 +02:00
6d99264686 bump version 2022-08-04 12:48:36 +02:00
srgooglo
16c418f2b1
Merge pull request #63 from ragestudio/apis-controller
reimplement api, with namespaces
2022-08-04 12:47:03 +02:00
a09b79aaea reimplement api, with namespaces 2022-08-04 12:46:20 +02:00
8d42494f10 added early mediaPlayer 2022-08-04 11:57:45 +02:00
9f8f66391a set classname atribute on new DomWindow 2022-08-04 11:57:14 +02:00
4e57d40bcb implement attached elements 2022-08-04 11:56:41 +02:00
3336107811 added cors origin 2022-08-04 11:56:22 +02:00
bba2e31a6b update linebridge 2022-07-01 05:43:05 +02:00
f441a9a02c update default remotes 2022-07-01 05:42:34 +02:00
bd8285762a added multiple envOrigins depending on env 2022-07-01 05:42:25 +02:00
8c67d78d45 rewrited live player 2022-07-01 05:39:40 +02:00
2ed179e918 improve api connection details error 2022-06-29 20:13:29 +02:00
14b302c598 fix development streamingApi address 2022-06-29 19:32:57 +02:00
8425a7b906 fix style apply for live 2022-06-29 19:31:46 +02:00
6bdf9161b5 improve style.autoDarkModeToogle method naming 2022-06-29 19:31:17 +02:00
9b356584eb added style.compactMode handler 2022-06-29 19:30:39 +02:00
424b6bcc1f added getValue to methods 2022-06-29 19:30:24 +02:00
6bf6b8f9a4 added default style.compactMode 2022-06-29 19:29:27 +02:00
c598869675 added layoutMargin & layoutPadding to defaultTheme 2022-06-29 19:29:04 +02:00
d1d4479838 use layoutMargin & layoutPadding from root vars 2022-06-29 19:28:37 +02:00
6f6b7a39e3 remove compactMode logic from layout 2022-06-29 19:28:11 +02:00
59b6a205af move stream player to live page 2022-06-27 08:30:58 +02:00
6607f182ff update linebridge 2022-06-27 08:30:36 +02:00
59523637f3 fix publics envs 2022-06-10 05:40:41 +02:00
bbf7d32ad0 added fix_posts_data endpoint 2022-06-10 05:40:18 +02:00
1b9e005b6d export resolveToUrl 2022-06-06 23:32:47 +02:00
d4812d44cc use express static to server /storage 2022-06-06 23:24:24 +02:00
9fd287bc48 added TODO 2022-06-06 18:42:41 +02:00
9451d8a12f added failed array 2022-06-06 18:42:35 +02:00
8f1155a6d0 added a little padding on top and bottom 2022-06-06 17:29:48 +02:00
0307e866c4 use concurrent map for processing 2022-06-06 17:28:41 +02:00
f67f7152c0 added utils 2022-06-06 17:28:21 +02:00
ba35218f3c improve pending additions process 2022-06-06 17:28:14 +02:00
85477efb8d use class component for PostAdditions 2022-06-06 17:27:58 +02:00
b3bc39daef remove unwanted log 2022-06-06 16:23:47 +02:00
752703a664 fix url prop 2022-06-06 16:19:28 +02:00
445f6552bc disable visibilities on submit 2022-06-06 16:11:07 +02:00
76a038e395 bump version 2022-06-06 16:10:15 +02:00
49999e8acb rewrite component & support additions uploads 2022-06-06 16:09:54 +02:00
a0f641957f fix fetchingData is on false by default 2022-06-06 16:09:34 +02:00
bef9d7552f split PostAdditions component & new addition model 2022-06-06 16:09:11 +02:00
eec2f03ccb support additions 2022-06-06 16:08:49 +02:00
ec7de4b52f increase max files per req 2022-06-06 16:08:26 +02:00
fd9687bef0 remove unnecesary import 2022-06-06 14:54:24 +02:00
bee41497ae added className to bottom 2022-06-06 14:52:43 +02:00
35f7098a14 better loadingIcon style 2022-06-06 14:52:24 +02:00
7def0add0c use display flex to center items 2022-06-06 14:52:15 +02:00
fd5b6b675d remove dep 2022-06-06 14:45:56 +02:00
98ecda8751 fix LoadingComponent was causing memory leaks 2022-06-06 14:45:51 +02:00
1b085aabd6 observer.disconnect when unmount 2022-06-06 14:44:48 +02:00
srgooglo
666b6b2817
Merge pull request #62 from ragestudio/files-controller
Files controller
2022-06-06 12:55:31 +02:00
a938016cea videoTranscode support options 2022-06-06 12:52:54 +02:00
657b557880 use formidable & video transcoder 2022-06-06 12:49:45 +02:00
8107926b4a added uploads dir to gitignore 2022-06-06 12:17:40 +02:00
4f3194141e added express json & urlencoded body parser 2022-06-06 12:16:41 +02:00
98fee074d0 fix middlewares position 2022-06-06 12:07:39 +02:00
23ac71c95e added uploadCachePath 2022-06-06 12:07:09 +02:00
7b7b5650be update deps 2022-06-06 11:32:29 +02:00
573c515860 use express as http engine 2022-06-06 11:32:24 +02:00
e13193f39c disable key toLowerCase 2022-06-06 10:30:03 +02:00
171235bdb9 added todo 2022-06-06 10:29:48 +02:00
c705f11b7e refactor to use functional component 2022-06-06 10:29:42 +02:00
9e8bb06921 fix query trim feed 2022-06-06 10:00:49 +02:00
0b836b2bc9 added deps 2022-06-06 10:00:37 +02:00
fb99301a3c load more posts on feed 2022-06-06 10:00:32 +02:00
fbba723c2c added LoadMore component 2022-06-06 09:59:57 +02:00
ecb43e1a4c added feed_max_fetch setting 2022-06-06 09:59:44 +02:00
b09b82a627 fix mimeType and set extension with lower case 2022-06-05 11:43:32 +02:00
9b3fd166b9 bump version 2022-06-05 11:12:55 +02:00
2aa5c70de6 use new tab methods 2022-06-05 11:09:54 +02:00
089f0826a8 fix renderError style 2022-06-05 10:52:11 +02:00
f06ab1d598 cleanup 2022-06-04 21:19:08 +02:00
e060be2fb9 fix border radius 2022-06-04 21:18:45 +02:00
1a3235013c remove Swipper.Item 2022-06-04 21:18:36 +02:00
d59cae979a implement autoCarrousel & expansibleActions support 2022-06-04 21:15:06 +02:00
a85c157003 implement forceUpdate event 2022-06-04 21:14:19 +02:00
9171b9fa03 update default settinmgs 2022-06-04 21:14:03 +02:00
094770b782 added postCard_expansible_actions setting 2022-06-04 21:13:56 +02:00
00dea75988 added postCard_carrusel_auto 2022-06-04 07:42:29 +02:00
a3006708c4 added posts 2022-06-04 07:42:07 +02:00
f94720c34b added compactWidth setting 2022-06-04 07:06:38 +02:00
e426d7a90b fix message color 2022-06-04 06:28:39 +02:00
c12cb48a48 fix arrows 2022-06-04 06:26:31 +02:00
b7f560e867 clean up 2022-06-04 06:23:28 +02:00
dbd483e768 use new carrusel 2022-06-04 06:07:43 +02:00
4ca1d27138 force build 2022-06-03 23:55:57 +02:00
ac7419ece3 bump version 2022-06-03 21:15:38 +02:00
78c69e6711 use React.createElement 2022-06-03 21:15:23 +02:00
8a6335762d added processString util 2022-06-03 21:13:19 +02:00
f04b4994d6 added message parsing 2022-06-03 21:12:32 +02:00
31733fcd72 added cors options 2022-06-03 20:56:20 +02:00
de775f07a8 fix media render 2022-06-03 20:32:26 +02:00
2b66a44d28 update scripts 2022-06-03 19:35:53 +02:00
394e327430 added cors to server 2022-06-03 19:34:16 +02:00
c3ec2845f1 use savePostData method 2022-06-03 06:26:41 +02:00
ceb4bb9406 remove react-virtualized 2022-06-03 06:26:29 +02:00
4359a1b1b3 added unlisten method 2022-06-03 06:26:08 +02:00
d47f07fe8b pass props.key 2022-06-03 06:25:50 +02:00
b4b2094253 use new PostCard component & use new listing method 2022-06-03 06:25:40 +02:00
85a0d206bb fix style 2022-06-03 06:25:07 +02:00
3bc42bff3c Rewrite in order to use react hooks 2022-06-03 06:03:31 +02:00
e1f68623dc implement toogleLike & cleanup 2022-06-03 05:54:29 +02:00
d69c18e02e handle post.delete events 2022-06-02 21:08:01 +02:00
d590906bb0 fix onDelete prop 2022-06-02 21:07:23 +02:00
eb9b2f4277 format 2022-06-02 20:50:02 +02:00
b8436056c8 added not implemented messages 2022-06-02 20:48:08 +02:00
14937817a4 bump version 2022-06-02 00:59:30 +02:00
0c2ecb5def fix goToAccount 2022-06-02 00:57:16 +02:00
8b7d4b7443 bump version 2022-06-02 00:45:20 +02:00
8ef36e0d40 remove header calls 2022-06-02 00:44:47 +02:00
7068ac021b remove header from layout 2022-06-02 00:44:37 +02:00
18b9fea6b8 implement background images supports 2022-06-02 00:37:03 +02:00
8a032790cd update antd 2022-06-02 00:36:43 +02:00
3a229d2874 pass ctx props 2022-06-02 00:36:31 +02:00
a3995eb988 implement background settings 2022-06-02 00:36:10 +02:00
74168bab9d moved to each folder 2022-06-02 00:35:53 +02:00
48fa9dc010 update default theme 2022-06-02 00:34:43 +02:00
17596ea54a use Image component 2022-06-01 22:43:37 +02:00
54c5472d86 added Cover & Avatar update settings 2022-06-01 21:42:22 +02:00
27c2a26f27 added cover to AllowedPublicUpdateFields 2022-06-01 21:42:05 +02:00
6341247ee5 update deps 2022-06-01 21:41:50 +02:00
81fb5c9d9c avatar more bigger 2022-06-01 21:41:25 +02:00
2af42b80e7 added Image to components 2022-06-01 21:41:01 +02:00
f46b3a4bf7 update deps 2022-06-01 21:14:07 +02:00
021e08a163 force install 2022-06-01 21:03:38 +02:00
ce8bdc8510 added react to peerDependencies 2022-06-01 21:02:50 +02:00
1b10769cee remove capacitor from package 2022-06-01 20:58:04 +02:00
3a1811f5ac fix install dev 2022-06-01 20:56:57 +02:00
8039280ba5 bump version 2022-06-01 20:56:08 +02:00
6017b91a95 stop method if user is not existent 2022-06-01 20:55:57 +02:00
68631f27bf cleanup 2022-06-01 20:55:09 +02:00
a366565d7c support user indexation in url 2022-06-01 20:55:02 +02:00
3aeebbcead handle not existent user 2022-06-01 20:54:31 +02:00
06fe19f684 not update isOnTransition when reduceAnimations is enabled 2022-06-01 20:38:49 +02:00
7b7e0445ea added PageLoad static render 2022-06-01 20:36:52 +02:00
64076a21da fix props 2022-06-01 20:36:32 +02:00
4686fb6bfe install only dev deps 2022-06-01 20:27:46 +02:00
131a2d3d6b force binstall 2022-06-01 20:25:05 +02:00
3d8c25c7cc remove peerdeps 2022-06-01 20:25:00 +02:00
6ca00bf474 install deps on build 2022-06-01 20:24:01 +02:00
d5a8e1b71a copy entire package 2022-06-01 20:23:09 +02:00
0fa964414b fix set locations on events 2022-06-01 20:19:02 +02:00
9f3a538492 Render layout even when has renderlock 2022-06-01 20:18:55 +02:00
6c1699447f disable config.build 2022-06-01 20:17:55 +02:00
751a1ffa01 update Dockerfile 2022-06-01 20:17:46 +02:00
c305661c89 bump version 2022-06-01 19:52:23 +02:00
97d55ff324 update aliases 2022-06-01 19:48:57 +02:00
87625f99a6 update deps 2022-06-01 19:48:51 +02:00
5227b947a4 remote pages plugin 2022-06-01 19:48:44 +02:00
c16586e701 added router 2022-06-01 19:48:34 +02:00
c3dd462113 remove render core 2022-06-01 19:48:23 +02:00
dbcb39357c toggleVisibility on mount instead event 2022-06-01 19:48:14 +02:00
c460f95775 Use new Router & Layout 2022-06-01 19:47:54 +02:00
8cbd891993 added build config 2022-06-01 05:07:02 +02:00
bd844f7be8 fix ant result content flex direction 2022-06-01 05:04:26 +02:00
f0de3fa1cf use SidedrawerController 2022-06-01 03:16:29 +02:00
7dbeeba2a7 move order 2022-06-01 03:15:51 +02:00
48dc8a2493 support elevation 2022-06-01 03:15:44 +02:00
40b78237fe set string lenght limits 2022-06-01 03:15:34 +02:00
dc740d7a8f set 150 ms transition 2022-06-01 03:14:11 +02:00
954d231da4 rewrite new Sidedrawer controller 2022-06-01 03:11:23 +02:00
5ba73d13eb update default remotes 2022-05-31 22:02:00 +02:00
79feaa0608 create a substring if exceed maximung lenght 2022-05-31 21:55:11 +02:00
de78da3f47 close drawer when clicked about 2022-05-31 21:49:46 +02:00
af0fcf52d8 change default websocket api port 2022-05-31 21:46:47 +02:00
663029066b fix events 2022-05-31 21:44:58 +02:00
f7a9635c72 move order 2022-05-31 21:44:52 +02:00
77f5f61461 update evite 2022-05-31 21:44:43 +02:00
22065de20f select text on crash code 2022-05-31 21:44:38 +02:00
5f6a1738e3 implement MaxStringLenghts 2022-05-31 21:16:53 +02:00
ee120f0cd0 fix routes 2022-05-31 21:16:42 +02:00
b8c1680418 bump version 2022-05-31 21:03:21 +02:00
de725f42b0 fix open animation 2022-05-31 21:02:51 +02:00
ee5daf47ce added default crash details 2022-05-31 20:15:53 +02:00
e3ea0bf1ef fix Mask 2022-05-31 04:45:20 +02:00
2963298f63 go to /about when about button is clicked 2022-05-31 04:29:46 +02:00
5a02fe4ad2 added build script 2022-05-31 04:07:37 +02:00
b173f81678 added static deployment 2022-05-31 02:47:36 +02:00
be63bbad3d added Dockerfile and docker-compose 2022-05-31 02:29:50 +02:00
a0dcff4922 use dotenv 2022-05-31 02:28:13 +02:00
54bcad1790 bump version 2022-05-31 02:27:14 +02:00
aea40be34e fix methods namings 2022-05-31 02:26:04 +02:00
8353de34a2 fix events namigs 2022-05-31 02:25:57 +02:00
2b262f3e47 improve Splash style 2022-05-31 02:13:55 +02:00
ae5760f800 correct toggle methods namings 2022-05-31 02:10:35 +02:00
fa56bf33eb format 2022-05-31 02:09:53 +02:00
f59b6748ed correct toggle 2022-05-31 02:09:48 +02:00
1b8aab1bf9 set default auto_darkMode to false 2022-05-31 01:56:28 +02:00
20c5adec71 update evite 2022-05-31 01:54:21 +02:00
36262132f3 fix publicEvents and eventHandlers 2022-05-31 01:53:03 +02:00
ade7db35dc handle auto color scheme 2022-05-31 01:29:09 +02:00
1562ac7555 disable depensOn for auto_darkMode 2022-05-31 01:29:00 +02:00
srgooglo
cfbbafac7d
Merge pull request #60 from ragestudio/core-integration
Fix forgotten changes
2022-05-31 01:13:00 +02:00
srgooglo
e970a892f2
Merge branch 'master' into core-integration 2022-05-31 01:12:56 +02:00
63613e58f2 fix cores initialization 2022-05-31 01:09:36 +02:00
78a7ac018d handle auto prefered color scheme 2022-05-31 01:09:24 +02:00
8590a364d2 fix render core 2022-05-31 01:04:01 +02:00
aa84b5d006 fix depends validation & unsubscribe update events for unmounted items 2022-05-31 00:51:04 +02:00
4f19250d07 added auto_darkMode 2022-05-31 00:46:02 +02:00
3aaf668903 dependsOn now should check every update 2022-05-31 00:45:47 +02:00
5e9678aa3b support custom components 2022-05-31 00:32:04 +02:00
321131d529 update streamingApi remote 2022-05-31 00:19:49 +02:00
srgooglo
b48450e92f
Merge pull request #59 from ragestudio/core-integration
Core integration
2022-05-31 00:18:53 +02:00
eb17c036a2 fix setLocation 2022-05-30 22:29:53 +02:00
4f1098af9e use correct cores to initialize app 2022-05-30 22:28:17 +02:00
b020f4018b fix bindContext 2022-05-30 22:28:00 +02:00
c9c13e895c fix constructor 2022-05-30 22:27:50 +02:00
30d0b8044d use new evite model 2022-05-30 22:08:50 +02:00
611558abbb update evite 2022-05-30 22:08:35 +02:00
27f6480be4 remove extensions 2022-05-30 22:08:29 +02:00
7d98769806 implemented cores 2022-05-30 22:08:23 +02:00
0e402fad56 only render code details if has that prop 2022-05-30 22:08:15 +02:00
a13a0a50cf added cores alias 2022-05-30 21:40:29 +02:00
956dcec7f3 update crash style 2022-05-30 21:35:57 +02:00
54c08e99a1 fix events 2022-05-30 16:50:18 +02:00
30aadbd5d6 improve initialization crash process 2022-05-30 16:49:51 +02:00
f398358afc use display:flex 2022-05-30 16:49:03 +02:00
dfcd9829e8 added CrashWrapper 2022-05-30 16:48:31 +02:00
26d7e1bc03 use development remotes 2022-05-30 16:47:52 +02:00
00ab91453b added streaming information inputs placeholders 2022-05-29 18:35:45 +02:00
2478ee31ea Merge branch 'master' of https://github.com/ragestudio/comty 2022-05-29 18:27:04 +02:00
14d6ac9954 added initialization steps finish events 2022-05-29 18:26:55 +02:00
1be4ed3c61 emit initialization steps error on eventBus 2022-05-29 18:25:15 +02:00
2664548fac added about page 2022-05-29 18:24:43 +02:00
bba10b30e3 split modalCard 2022-05-29 18:24:35 +02:00
db2c511e64 use padding instead margin 2022-05-29 18:12:51 +02:00
fc6bf55f86 use and button 2022-05-29 18:08:32 +02:00
52a7eca414 space 2022-05-29 17:49:52 +02:00
130097f5df update build cmds 2022-05-28 20:43:26 +02:00
672b778ae4 added corenode dep & added build script 2022-05-28 20:39:09 +02:00
6f939105d2 update ports 2022-05-28 20:37:12 +02:00
da689b31dd added dotenv 2022-05-28 20:37:07 +02:00
be1ae1c5bd added docker-compose and Dockerfile 2022-05-28 20:26:11 +02:00
c5f3713d28 rename streamingKey 2022-05-28 20:25:33 +02:00
srgooglo
f2c75d1e2a
Merge pull request #58 from ragestudio/login-drawer
Login drawer
2022-05-19 20:31:05 +02:00
7de4f21222 remove parentesis 2022-05-19 20:28:54 +02:00
992f0ac259 use initialize method 2022-05-19 20:24:53 +02:00
16c97b51ab Merge branch 'login-drawer' of https://github.com/ragestudio/comty into login-drawer 2022-05-19 19:51:50 +02:00
0b3997c3cc fix import 2022-05-19 19:48:33 +02:00
9def7a31b0 emit session.logout 2022-05-19 19:48:33 +02:00
5e23aa1642 bump version 2022-05-19 19:48:33 +02:00
4e56b2af76 added Login component 2022-05-19 19:48:33 +02:00
1f2831e47a added app.createLogin & app.logout handlers 2022-05-19 19:48:33 +02:00
7106b43e30 remove login from pages 2022-05-19 19:48:33 +02:00
704500f50a added formWrapper style 2022-05-19 19:48:33 +02:00
f42a05c3c6 fix authMechanism 2022-05-19 19:45:04 +02:00
56b8d8b44a split DB manager to a external class 2022-05-19 19:44:54 +02:00
12c2dd3e49 use getConnectionConfig 2022-05-14 19:44:38 +02:00
c4d8d34b00 use getConnectionConfig 2022-05-14 19:43:55 +02:00
94400bbd53 fix import 2022-05-13 22:23:19 +02:00
e94d8bd351 emit session.logout 2022-05-13 22:09:16 +02:00
4bf49f4050 bump version 2022-05-13 21:48:27 +02:00
b9638f0c2c added Login component 2022-05-13 21:48:06 +02:00
61bcdff5fb added app.createLogin & app.logout handlers 2022-05-13 21:47:57 +02:00
5ae5c73b23 remove login from pages 2022-05-13 21:47:27 +02:00
3bfd9b2117 added formWrapper style 2022-05-13 21:35:49 +02:00
e5a0ce109d bump version 2022-05-13 20:31:08 +02:00
a54304ec54 player events 2022-05-13 20:30:38 +02:00
0293a5cd6f return response if only has data 2022-05-13 20:13:06 +02:00
b579890c0c update settings 2022-05-13 20:12:18 +02:00
10a316c64d generate settings tabs from one object 2022-05-13 20:12:11 +02:00
e09c050a53 remove unused imports 2022-05-13 15:16:39 +02:00
db726c2c61 added internal-nms 2022-05-13 15:13:02 +02:00
5971e5bb2c get server status from internal server 2022-05-13 15:10:06 +02:00
b5396530fd added /**/**/cache to git ignore 2022-05-13 15:06:30 +02:00
b16b7a9fa4 added mediaroot 2022-05-13 14:58:53 +02:00
bcaf757894 Merge branch 'master' of https://github.com/ragestudio/comty 2022-05-13 14:56:35 +02:00
eb5911b32c added deps 2022-05-13 14:56:10 +02:00
8694d65b2e display empty 2022-05-13 14:56:06 +02:00
0a3eb7eba0 added plyr options 2022-05-13 14:55:48 +02:00
12f2b9be67 use internal media server 2022-05-13 14:46:54 +02:00
7f75d988c2 added cpu lib 2022-05-13 14:46:43 +02:00
15751617e7 write head on stream flv mode 2022-05-13 14:46:21 +02:00
c11505bacf write head on stream flv mode 2022-05-13 09:53:55 +02:00
7c39556ae1 expose methods to window 2022-05-13 09:53:39 +02:00
52062b1897 use direct piping instead create a buffer 2022-05-13 09:42:33 +02:00
eff643a445 update style 2022-05-12 21:15:39 +02:00
cb562d1c65 find by id instead query 2022-05-12 21:04:51 +02:00
7691702c10 fix query 2022-05-12 21:04:16 +02:00
fa0768c803 update default remotes 2022-05-12 20:24:56 +02:00
908201519e update default remotes 2022-05-12 20:15:27 +02:00
6ca59b24b3 fix websocket remote address 2022-05-12 19:35:13 +02:00
c5dd2b3525 , 2022-05-12 19:32:39 +02:00
srgooglo
dcc3eb2151
Merge pull request #57 from ragestudio/streaming-server
Streaming server
2022-05-12 19:32:03 +02:00
c49283243b added streams to default sidebar keys 2022-05-12 19:30:47 +02:00
0c0b47527c added basic streaming control 2022-05-12 19:30:16 +02:00
223b8d36f4 get streams from username 2022-05-12 19:28:13 +02:00
b976a595df update StreamViewer 2022-05-12 19:28:01 +02:00
ec249b70db added get/stream_config_from_username endpoint 2022-05-12 19:27:26 +02:00
fd31478636 get remote addresses from remotes config 2022-05-12 19:26:09 +02:00
a099cff27a set default language to en 2022-05-12 19:25:22 +02:00
f6bbeb5c66 update remotes config 2022-05-12 19:25:08 +02:00
046dd43831 fix methods & added missings 2022-05-12 19:03:40 +02:00
b02bf6877c fix ant-result colors 2022-05-12 19:02:59 +02:00
9175befcfc format 2022-05-12 19:02:39 +02:00
c5a020a899 apply variant only storage variant from eventBus 2022-05-12 17:22:04 +02:00
21bbb5c9df update darkMode setting 2022-05-12 17:21:30 +02:00
ff50b5d4f3 make border 0 on hidden mode 2022-05-12 17:00:31 +02:00
c33003b3c6 added withEvent method 2022-05-12 16:52:08 +02:00
ac6e8c9129 added compact mode 2022-05-12 16:51:59 +02:00
735b4dbbb4 added rxjs dep 2022-05-12 16:51:43 +02:00
d3169737b1 added layout_page modes 2022-05-12 16:51:34 +02:00
c4de104b0a added lodash lib 2022-05-12 16:24:53 +02:00
3acc8cca8b extend get/streams with internal api details 2022-05-12 16:24:46 +02:00
618f951aa2 fetch list of streams 2022-05-12 15:00:52 +02:00
5cb5f85b45 get public streams methods 2022-05-12 14:59:14 +02:00
2f717a179e generate keys with username 2022-05-12 14:58:54 +02:00
86d2bce01f update streamingKey schema 2022-05-12 14:58:33 +02:00
182040d37a stream pipe with streamingUserspace username resolver 2022-05-12 14:58:09 +02:00
a86918d3b9 fix addresses 2022-05-12 11:05:52 +02:00
27972f3f5f initialize http server 2022-05-12 10:53:43 +02:00
ff34d2900b added publishStream & unpublishStream methods 2022-05-12 10:53:30 +02:00
bb2cd23c4b fetch data from media server api 2022-05-12 10:53:15 +02:00
0ae6cbc0d9 added streaming routes 2022-05-12 10:40:16 +02:00
1a0a53445e added basic streaming server classes 2022-05-12 10:40:00 +02:00
bbd0c1d5bb added @ffmpeg-installer/ffmpeg dep 2022-05-12 10:39:41 +02:00
10eb64692f reject client session when session is removed or ended 2022-05-12 10:39:30 +02:00
d295ddda53 fix streamingKey schema 2022-05-12 10:39:05 +02:00
39ecbf3ee3 added StreamingController with basic methods 2022-05-12 10:38:44 +02:00
f149060dff added streamingKey to models 2022-05-12 10:38:24 +02:00
9b44010bc3 added basic streaming control panel 2022-05-12 10:38:09 +02:00
06f38164b3 added mongoose dep 2022-05-10 20:45:55 +02:00
d154c36062 added StreamingKeys schema 2022-05-10 20:45:45 +02:00
fa2e40c75d added StreamingKeys model 2022-05-10 20:45:40 +02:00
f00a15548b add managers 2022-05-10 20:45:24 +02:00
df151c5a0a add lib 2022-05-10 20:45:15 +02:00
65a5ecc3b6 added streaming-server package 2022-05-10 20:04:49 +02:00
50f6ca2c14 added todo 2022-05-10 19:58:10 +02:00
srgooglo
b7837284a9
Merge pull request #56 from ragestudio/linebridge-update
Linebridge update
2022-05-06 19:01:21 +02:00
7eb67cc124 update linebridge dep 2022-05-06 19:00:43 +02:00
9e4721579a added comments 2022-05-06 19:00:29 +02:00
0105d79977 remove unwanted log 2022-05-06 19:00:12 +02:00
08928cdae9 update linebridge dep 2022-05-06 18:48:47 +02:00
07e01c7471 Merge branch 'linebridge-update' of https://github.com/ragestudio/comty into linebridge-update 2022-05-06 17:43:29 +02:00
88101b767e update linebridge dep 2022-05-06 17:43:21 +02:00
efaabf8cf2 update linebridge imports 2022-05-06 17:43:12 +02:00
srgooglo
aac047423e
Merge pull request #55 from RubenPX/linebridge-update
fix typo `autocapitalize` & `autoCorrect`
2022-05-06 15:00:37 +02:00
b7739080ab fix typo autocapitalize & autoCorrect 2022-05-06 14:57:31 +02:00
80cc6e0721 update linebridge dep 2022-05-06 13:56:57 +02:00
2674438c4e dont encode login body payload 2022-05-06 13:27:15 +02:00
db5a0bfcac update linebridge dep 2022-05-06 13:26:07 +02:00
d498a03505 not decode password username on passport stratergy 2022-05-06 12:27:17 +02:00
3190fba4b7 remove req type checking 2022-05-06 12:12:37 +02:00
cdb752dd1b update linebridge dep 2022-05-06 12:10:49 +02:00
85667624b3 update linebridge lib 2022-05-06 11:40:09 +02:00
b7864e7a39 update linebridge dep 2022-05-06 11:36:47 +02:00
2a9f086f14 use res.json to parse responses 2022-05-06 11:17:13 +02:00
402f0d4557 update linebridge dep 2022-05-06 11:16:50 +02:00
f9acfa8de9 update linebridge 2022-05-05 10:45:19 +02:00
f3f02e8095 disable upload endpoints 2022-05-05 10:45:10 +02:00
dddee4009b remove @nanoexpress/middleware-file-upload/cjs from middlewares 2022-05-05 10:45:01 +02:00
302fbe301b added plyr-react 2022-04-04 21:43:38 +02:00
7c49505551 early implement media viewer & self menu 2022-04-04 21:43:28 +02:00
c08fc6cf8e pass initial props from feed controller 2022-04-04 21:43:00 +02:00
03ce741235 added additions to schemas 2022-04-04 21:42:48 +02:00
cac10348f6 added delete methods 2022-04-04 21:42:37 +02:00
c80e3b9763 clean 2022-03-28 19:53:54 +02:00
58148d6c3e temporaly disable build config 2022-03-16 04:44:12 +01:00
171ac2596a remove unused import 2022-03-16 04:43:52 +01:00
ea34632669 fix entry 2022-03-16 04:42:43 +01:00
a47c7c4593 remove index 2022-03-16 04:42:13 +01:00
3fc8720d81 bump version 2022-03-16 04:34:51 +01:00
eefac29eba fix versions 2022-03-16 04:34:46 +01:00
d0b16d2abb added basic style 2022-03-16 04:29:31 +01:00
34d18dd464 added text to tabs 2022-03-16 04:29:19 +01:00
eb97bbbaf9 fix session controller 2022-03-16 04:28:57 +01:00
2745104b7c reimplement with evite extension v2 2022-03-16 04:28:44 +01:00
e41cf70b51 refactor for use evite 2022-03-16 04:28:26 +01:00
76ab58d388 added splash style 2022-03-16 04:28:13 +01:00
74ab0045e5 use App.jsx as vite entry 2022-03-16 04:25:57 +01:00
6d14778682 update evite 2022-03-16 04:25:47 +01:00
aa48ae1a78 fix wrap style 2022-03-15 19:13:26 +01:00
14c036862d fix style of tabs 2022-03-15 18:42:02 +01:00
79231d2c6a reimplement account style 2022-03-15 04:24:48 +01:00
fbb1f9a62b restyle ant-avatar to set border-radius 2022-03-15 04:24:37 +01:00
925e5ee673 added FollowersList 2022-03-15 04:24:00 +01:00
c4c3defc4a set white color 2022-03-15 04:23:51 +01:00
c94a4a47e2 delete local components 2022-03-15 03:15:01 +01:00
36d28e7b06 splitted to account.profile settings group 2022-03-15 03:14:13 +01:00
b48c48a0c4 added update profile description field 2022-03-15 03:11:33 +01:00
8b165ae8d3 fix preventDefault 2022-03-15 03:11:16 +01:00
3586ba9d8b added TextArea 2022-03-15 03:10:47 +01:00
3e52ff1699 added counter 2022-03-15 03:10:37 +01:00
5f1b11569a fix AllowedPublicUpdateFields for only apply to public methods 2022-03-15 03:10:22 +01:00
76c9e905b9 extend user schema 2022-03-15 03:09:56 +01:00
d7cca6ce27 include createdAt 2022-03-15 03:09:43 +01:00
4379c6e0da followers 2022-03-15 01:54:00 +01:00
ebc0909c15 implement follow methods 2022-03-15 01:05:09 +01:00
9fc04da329 update models 2022-03-15 01:05:01 +01:00
703e133054 update exports 2022-03-15 01:04:26 +01:00
88bd3269fa added Badge schema 2022-03-15 01:04:20 +01:00
b3c3e7e1d8 added UserFollow schema 2022-03-15 01:03:54 +01:00
15186e4f1a reimplement follow methods & styling 2022-03-15 01:03:36 +01:00
10a471343c added FollowButton 2022-03-15 01:03:21 +01:00
423e305072 implement follow methods 2022-03-15 00:15:47 +01:00
c69584dc15 target node 16 2022-03-14 23:19:06 +01:00
cfce86db1f update readme 2022-03-14 23:13:26 +01:00
68023b1915 remove trash 2022-03-14 23:07:15 +01:00
bbbc0ea86a update gitignore 2022-03-14 23:07:00 +01:00
a75200a751 temporary delete wrapper 2022-03-14 23:06:30 +01:00
srgooglo
928552454d
Merge pull request #54 from ragestudio/rewrite
Rewrite
2022-03-14 23:03:39 +01:00
6411a79e6f added mongo db service to docker-compose on server 2022-03-14 22:59:42 +01:00
a3b5558749 remove skip 2022-03-14 22:59:23 +01:00
9ba5039e93 register keybinds depending of navigator useragent 2022-03-14 22:58:31 +01:00
f5826fdb9d added deps 2022-03-14 22:58:11 +01:00
819fdb070f added build config 2022-03-14 22:58:05 +01:00
9aceb9bc8a use rc-virtual-list 2022-03-14 22:57:53 +01:00
c0ada28b7a update remotes 2022-03-14 22:57:29 +01:00
1dc446d5d1 AnimatedPostCard 2022-03-14 22:57:23 +01:00
0115e34c5d added desktop package 2022-03-14 22:57:09 +01:00
19df441d1c await to save 2022-03-14 22:56:51 +01:00
28c655882c added esc keybind 2022-03-14 22:06:25 +01:00
61452bfb7d use PostsFeed component 2022-03-14 22:06:13 +01:00
d77145e1e3 use Shortcuts extension 2022-03-14 22:05:50 +01:00
fea6a88a1d format 2022-03-14 22:05:37 +01:00
5ef619b68b added Shortcuts extension 2022-03-14 22:05:32 +01:00
f045c38cb4 implement server setup 2022-03-14 18:45:33 +01:00
0b3619e901 added server setup scripts 2022-03-14 18:45:23 +01:00
00b125f575 update Config schema 2022-03-14 18:45:03 +01:00
446813f51e added Comment model & schema 2022-03-14 18:44:54 +01:00
1fbaef76a9 use lib 2022-03-14 18:44:35 +01:00
cde9caa24b added createUser to lib 2022-03-14 18:44:26 +01:00
81167c455e use PostsFeed component 2022-03-11 22:52:44 +01:00
4da1a0b506 added PostsFeed component 2022-03-11 22:52:25 +01:00
b34b2f9b90 change remotes origin 2022-03-11 22:42:58 +01:00
64edf048e2 use methods 2022-03-11 01:07:38 +01:00
c2a2e2ff25 update user schema 2022-03-11 01:07:33 +01:00
771989b7c9 set routeStyle to next 2022-03-11 01:07:25 +01:00
2f01fe7291 update antd 2022-03-11 01:07:07 +01:00
dc0983010e use theme var to set font family 2022-03-11 01:06:54 +01:00
72c377032b added Recursive font 2022-03-11 01:06:36 +01:00
009ccda2c6 remove unused const 2022-03-11 01:06:24 +01:00
dd4e38175f use " 2022-03-11 01:06:09 +01:00
3f259e5531 fix styling 2022-03-11 01:05:58 +01:00
16a7e8454b added debugComponents 2022-03-11 01:05:40 +01:00
9d99ad4002 goToProfile & verified badge 2022-03-11 01:05:30 +01:00
0634e24b8c added custom icons 2022-03-11 01:05:03 +01:00
dd7524385e added tabs to settings 2022-03-11 01:04:47 +01:00
751cb23683 update default theme 2022-03-11 01:04:18 +01:00
67ce4a9f61 added back Debug extension 2022-03-11 01:04:06 +01:00
779e9c763a use color 2022-03-03 18:16:10 +01:00
e85500a00f improve setting update event 2022-03-03 18:16:00 +01:00
6c3f918109 update primaryColor setting on reset default theme 2022-03-03 18:15:30 +01:00
3a122674ae fix selfMenu icon 2022-03-03 16:21:32 +01:00
3cf8758ef4 switch share action to save 2022-03-02 22:12:55 +01:00
5a753e6be7 update routes 2022-03-02 22:12:35 +01:00
fa9604abdb use primarycolor 2022-03-02 22:06:33 +01:00
e3e5444d9c improve shadow-color 2022-03-02 22:06:04 +01:00
6a3e0658e5 added actions indicator notch 2022-03-02 21:53:49 +01:00
b52dd47375 moved post statistics to post header 2022-03-02 21:40:44 +01:00
bbf51f1f7d improve night mode 2022-03-02 21:40:23 +01:00
5a83e2c11c mix opacity with actions bar when hover the card 2022-03-02 21:05:53 +01:00
85f24c973b improve color & outline color & shadow color 2022-03-02 20:56:32 +01:00
53e5c8302e added shadow color 2022-03-02 20:56:18 +01:00
c8b1263a6f listen collapse settings change 2022-03-02 20:36:51 +01:00
f8701a22c9 format 2022-03-02 20:36:41 +01:00
0ac5a3f3e8 fix locked items can be deleted 2022-03-02 20:36:32 +01:00
b1b1a7fc38 handle dependsOn 2022-03-02 20:36:16 +01:00
9a6a0d937e use max-width 2022-03-02 20:34:44 +01:00
a6d7fd6d54 use like logic 2022-03-02 20:34:17 +01:00
501983f615 added LikeButton 2022-03-02 20:34:06 +01:00
af86376e79 update settings 2022-03-02 20:33:52 +01:00
5646e847da update routes 2022-03-02 19:25:06 +01:00
4083a618af update default settings 2022-03-02 19:25:01 +01:00
6f87790979 update default color 2022-03-02 19:24:54 +01:00
7c93bf4ca2 added fullLogo 2022-03-02 19:24:48 +01:00
12ddf3b3ae improve like action & await to server response 2022-03-02 17:14:13 +01:00
fe25d6d54f added like button logic 2022-03-02 17:01:22 +01:00
154cc51274 added ´like´ & ´unlike´ methods 2022-03-02 17:01:15 +01:00
cf16e3343e update post schema 2022-03-02 16:08:46 +01:00
44eb21a777 added like method 2022-03-02 16:08:33 +01:00
c90b924a00 fetch methods 2022-03-02 16:08:20 +01:00
a46902bb1e update exports 2022-03-02 16:08:02 +01:00
dd17cbe0ff added basis 2022-03-02 16:07:57 +01:00
b4ee145e33 added rc-virtual-list dep 2022-03-02 16:07:37 +01:00
587e5b2f32 use createIconRender 2022-03-02 16:07:20 +01:00
c1ced9e4c1 added PostCreator 2022-03-02 16:07:11 +01:00
4c225c6014 update remotes 2022-03-02 16:07:02 +01:00
083b42340e use defaultRemotes 2022-03-02 16:06:52 +01:00
e3d06b7823 rewrite to new models 2022-02-22 20:22:41 +01:00
5467192ef2 implement PostController 2022-02-22 20:22:25 +01:00
a2f825fe10 update constants 2022-02-22 20:22:13 +01:00
238ea51427 update config 2022-02-22 19:44:55 +01:00
ba94adfa18 update assets 2022-02-22 19:44:41 +01:00
67577485b0 rewrite for use linebridge 0.10.x 2022-02-22 19:44:27 +01:00
7207cf5388 update package 2022-01-03 18:37:44 +01:00
775b3ea2e9 refactor controllers 2022-01-03 18:37:38 +01:00
6f50106fd3 refactor lib 2022-01-03 18:37:29 +01:00
cbc791db35 update repo 2022-01-03 18:37:15 +01:00
a7f01730eb refactor App with evite scattfold 2022-01-03 18:36:51 +01:00
9becc55879 update package 2022-01-03 18:36:33 +01:00
d7c4ee039e remove assets 2022-01-03 18:36:26 +01:00
a8653211b4 added vite support 2022-01-03 18:36:20 +01:00
5be9229456 update public assets 2022-01-03 18:35:47 +01:00
1fa3e720e7 update config 2022-01-03 18:35:35 +01:00
3b5c67937f added constants 2022-01-03 18:35:25 +01:00
270ae41104 update all 2021-12-06 18:06:51 +01:00
1e49c9bd84 remove unused 2021-11-17 18:38:33 +01:00
815d591b83 clean 2021-11-17 18:36:56 +01:00
dc96cf271b initialize basic scatfold 2021-11-17 18:35:55 +01:00
44eceb487b use evite as engine 2021-11-17 17:58:04 +01:00
08fbde4f7f rename folder 2021-11-16 17:53:34 +01:00
e08da3942b cleanup & upgrade 2021-11-16 17:51:00 +01:00
89943a6446 updated splash screen 2021-07-01 10:46:37 +02:00
srgooglo
326c63b457 updated packages 2021-05-11 13:25:16 +02:00
srgooglo
f975406092 ✳ added server package 2021-01-11 06:48:26 +01:00
srgooglo
ac3d2e67cc cleaun packages 2021-01-05 09:56:04 +01:00
srgooglo
3ec1ac1997 updated: general settings code cuality 2021-01-05 09:43:39 +01:00
srgooglo
028448cc32 config cleanup 2021-01-05 08:50:24 +01:00
srgooglo
239e7eca68 removed debug from pages 2021-01-05 08:17:20 +01:00
srgooglo
9941d9ea95 refactor settings components 2021-01-05 08:16:31 +01:00
srgooglo
b5c9ce90a4 removed: debuggers 2021-01-05 08:14:35 +01:00
srgooglo
bbcc181ee4 clean 2021-01-05 08:11:21 +01:00
srgooglo
0980990d0e clean 2021-01-05 08:11:12 +01:00
srgooglo
3a5af895a3 updated packages 2021-01-05 08:07:16 +01:00
srgooglo
a39a70e4cb added: wrapper to packages, update: code quality, update: changed verbosity lib to @nodecorejs 2021-01-05 07:57:55 +01:00
srgooglo
362284690b boostrap new proyect model 2020-12-14 20:29:29 +01:00
srgooglo
3c10943ba6 new scaffolding 2020-12-14 19:48:49 +01:00
srgooglo
e78154ea09 update packages 2020-12-08 01:31:13 +01:00
srgooglo
d7362ce479
Update README.md 2020-11-29 15:49:20 +01:00
srgooglo
413487a154 Merge branch 'master' of https://github.com/srgooglo/comty 2020-11-29 12:34:28 +01:00
srgooglo
773b6343f0 add newStreaming 2020-11-29 12:33:18 +01:00
srgooglo
89348aa1db
Merge pull request #40 from srgooglo/snyk-upgrade-e9230396cf7c227ac6ea2555c769e13b
[Snyk] Upgrade @ragestudio/nodecore-utils from 0.1.18 to 0.1.19
2020-11-28 13:57:13 +01:00
snyk-bot
9dcb2e4419
fix: upgrade @ragestudio/nodecore-utils from 0.1.18 to 0.1.19
Snyk has created this PR to upgrade @ragestudio/nodecore-utils from 0.1.18 to 0.1.19.

See this package in npm:
https://www.npmjs.com/package/@ragestudio/nodecore-utils

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-28 00:56:33 +00:00
srgooglo
daa0f31c24
Merge pull request #39 from srgooglo/snyk-upgrade-2edc1f1b3e94a1bacbb012f11926c0b1
[Snyk] Upgrade @types/lodash from 4.14.164 to 4.14.165
2020-11-27 07:35:06 +01:00
snyk-bot
a036fe4239
fix: upgrade @types/lodash from 4.14.164 to 4.14.165
Snyk has created this PR to upgrade @types/lodash from 4.14.164 to 4.14.165.

See this package in npm:
https://www.npmjs.com/package/@types/lodash

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-27 00:48:53 +00:00
srgooglo
cd6ea940ed add cloudlink to core 2020-11-26 14:57:22 +01:00
srgooglo
f8c6b8a6ab
Merge pull request #38 from srgooglo/snyk-upgrade-2a8f3fe2575798b270bfa8e965199751
[Snyk] Upgrade electron-log from 4.2.4 to 4.3.0
2020-11-24 06:13:01 +01:00
snyk-bot
d4b9bca4b0
fix: upgrade electron-log from 4.2.4 to 4.3.0
Snyk has created this PR to upgrade electron-log from 4.2.4 to 4.3.0.

See this package in npm:
https://www.npmjs.com/package/electron-log

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-24 00:47:04 +00:00
srgooglo
cad1e93ec8
Merge pull request #36 from srgooglo/snyk-upgrade-b3e199c7c3c1c24334ae383ddd13a1c3
[Snyk] Upgrade react-color from 2.18.1 to 2.19.3
2020-11-23 06:02:17 +01:00
srgooglo
2ceea7d154
Merge pull request #35 from srgooglo/snyk-upgrade-a6eb8b4985a80daf891453238820fa18
[Snyk] Upgrade @types/lodash from 4.14.162 to 4.14.164
2020-11-23 06:02:02 +01:00
srgooglo
95b1405b95
Merge pull request #37 from srgooglo/snyk-upgrade-c002ca3d765fc0297fc88d8548585602
[Snyk] Upgrade react-redux from 7.2.1 to 7.2.2
2020-11-23 06:01:47 +01:00
snyk-bot
fb72ee89fd
fix: upgrade react-redux from 7.2.1 to 7.2.2
Snyk has created this PR to upgrade react-redux from 7.2.1 to 7.2.2.

See this package in npm:
https://www.npmjs.com/package/react-redux

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-23 00:52:59 +00:00
snyk-bot
5cf9760540
fix: upgrade react-color from 2.18.1 to 2.19.3
Snyk has created this PR to upgrade react-color from 2.18.1 to 2.19.3.

See this package in npm:
https://www.npmjs.com/package/react-color

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-23 00:52:52 +00:00
snyk-bot
8ce213f863
fix: upgrade @types/lodash from 4.14.162 to 4.14.164
Snyk has created this PR to upgrade @types/lodash from 4.14.162 to 4.14.164.

See this package in npm:
https://www.npmjs.com/package/@types/lodash

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-23 00:52:45 +00:00
srgooglo
72859c2fbd fixed unused props 2020-11-22 04:11:14 +01:00
srgooglo
dc1e364b25 fixed missing package 2020-11-22 00:04:37 +01:00
srgooglo
281757a187 Merge branch 'master' of https://github.com/srgooglo/comty 2020-11-21 23:33:24 +01:00
srgooglo
8aa8fd405e changed overlaySwapper method 2020-11-21 23:33:17 +01:00
srgooglo
96d2a3ae8b
Update README.md 2020-11-18 18:47:13 +01:00
srgooglo
94fa7f2ce6
Merge pull request #34 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-11-17 18:58:50 +01:00
codefactor-io
82d2809ccb [CodeFactor] Apply fixes to commit d50aeb1 2020-11-17 17:58:00 +00:00
srgooglo
d50aeb14fc updated & cleaned layout components, other refactors 2020-11-17 18:57:40 +01:00
srgooglo
89ebf3e4e8 clean themes 2020-11-17 15:09:09 +01:00
srgooglo
a801bab8a1
Merge pull request #32 from srgooglo/snyk-upgrade-bcb6569fb23be5395d6e6125978562ee
[Snyk] Upgrade ts-jest from 26.4.2 to 26.4.3
2020-11-17 14:43:43 +01:00
srgooglo
4b6916cad2
Merge pull request #33 from srgooglo/snyk-upgrade-79f0d1dc239a858e6b636a0d2328c4e8
[Snyk] Upgrade react-redux from 7.2.1 to 7.2.2
2020-11-17 14:43:00 +01:00
snyk-bot
1c3899c97d
fix: upgrade react-redux from 7.2.1 to 7.2.2
Snyk has created this PR to upgrade react-redux from 7.2.1 to 7.2.2.

See this package in npm:
https://www.npmjs.com/package/react-redux

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-17 00:52:02 +00:00
snyk-bot
5b788f0e51
fix: upgrade ts-jest from 26.4.2 to 26.4.3
Snyk has created this PR to upgrade ts-jest from 26.4.2 to 26.4.3.

See this package in npm:
https://www.npmjs.com/package/ts-jest

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-17 00:51:53 +00:00
srgooglo
417522367e
Merge pull request #31 from srgooglo/snyk-upgrade-f707a48ab939f3daa5c75a6f47f81887
[Snyk] Upgrade howler from 2.2.0 to 2.2.1
2020-11-16 07:24:07 +01:00
snyk-bot
035246c248
fix: upgrade howler from 2.2.0 to 2.2.1
Snyk has created this PR to upgrade howler from 2.2.0 to 2.2.1.

See this package in npm:
https://www.npmjs.com/package/howler

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-16 00:47:34 +00:00
srgooglo
96ac872a30 replace app_config schema to app runtime 2020-11-15 12:43:11 +01:00
srgooglo
5023e8dee6
Merge pull request #30 from srgooglo/snyk-upgrade-8fc24f95beebd1d0619557e4d45303c5
[Snyk] Upgrade ts-jest from 26.4.1 to 26.4.2
2020-11-15 11:07:03 +01:00
srgooglo
05b39ad532
Merge pull request #29 from srgooglo/snyk-upgrade-b5b2198d002d220455f06b28e01b22aa
[Snyk] Upgrade axios from 0.20.0 to 0.21.0
2020-11-15 11:06:47 +01:00
snyk-bot
efceb8fd85
fix: upgrade ts-jest from 26.4.1 to 26.4.2
Snyk has created this PR to upgrade ts-jest from 26.4.1 to 26.4.2.

See this package in npm:
https://www.npmjs.com/package/ts-jest

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-14 01:12:17 +00:00
snyk-bot
5452ed4930
fix: upgrade axios from 0.20.0 to 0.21.0
Snyk has created this PR to upgrade axios from 0.20.0 to 0.21.0.

See this package in npm:
https://www.npmjs.com/package/axios

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-14 01:12:09 +00:00
srgooglo
e17a8d9b82
Merge pull request #28 from srgooglo/snyk-upgrade-cd3a985465ccde75fe904f67661b52fd
[Snyk] Upgrade @types/jest from 26.0.14 to 26.0.15
2020-11-11 17:51:17 +01:00
snyk-bot
f20804f7bd
fix: upgrade @types/jest from 26.0.14 to 26.0.15
Snyk has created this PR to upgrade @types/jest from 26.0.14 to 26.0.15.

See this package in npm:
https://www.npmjs.com/package/@types/jest

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-11-11 00:43:13 +00:00
srgooglo
0e7c94db0c added moreMenu on postCard & updated to antd 4.8 2020-11-02 19:16:21 +01:00
srgooglo
3bb5646713 added saved and post 2020-11-02 18:41:14 +01:00
srgooglo
f7c48a6228 changed fit-content 2020-11-02 17:31:08 +01:00
srgooglo
d8fb515afd refactor & clean icons 2020-11-02 17:22:31 +01:00
srgooglo
eb833b6412 fix offset 2020-11-02 15:39:16 +01:00
srgooglo
57aedb84d4 added follow support 2020-10-30 19:35:38 +01:00
srgooglo
7f28f854b1 fixed likeBtn blur animation & profile avatar & followers count 2020-10-30 14:15:34 +01:00
srgooglo
1b557a49af added user posts in profile & collapse sidebar 2020-10-30 13:49:06 +01:00
srgooglo
7f43427cb8 fixed overlay & primaryLayout on mobile mode 2020-10-30 13:04:45 +01:00
srgooglo
94015e7f1e changed like method & added post_autoposition setting 2020-10-30 12:36:48 +01:00
srgooglo
736f933ce5 add persistent mode to use & other changes 2020-10-29 20:29:09 +01:00
srgooglo
878143c515 fixed missing user_id & fix fadeclock 2020-10-29 15:39:31 +01:00
srgooglo
b3c48b9fdd removed console.logs 2020-10-29 14:57:17 +01:00
srgooglo
636eaaaf83 added splash loader 2020-10-29 14:56:06 +01:00
srgooglo
03709378f3 fixed socket token refresh 2020-10-29 13:33:01 +01:00
srgooglo
01bbafcd4a fixed broked refreshToken 2020-10-29 10:30:54 +01:00
srgooglo
7957e6bea4 update version 2020-10-29 09:14:46 +01:00
srgooglo
f41f565e93 updated login method 2020-10-28 19:17:28 +01:00
srgooglo
6fda7a5ef3 format code 2020-10-28 18:22:40 +01:00
srgooglo
340b673080 format & fix bad ts typing 2020-10-28 18:21:56 +01:00
srgooglo
1c90adcd3f added __proto__filterSchematizedArray to core & refactor components 2020-10-28 18:20:20 +01:00
srgooglo
0fbba17316 splitted code from function & added decyle to core 2020-10-28 18:15:21 +01:00
srgooglo
6eb8db9f20 removed v3api debbuger due to poor code quality 2020-10-28 18:08:42 +01:00
srgooglo
ca9a56c330 refactor render, fixed complexity, exceding lines 2020-10-28 18:05:39 +01:00
srgooglo
ebba5828e8 refactors for #27 & removed unused code 2020-10-28 17:56:45 +01:00
srgooglo
0c9c5007c8 Merge branch 'master' of https://github.com/srgooglo/comty 2020-10-28 14:44:22 +01:00
srgooglo
c15e92e386 changed auth method to ws 2020-10-28 14:44:15 +01:00
srgooglo
bef884fe2d
Create callback_codes.md 2020-10-28 09:19:58 +01:00
srgooglo
04a591d278
Merge pull request #23 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-10-27 18:25:41 +01:00
srgooglo
ac69e2fec7 changed ?? operators values on socket debugger 2020-10-27 18:25:13 +01:00
srgooglo
d328d612b2 update explore request method 2020-10-27 18:24:43 +01:00
codefactor-io
82d426697a [CodeFactor] Apply fixes to commit 5564e8e 2020-10-27 17:23:31 +00:00
srgooglo
9ea418b99d Merge branch 'master' of https://github.com/srgooglo/comty 2020-10-27 18:23:05 +01:00
srgooglo
5564e8e323 added locked state & use method to socket 2020-10-27 18:22:55 +01:00
srgooglo
6af0d092d9
Update README.md
added maintainability badge
2020-10-27 11:17:56 +01:00
srgooglo
79ecb69f16
Update README.md 2020-10-25 20:46:19 +01:00
srgooglo
273bd10b8d
Merge pull request #22 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-10-23 20:04:38 +02:00
codefactor-io
21c120c95f [CodeFactor] Apply fixes to commit 9cf915d 2020-10-23 18:01:42 +00:00
srgooglo
9cf915dfaa added headerNode & headerReset 2020-10-23 18:01:20 +02:00
srgooglo
ca1bbf0dd2 added temporaly socket debugger 2020-10-23 16:13:52 +02:00
srgooglo
f7535ae7c0 manage sockets by nodes, supporting multiplexing 2020-10-23 16:13:29 +02:00
srgooglo
c1b764be06 Changed nested content generation length minimun depth 2020-10-23 15:17:56 +02:00
srgooglo
20faeea409 multiple changes 2020-10-22 18:50:58 +02:00
srgooglo
030027b2a1 fixed bas typing updateListener 2020-10-22 17:20:59 +02:00
srgooglo
a12a1372ff updated socket init methods 2020-10-22 17:12:35 +02:00
srgooglo
bdc19546fe changed socketinit method 2020-10-21 17:53:13 +02:00
srgooglo
cc184378af added sockets listeners handlers & update debuggers 2020-10-21 17:35:31 +02:00
srgooglo
d9315c6d93 added selectedKeys storage 2020-10-21 13:02:48 +02:00
srgooglo
d43d23fbc1 Merge branch 'master' of https://github.com/srgooglo/comty 2020-10-21 12:31:42 +02:00
srgooglo
f601a01672 update redux debugger from support decyle/serialize 2020-10-21 12:31:29 +02:00
srgooglo
6f1e3d001f
Merge pull request #21 from srgooglo/snyk-fix-4eac0b1560decc7b0968ddbe4c96b988
[Snyk] Security upgrade react-scripts from 3.4.3 to 3.4.4
2020-10-21 08:26:21 +02:00
snyk-bot
8150528ced
fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-OBJECTPATH-1017036
2020-10-21 00:45:29 +00:00
srgooglo
fb03a2cef1
Merge pull request #20 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-10-20 21:51:10 +02:00
codefactor-io
18262638fc [CodeFactor] Apply fixes to commit 674d270 2020-10-20 19:49:44 +00:00
srgooglo
674d270fa6 socket init on query model 2020-10-20 21:49:25 +02:00
srgooglo
052d3b58c7 remove unused modules 2020-10-20 21:48:53 +02:00
srgooglo
aa424fb420 updated redux debugger, added ParamsList to component 2020-10-20 21:48:18 +02:00
srgooglo
06455f6412 other changes 2020-10-20 16:31:38 +02:00
srgooglo
b39b0cea56 changed Sider render method 2020-10-20 16:31:06 +02:00
srgooglo
cc9396b5b6 removed RenderComponent object 2020-10-20 16:30:34 +02:00
srgooglo
b6295785af updated some dependencies 2020-10-20 14:14:38 +02:00
srgooglo
7b1962d778 updated ie target version 2020-10-20 14:14:14 +02:00
srgooglo
63bb85cf5c added Provider to FloatComponent 2020-10-20 14:13:53 +02:00
srgooglo
a7a9b887e1 add socket model to app models 2020-10-19 16:47:17 +02:00
srgooglo
f327225db2 changed method of import for verbosity 2020-10-19 16:46:57 +02:00
srgooglo
c06ffe5e0d added FloatComponent prototype to components 2020-10-19 16:45:42 +02:00
srgooglo
1c1acb9a2b updated versions 2020-10-19 16:44:27 +02:00
srgooglo
45e4d2cd96 changed debugger directories 2020-10-19 16:43:14 +02:00
srgooglo
2aa02a5009 refactorized Loader 2020-10-16 13:39:12 +02:00
srgooglo
7b0bcf7bf6 updated gitignore, including package-lock 2020-10-16 13:24:27 +02:00
srgooglo
a2993d4eab fix #18, refactor objectToArray to legacy util 2020-10-16 13:14:24 +02:00
srgooglo
572eb5f5bc change app_info object from core to clientInfo, added getBuild function and tidy up, remplazed local utils methods to nodecore-utils lib 2020-10-16 13:12:53 +02:00
srgooglo
4e76cb0db6 updated get_user_data endpoints 2020-10-14 22:32:08 +02:00
srgooglo
bb5daa7c84
Merge pull request #19 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-10-12 20:50:48 +02:00
srgooglo
ecbd3d50c9 fixed auto close when no valid events 2020-10-12 20:38:06 +02:00
srgooglo
d484cefa92 refactor and clean some 2020-10-12 19:14:50 +02:00
codefactor-io
6f4a5943a8 [CodeFactor] Apply fixes to commit 4720ed2 2020-10-12 17:14:49 +00:00
srgooglo
4720ed240a update model for contextMenu and render method 2020-10-12 19:14:31 +02:00
srgooglo
c8d7809106 changed to global api requireQuery 2020-10-12 16:00:49 +02:00
srgooglo
c646d18c1a refactorized verbosity with the correct array schema 2020-10-12 15:59:37 +02:00
srgooglo
1838682c2a Merge branch 'master' of https://github.com/srgooglo/comty 2020-10-12 12:07:18 +02:00
srgooglo
c9aa8ad4f8 fixed duplicated queryIndexer & fixed bad schema for ipcInvoke in windowNavbar 2020-10-12 12:07:09 +02:00
srgooglo
eb53751f4f
Update trello_issue.yml 2020-10-12 11:50:41 +02:00
srgooglo
f9d83647cb
Update trello_issue.yml 2020-10-12 11:45:06 +02:00
srgooglo
d3576e23b9
Create trello_issue.yml 2020-10-12 11:41:53 +02:00
srgooglo
bbfe6f31cf add feature issue template 2020-10-12 11:20:54 +02:00
srgooglo
8badcbaa2e added Issues_Templates & added electron script 2020-10-12 11:12:11 +02:00
srgooglo
d094673bde updated api debugger for invalid import cause render crash and for stringify results 2020-10-12 09:02:10 +02:00
srgooglo
e5a5243dbe updated versions and added jest for tests 2020-10-12 08:57:34 +02:00
srgooglo
dbb752651b update nodecore-api-lib 2020-10-09 19:47:57 +02:00
srgooglo
821d183ef4 refactor DynamicSDCP lib for core 2020-10-09 19:16:05 +02:00
srgooglo
dfdbe2fc73 remove PageTransition and HeaderIconRender deprecated components 2020-10-09 19:14:48 +02:00
srgooglo
bd2b1fdbb4
Merge pull request #14 from srgooglo/apiv3-model-nodecore
Apiv3 model nodecore
2020-10-09 19:13:19 +02:00
codefactor-io
82404cc32a [CodeFactor] Apply fixes 2020-10-09 17:10:08 +00:00
srgooglo
dae830a7ce change api_v3 model with new @nodecore-api-lib 2020-10-09 19:07:14 +02:00
srgooglo
90ddeca69b this missed too 2020-10-09 17:45:35 +02:00
srgooglo
789c03cecf
Merge pull request #13 from srgooglo/added-user-model
Add user model to app models
2020-10-09 16:08:44 +02:00
codefactor-io
f5722f044d [CodeFactor] Apply fixes 2020-10-09 14:05:41 +00:00
srgooglo
91e7a162af Add user model to app models, user layout updated for gathering data from model instead local functions 2020-10-09 16:03:10 +02:00
srgooglo
5841296cd7 move verbosity call outside condition for avoid misbehaviors 2020-10-09 16:01:37 +02:00
srgooglo
5c6836245b wooups i forgot this again -.- 2020-10-09 15:28:50 +02:00
srgooglo
b59aef5d12 Merge branch 'master' of https://github.com/srgooglo/comty 2020-10-09 15:27:28 +02:00
srgooglo
c141c3d689 updated ErrorHandler with new flags, component Invalid now supports typeByCode, also refactor dependents components and fix some methods, like returning and CustomInvalid when nothing to return render 2020-10-09 15:27:16 +02:00
srgooglo
d954b78f79
Update changelog.yml 2020-10-09 13:28:18 +02:00
srgooglo
22a888d51f
Create changelog.yml 2020-10-09 13:27:17 +02:00
srgooglo
477fe6608b
Merge pull request #11 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-10-09 13:16:41 +02:00
srgooglo
ca669bf042
Merge pull request #12 from srgooglo/improve-error-handler
Better error handler
2020-10-09 13:10:29 +02:00
srgooglo
5e372157a4 better error handler 2020-10-09 13:01:13 +02:00
srgooglo
10d2138027 removed invalid props 2020-10-09 12:59:43 +02:00
codefactor-io
ad5e38ead1 [CodeFactor] Apply fixes to commit 95a74eb 2020-10-08 18:52:43 +00:00
srgooglo
95a74eb3a2 Add user profile layout 2020-10-08 20:52:28 +02:00
srgooglo
b500171b01 Added basicData endpoint & refactorize profileData 2020-10-08 20:51:42 +02:00
srgooglo
005b6a41c9 woops i miss that one 2020-10-08 20:03:11 +02:00
srgooglo
0cae935aab refactor core "helpers" to "models" 2020-10-08 20:02:07 +02:00
srgooglo
296f12c67e Merge branch 'master' of https://github.com/srgooglo/comty 2020-10-08 18:45:08 +02:00
srgooglo
d27d7e70a4 wtf 2020-10-08 18:43:34 +02:00
srgooglo
b58128519d
Update codeql-analysis.yml 2020-10-08 16:40:21 +02:00
srgooglo
95a8010042
Update codeql-analysis.yml 2020-10-08 16:39:26 +02:00
srgooglo
2e6db0c645
Update README.md 2020-10-08 16:27:40 +02:00
srgooglo
b7f6223ded added to core queryIndexer, used for index an input rules and returns parsed string with callback 2020-10-08 16:17:05 +02:00
srgooglo
4ce2564a50 woops 2020-10-08 15:15:15 +02:00
srgooglo
5012174297 Updated InvalidComponents with Custom, InvalidIndex classes 2020-10-08 15:14:58 +02:00
srgooglo
794e11c9eb remove some trash 2020-10-08 15:12:38 +02:00
srgooglo
dfeb56c8ea refactorize ListedMenu to .tsx and update methods on dependents components 2020-10-08 15:11:54 +02:00
srgooglo
69df0eb9d6 Added ListedMenu component for menu menu generation with applied schema(41) 2020-10-08 14:47:44 +02:00
srgooglo
d34614d178 Merge branch 'master' of https://github.com/srgooglo/comty 2020-10-08 13:57:23 +02:00
srgooglo
a918c5821e switch to window.requireQuery method, also include URLSearchParams support for key indexing 2020-10-08 13:57:13 +02:00
srgooglo
a8fe322432
Update README.md 2020-10-07 18:36:00 +02:00
srgooglo
0d2e9eb907 added events handlers to basics events (" disconnect, connect... etc ") 2020-10-07 18:19:26 +02:00
srgooglo
b94eda6b7e added to lib global (wip) 2020-10-07 18:17:44 +02:00
srgooglo
5f6dee7988 added window.openLink() for support electron desktop to open new window with the default os browser 2020-10-07 18:17:15 +02:00
srgooglo
69bf50712d added new schemas for functional modules (schema-21) 2020-10-07 16:36:20 +02:00
srgooglo
007ebcc0ed
Merge pull request #9 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-10-07 15:14:44 +02:00
codefactor-io
a403a7008c [CodeFactor] Apply fixes to commit f493c83 2020-10-07 13:13:23 +00:00
srgooglo
06cedf9887 Merge branch 'master' of https://github.com/srgooglo/Comty-Development 2020-10-07 15:12:51 +02:00
srgooglo
f493c830b5 refactor & other changes 2020-10-07 15:11:43 +02:00
srgooglo
0c864e6246 updated verbosity module with fixed method 2020-10-07 15:06:12 +02:00
srgooglo
f5898f6f92
Merge pull request #7 from srgooglo/master-cf-autofix
Apply fixes from CodeFactor
2020-10-07 11:18:57 +02:00
srgooglo
f349519678 [update] improved render methods for childrens with selectKey 2020-10-07 11:16:26 +02:00
srgooglo
e350d16b81 [added] GetPropertyValue => to core functions 2020-10-07 11:13:19 +02:00
srgooglo
2f84bedfd3 created new snippets for vscode, with create componets schema & switched (RPC) rich presence discord to "enabled" 2020-10-07 11:12:28 +02:00
srgooglo
991511f58d Merge branch 'master' of https://github.com/srgooglo/Comty-Development 2020-10-07 11:07:49 +02:00
srgooglo
95b80bd35c [update] verbosity removed legacy & refactor components, also tweaked string concat for support spread multiple data inputs 2020-10-07 11:07:36 +02:00
srgooglo
e2b89ce113
Merge pull request #8 from srgooglo/snyk-upgrade-448a54d0755f796663ec3bbcbdd9047c
[Snyk] Upgrade axios from 0.19.2 to 0.20.0
2020-10-07 10:22:12 +02:00
snyk-bot
268e1b75bc
fix: upgrade axios from 0.19.2 to 0.20.0
Snyk has created this PR to upgrade axios from 0.19.2 to 0.20.0.

See this package in npm:
https://www.npmjs.com/package/axios

See this project in Snyk:
https://app.snyk.io/org/srgooglo/project/32d7496a-6bfa-4eab-978c-5bc618fd2e41?utm_source=github&utm_medium=upgrade-pr
2020-10-07 00:44:05 +00:00
srgooglo
956d36edaa updated node-sass 2020-10-06 20:33:18 +02:00
srgooglo
d229001944 Merge branch 'master' of https://github.com/srgooglo/Comty-Development 2020-10-06 20:30:05 +02:00
srgooglo
471deba2d6
Create codeql-analysis.yml 2020-10-06 20:23:19 +02:00
codefactor-io
9b066ad9ee [CodeFactor] Apply fixes to commit 9433138 2020-10-06 18:07:10 +00:00
567 changed files with 35378 additions and 11523 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
.corenode Executable file
View File

@ -0,0 +1,3 @@
{
"version": "0.20.2"
}

View File

@ -1,16 +0,0 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

2
.env
View File

@ -1,2 +0,0 @@
UMI_UI=false
NODE_ENV=development

11
.github/ISSUE_TEMPLATE/01-bug.md vendored Executable file
View File

@ -0,0 +1,11 @@
---
name: Bug Report
about: Issues caused by bugs
---
**Describe the bug:**
**Screenshots or/and Reproductions(if applicable):**
**App/Core/Dependency version info:**
**Logs or logDump(if applicable):**

10
.github/ISSUE_TEMPLATE/02-feature.md vendored Executable file
View File

@ -0,0 +1,10 @@
---
name: Feature Request
about: Request an feature
---
**Describe the Idea/Feature:**
**Any concept or sketch?:**
**How it could be applied or in what way?:**

13
.github/workflows/changelog.yml vendored Executable file
View File

@ -0,0 +1,13 @@
name: Create Changelogs
on:
push:
branches: [ master ]
jobs:
create_changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Changelogs
uses: heineiuo/create-changelogs@v0.2.8

68
.github/workflows/codeql-analysis.yml vendored Executable file
View File

@ -0,0 +1,68 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
pull_request:
branches: [master]
schedule:
- cron: '0 0 * * *'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

18
.github/workflows/trello_issue.yml vendored Executable file
View File

@ -0,0 +1,18 @@
name: Trello Issue List
on:
issues:
types: [opened]
env:
TRELLO_KEY: ${{ secrets.TRELLO_KEY }}
TRELLO_TOKEN: ${{ secrets.TRELLO_TOKEN }}
jobs:
issue_send:
name: Send Issue to Trello
runs-on: ubuntu-latest
steps:
- name: Runs trello manage
uses: sisodiya2421/trello-manage@master
with:
repo-name: Comty Development
trello-username: ${{ secrets.TRELLO_USERNAME }}

17
.github/workflows/validate.yml vendored Executable file
View File

@ -0,0 +1,17 @@
name: Validate code
on:
push:
branches:
- '**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12
- run: npm ci
- run: npm test

47
.gitignore vendored Executable file → Normal file
View File

@ -1,17 +1,36 @@
# dependencies
/node_modules
/npm-debug.log*
/yarn-error.log
/yarn.lock
/package-lock.json
# Secrets
/**/**/.env
/**/**/origin.server
/**/**/server.manifest
/**/**/server.registry
# production
/dist
/out
# Trash
/**/**/*.log
/**/**/dumps.log
/**/**/.crash.log
/**/**/.tmp
/**/**/.cache
/**/**/cache
/**/**/out
/**/**/.out
/**/**/dist
/**/**/node_modules
/**/**/corenode_modules
/**/**/.DS_Store
/**/**/package-lock.json
/**/**/yarn.lock
/**/**/.evite
/**/**/uploads
/**/**/d_data
/**/**/*.tar
/**/**/*.7z
/**/**/*.zip
# umi
/src/.umi
/src/.umi-production
/src/.umi-test
/.env.local
# Logs
/**/**/npm-debug.log*
/**/**/yarn-error.log
/**/**/dumps.log
/**/**/corenode.log
# Temporal configurations
/**/**/.aliaser

View File

@ -1,8 +0,0 @@
**/*.md
**/*.svg
**/*.ejs
**/*.html
package.json
.umi
.umi-production
.umi-test

View File

@ -1,11 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

113
.umirc.js
View File

@ -1,113 +0,0 @@
import { defineConfig } from 'umi';
const { resolve, join } = require('path');
export default defineConfig({
hash: false,
dynamicImport: {
loading: 'components/Loader/Loader.js',
},
// dynamicImport: false,
// history: { type: "hash" },
targets: { ie: 9 },
dva: { immer: true },
ignoreMomentLocale: true,
mountElementId: "root",
nodeModulesTransform: {
type: 'none',
},
// ssr: {
// devServerRender: true,
// },
alias: {
antd: resolve(__dirname, './node_modules/antd'),
api: resolve(__dirname, './node_modules/@ragestudio/ycorejs-lib'), // ./api
plugins: resolve(__dirname, './plugins'),
globals: resolve(__dirname, './globals'),
core: resolve(__dirname, './src/core'),
theme: resolve(__dirname, './src/theme'),
config: resolve(__dirname, './config'),
components: resolve(__dirname, './src/components'),
models: resolve(__dirname, './src/models'),
node_modules: resolve(__dirname, './node_modules')
},
extraBabelPlugins: [
[
'import',
{
libraryName: 'lodash',
libraryDirectory: '',
camel2DashComponentName: false,
},
'lodash',
],
],
// externals(context, request, callback) {
// const isDev = process.env.NODE_ENV === 'development';
// let isExternal = false;
// const load = [
// 'electron',
// 'fs',
// 'path',
// 'os',
// 'child_process'
// ];
// if (load.includes(request)) {
// isExternal = `require("${request}")`;
// }
// const appDeps = Object.keys(require('./package.json').dependencies);
// if (appDeps.includes(request)) {
// const orininalPath = slash(join(__dirname, 'node_modules', request));
// const requireAbsolute = `require('${orininalPath}')`;
// console.log(isDev)
// isExternal = isDev ? requireAbsolute : `require('${request}')`;
// }
// callback(null, isExternal);
// },
// plugins: [themePlugin],
// chainWebpack: function(config, { webpack }) {
// config.module
// .rule('js-in-node_modules')
// .exclude.add(/node_modules/)
// .end()
// config.module
// .rule('ts-in-node_modules')
// .exclude.add(/node_modules/)
// .end()
// config.merge({
// optimization: {
// minimize: true,
// splitChunks: {
// chunks: 'all',
// minSize: 30000,
// minChunks: 3,
// automaticNameDelimiter: '.',
// cacheGroups: {
// react: {
// name: 'react',
// priority: 20,
// test: /[\\/]node_modules[\\/](react|react-dom|react-dom-router)[\\/]/,
// },
// antd: {
// name: 'antd',
// priority: 20,
// test: /[\\/]node_modules[\\/](antd|@ant-design\/icons)[\\/]/,
// },
// async: {
// chunks: 'async',
// minChunks: 2,
// name: 'async',
// maxInitialRequests: 1,
// minSize: 0,
// priority: 5,
// reuseExistingChunk: true,
// },
// },
// },
// },
// })
// },
});

23
.vscode/components_schemas.code-snippets vendored Executable file
View File

@ -0,0 +1,23 @@
{
"[schema-31] New Connected Component with decorators": {
"scope": "javascript",
"prefix": "newConnectedComponent",
"description": "Create a new react component with default schema for ycore-schema31",
"body": [
"import React from 'react'",
"import * as antd from 'antd'",
"import { connect } from 'umi'",
"",
"@connect(({ app }) => ({ app }))",
"export default class ${1:NewComponent} extends React.Component{",
"\trender(){",
"\t\treturn(",
"\t\t\t<div>",
"\t\t\t\t",
"\t\t\t</div>",
"\t\t)",
"\t}",
"}",
]
}
}

30
.vscode/functions_schemas.code-snippets vendored Executable file
View File

@ -0,0 +1,30 @@
{
"[schema-21] New api v3 based functions": {
"scope": "javascript",
"prefix": "newApiFunctional21",
"description": "Create a new functional core module with default schema for ycore-schema21",
"body": [
"import verbosity from 'core/libs/verbosity'",
"import handle from 'core/libs/errorhandler'",
"import { notify } from 'core/libs/ui'",
"import settings from 'core/libs/settings'",
"import endpoints from 'config/endpoints'",
"import { v3_model } from 'core/libs'",
"",
"export function ${1:NewFunction}(payload, callback){",
"\tif (!payload) return false",
"\tconst { data } = payload",
"\t\t",
"\treturn callback(false, null)",
"}",
"",
"export const ${2:ObjectFunction} = {",
"\tsomething: (payload, callback) => {",
"\t\treturn callback(false, null)",
"\t}",
"}",
"",
"export default ${1:NewFunction}",
]
}
}

33
.vscode/launch.json vendored Normal file → Executable file
View File

@ -1,5 +1,22 @@
{
"configurations": [
{
"name": "[Node] Run file",
"internalConsoleOptions": "openOnSessionStart",
"program": "${file}",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "Attach to Chrome",
"port": 8000,
"request": "attach",
"type": "pwa-chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
@ -22,10 +39,22 @@
{
"name": "Launch via NPM",
"request": "launch",
"runtimeArgs": ["run-script", "debug"],
"runtimeArgs": [
"run-script",
"debug"
],
"runtimeExecutable": "npm",
"skipFiles": ["<node_internals>/**"],
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "Launch WEB APP",
"type": "pwa-chrome",
"name": "http://192.168.0.2:8000",
"request": "launch",
"url": "http://192.168.0.2:8000"
}
]
}

3
.vscode/settings.json vendored Executable file
View File

@ -0,0 +1,3 @@
{
"discord.enabled": false
}

45
README.md Normal file → Executable file
View File

@ -1,5 +1,44 @@
# Comty-Development
[![CodeFactor](https://www.codefactor.io/repository/github/srgooglo/comty-development/badge?s=d26d9c4b4f23bdf7fdc8209a8088c58841deffc3)](https://www.codefactor.io/repository/github/srgooglo/comty-development)
# Comty - An prototype of an social network
![CodeQL](https://github.com/srgooglo/comty/workflows/CodeQL/badge.svg?branch=master)
[![CodeFactor](https://www.codefactor.io/repository/github/ragestudio/comty/badge)](https://www.codefactor.io/repository/github/ragestudio/comty)[![Maintainability](https://api.codeclimate.com/v1/badges/f89a278695d0a1301fe5/maintainability)](https://codeclimate.com/github/srgooglo/comty/maintainability)
[![Code Alerts](https://img.shields.io/lgtm/alerts/g/srgooglo/comty.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/srgooglo/comty/alerts/)
[![Grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/srgooglo/comty.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/srgooglo/comty/context:javascript)
[![Discord](https://img.shields.io/badge/dynamic/json?color=blue&label=Discord&query=name&url=https%3A%2F%2Fdiscord.com%2Fapi%2Fguilds%2F769176303978938389%2Fwidget.json)](https://discord.gg/NmnJexe)
[Join our Trello](https://trello.com/invite/b/UbwvlG1I/2bc02725b9b210d2e9e9a82c5040b895/comty-development)
A prototype of a platform inside of application focused on microservices, extensible and modular, in which the application adapts to your needs, where you will find tools and services allowing you to facilitate communication with other people in a social way.
## Getting Started
>Its needed to have installed NodeJS ( v13^ ), Git, and a dependency manager (npm, yarn...)
- Getting the repository
```shell
git clone https://github.com/ragestudio/comty && cd comty
yarn
```
- Starting web development server
```shell
cd packages/app
yarn start
```
- Starting api development server
```shell
cd packages/server
yarn start
```
## Status
This project is in a **early** design phase, there are still many phases to go through.
At the moment the ***development is slow but without pause***, because being a very complicated and important phase, we are **prioritizing quality** so that the project base can be preserved for a **long time**.
Now **we are looking** for a team too.
Anyway, anyone interested, we are **looking for you**.
***Soon*** new updates adding much more information about this project.
***Any contribution is very grateful, we need you more than ever!***
## Tech
![tech](https://dl.ragestudio.net/persistent/CbEWXaE3cB35Rm2TFhY4Hnezr2P7qbtGDG86MPWUa6.png)

View File

@ -1,9 +0,0 @@
module.exports = {
unsplash_key: 'slrHmuo9FEJajV4xvWl38TUhbib6BhhGI4VIZ1-cqnw',
unsplash_secret: 'dh3UlgLTdunO7a_l_iKjotXbz0xB7w5EuDIBU8Pa8pA',
g_recaptcha_key: '6Lc55uUUAAAAAEIACMVf3BUzAJSNCmI3RrjEirZ6',
g_recaptcha_secret: '6Lc55uUUAAAAAOP4OgUa5DpqJC-70t53AmW0lyYf',
// Global Server Key (Requiered for RS-YIBTP), Not autogenerated, must be included on. (Recommended not modify this constant)
server_key:
'f706b0a535b6c2d36545c4137a0a3a26853ea8b5-1223c9ba7923152cae28e5a2e7501b2b-50600768',
};

View File

@ -1,10 +0,0 @@
export default {
auth_server: 'POST /auth_server',
auth: 'POST /auth',
sessions: 'POST /sessions',
posts: 'POST /posts',
post_actions: 'POST /post-actions',
get_data: 'POST /get-user-data',
profileData: 'POST /early_user'
};

View File

@ -1,78 +0,0 @@
module.exports = {
app_config: {
id: "comty",
siteName: 'Comty',
copyright: 'RageStudio©',
MainPath: '/',
LogoPath: '/logo.svg',
FullLogoPath: '/full_logo.svg',
DarkFullLogoPath: '/dark_full_logo.svg',
DarkLogoPath: '/dark_logo.svg',
api_interface: 'https://api.ragestudio.net',
api_prefix: 'ycorejs_apiv3',
app_settings_storage: 'app_settings',
endpoint_global: 'https://comty.pw',
proxy_local: 'http://localhost:8000',
session_token_storage: 'cid',
session_data_storage: 'data',
appTheme_container: 'app_theme',
appTheme_desiredContrast: 7,
// Contrast level AA = 4.5, Level AAA = 7
// Reference: https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0&showtechniques=143#qr-visual-audio-contrast-contrast
},
i18n: {
languages: [
{
key: 'en',
title: 'English',
},
],
defaultLanguage: 'en',
},
layouts: [
{
name: 'primary',
include: [/\/main/, /\/settings/, /\/saves/, /\/pro/, /\/chats/, /\//],
exclude: [/\/publics/, /\/login/ ],
},
{
name: 'public',
include: [/.*/]
}
],
// Default Behaviors
defaults: {
app_model: "app",
verbosity: false,
session_noexpire: false,
search_ontype: false,
overlay_loosefocus: true,
render_pagetransition_preset: 'moveToRightScaleUp',
post_catchlimit: '20',
post_hidebar: true,
feed_autorefresh: false,
keybinds: {
nextElement: "J",
prevElement: "U",
createNew: "N"
}
},
stricts: {
post_maxlenght: '512',
// In KB
api_maxpayload: '101376',
api_maxovertick: 10,
}
};

1
example.env Normal file
View File

@ -0,0 +1 @@
GITHUB_TOKEN=""

View File

@ -1,10 +0,0 @@
[
{"id": "alpha_test","title": "Alpha Tester","color": "green","icon": "Bug","tip": "Oh yeah!"},
{"id": "nsfw_flag","title": "NSFW","color": "volcano","require": "nsfw_flag","tip": "NSFW"},
{"id":"pro","title":"CPRO™","color":"purple","require":"pro","icon":"RocketOutlined","tip":"CPRO™"},
{"id":"dev","title":"DEVELOPER","color":"default","require":"dev","icon":"GitBranch","tip":"DEVELOPER"},
{"id":"professional_retarder","title":"Professional Retarder","color":"gold","require":"","icon":"SmileOutlined","tip":"hump...."},
{"id":"el_walter_pro","title":"Pro Chikito","color":"#a0d911","require":"","icon":"🐱‍🐉🧜‍♀️","tip":"Chikito"},
{"id":"patreon","title":"Patreon Member","color":"","require":"","icon":"Patreon","tip":"GoodBoy!"}
]

View File

@ -1,14 +0,0 @@
import * as Icons from 'components/Icons'
export default [
{
key: "inspect_element",
title: "Inspect",
icon: <Icons.Command />,
params: {
onClick: (e) => {
window.inspectElement(e)
}
}
}
]

View File

@ -1,6 +0,0 @@
module.exports = {
gitlab: "https://gitlab.com/rstudio-development/comty-development",
github:"https://github.com/srgooglo/Comty-Development",
trellojoin: "https://trello.com/invite/b/UbwvlG1I/2bc02725b9b210d2e9e9a82c5040b895/comty-development",
patreon: "https://www.patreon.com/rstudio",
}

View File

@ -1,6 +0,0 @@
{
"gitlab": "https://gitlab.com/rstudio-development/comty-development",
"github":"https://github.com/srgooglo/Comty-Development",
"trellojoin": "https://trello.com/invite/b/UbwvlG1I/2bc02725b9b210d2e9e9a82c5040b895/comty-development",
"patreon": "https://www.patreon.com/rstudio"
}

View File

@ -1,4 +0,0 @@
[
{"key":"pro_boost","icon":"RocketOutlined","type":"switch","title":"CPRO™ Boost","description":"","require":"pro","value":false},
{"key":"allow_comments","icon":"CommentOutlined","type":"switch","title":"Allow Comments","description":"","require":"","value":true}
]

View File

@ -1,32 +0,0 @@
import * as Icons from 'components/Icons'
export default [
{
id: 'session_noexpire',
icon: <Icons.Watch />,
type: 'switch',
title: 'No expire session',
description: 'Force the app to not expire any session... [Developer]',
},
{
id: 'search_ontype',
icon: <Icons.CornerDownRight />,
type: 'switch',
title: 'Detect input on search bar',
description: 'Force the app to automaticly search when a type input is detected... [Developer]',
},
{
id: 'post_hidebar',
icon: <Icons.Menu />,
type: 'switch',
title: 'Auto hide postbar',
description: 'Force the app to hide the post actions (likes, comments ...etc) automaticly... [Developer]',
},
{
id: 'verbosity',
icon: <Icons.Terminal />,
type: 'switch',
title: 'Enable core verbosity',
description: 'Show all console logs... [Developer]',
}
]

View File

@ -1,13 +0,0 @@
[
{"id":"session_noexpire","icon":"Watch","type":"switch","title":"No expire session","description":"Force the app to not expire any session... [Developer]"},
{"id":"search_ontype","icon":"CornerDownRight","type":"switch","title":"Detect input on search bar","description":"Force the app to automaticly search when a type input is detected... [Developer]"},
{"id":"post_hidebar","icon":"Menu","type":"switch","title":"Auto hide postbar","description":"Force the app to hide the post actions (likes, comments ...etc) automaticly... [Developer]"},
{"id":"verbosity","icon":"Terminal","type":"switch","title":"Enable core verbosity","description":"Show all console logs... [Developer]"},
{
"id":"seponetriste",
"icon":"Terminal",
"type":"switch",
"title":"Mayonesa",
"description":"liquiedA?"
}
]

View File

@ -1,110 +0,0 @@
import * as Icons from 'components/Icons'
/**
* Sidebar Menu scheme
*
* @param id {string} Used for ( key_filter ) & ( router.push(id) ) [required]
* @param icon {any} Render an "icon" component on the list | Default => null
* @param title {string} Render an string on the list | Default => null
* @param attributes.path {string} Override path for router.push(id)
* @param attributes.position {string} Sets render position (Only for desktop mode) | Default => "top"
* @param attributes.require {string} Sets an render condition | Default => null
* @param attributes.desktop {boolean} Activate render for desktop clients | Default => true
* @param attributes.mobile {boolean} Activate render for mobile clients | Default => true
*/
export default [
{
id: 'main',
icon: <Icons.Home />,
title: 'Main',
attributes: {
require: 'login',
desktop: false,
}
},
{
id: 'explore',
title: 'Explore',
icon: <Icons.Compass />,
},
{
id: 'saves',
title: 'Saves',
icon: <Icons.Bookmark />,
attributes: {
require: 'login',
mobile: false
}
},
{
id: 'messages',
title: 'Messages',
icon: <Icons.MessageSquare />,
attributes: {
require: 'login'
}
},
{
id: 'rooms',
title: 'Rooms',
icon: <Icons.Box />,
attributes: {
require: 'login'
}
},
{
id: 'workshop',
title: 'Workshop',
icon: <Icons.Package />,
attributes: {
require: 'login'
}
},
{
id: 'streams',
title: 'Streams',
icon: <Icons.Tv />,
attributes: {
require: 'login'
}
},
{
id: 'debug',
title: 'Debug',
icon: <Icons.Tool />,
attributes: {
position: "bottom",
require: "dev"
}
},
{
id: 'settings',
title: 'Settings',
icon: <Icons.Settings />,
attributes: {
position: "bottom"
}
},
{
id: 'logout',
title: 'Logout',
icon: <Icons.LogOut style={{ color: '#ef233c', marginRight: '10px' }} />,
attributes: {
position: "bottom",
require: 'login'
}
},
{
id: 'login',
title: 'Signin',
icon: <Icons.LogIn style={{ color: '#3a86ff', marginRight: '10px' }} />,
attributes: {
position: "bottom",
require: "guest"
}
}
]

View File

@ -1,100 +0,0 @@
[
{
"id":"main",
"icon":"Home",
"title":"Main",
"attributes": {
"require":"login",
"desktop":false
}
},
{
"id":"explore",
"title":"Explore",
"icon":"Compass"
},
{
"id":"saves",
"title":"Saves",
"icon":"Bookmark",
"attributes": {
"require":"login",
"mobile":false
}
},
{
"id":"messages",
"title":"Messages",
"icon":"MessageSquare",
"attributes": {
"require":"login"
}
},
{
"id":"rooms",
"title":"Rooms",
"icon":"Box",
"attributes": {
"require":"login"
}
},
{
"id":"workshop",
"title":"Workshop",
"icon":"Package",
"attributes": {
"require":"login"
}
},
{
"id":"streams",
"title":"Streams",
"icon":"Tv",
"attributes": {
"require":"login"
}
},
{
"id":"debug",
"title":"Debug",
"icon":"Tool",
"attributes": {
"position":"bottom",
"require":"dev"
}
},
{
"id":"settings",
"title":"Settings",
"icon":"Settings",
"attributes": {
"position":"bottom"
}
},
{
"id":"logout",
"title":"Logout",
"icon":"LogOut",
"iconStyle": {
"color": 'red',
"marginRight": '10px'
},
"attributes": {
"position":"bottom",
"require":"login"
}
},
{
"id":"login",
"title":"Signin",
"icon":"Login",
"iconStyle": {
"color": "blue",
"marginRight": "10px"
},
"attributes": {
"position":"bottom",
"require":"guest"
}
}
]

View File

@ -1,40 +0,0 @@
import * as Icons from 'components/Icons'
export default [
{
id: 'backgroundImage',
icon: <Icons.Image />,
title: 'Background',
description: 'Change the background of the app',
},
{
id: 'overlay',
icon: <Icons.Sidebar />,
title: 'Overlay',
description: 'Description blah blah',
},
{
id: 'color',
icon: <Icons.Droplet />,
title: 'Colors',
description: 'Texts, Buttons, Sliders ...etc',
},
{
id: 'text',
icon: <Icons.FontColorsOutlined style={{ marginRight: '10px' }} />,
title: 'Text',
description: 'Sizes, Fonts',
},
{
id: 'sounds',
icon: <Icons.Volume2 />,
title: 'Sounds',
description: 'BlipBlopBLup',
},
{
id: 'darkmode',
icon: <Icons.Moon />,
title: 'Dark Mode',
description: 'Yeaah, no more daying',
}
]

View File

@ -1,8 +0,0 @@
[
{"id":"backgroundImage","icon":"Image","title":"Background","description":"Change the background of the app"},
{"id":"overlay","icon":"Sidebar","title":"Overlay","description":"Description blah blah"},
{"id":"color","icon":"Droplet","title":"Colors","description":"Texts, Buttons, Sliders ...etc"},
{"id":"text","icon":"FontColorsOutlined","title":"Text","description":"Sizes, Fonts"},
{"id":"sounds","icon":"Volume2","title":"Sounds","description":"BlipBlopBLup"},
{"id":"darkmode","icon":"Moon","title":"Dark Mode","description":"Yeaah, no more daying"}
]

View File

@ -1,288 +0,0 @@
const {
app,
BrowserWindow,
ipcMain,
Tray,
Menu,
MenuItem,
dialog,
shell,
screen,
BrowserView,
systemPreferences,
Notification,
globalShortcut
} = require('electron')
const path = require('path')
// const { spawn, exec } = require('child_process')
// const { autoUpdater } = require('electron-updater')
const log = require('electron-log');
const packagejson = require('../package.json')
const is = require('electron-is')
const waitOn = require('wait-on');
const { getDoNotDisturb } = require('electron-notification-state');
const { app_config } = require("../config");
let app_path = is.dev()? app_config.proxy_local : `file://${path.join(__dirname, '..', 'renderer')}/index.html`;
let mainWindow;
let tray;
let watcher;
// This gets rid of this: https://github.com/electron/electron/issues/13186
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true
// app.commandLine.appendSwitch("disable-web-security")
//app.commandLine.appendSwitch('disable-gpu-vsync=gpu')
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
const gotTheLock = app.requestSingleInstanceLock()
const notifySupport = Notification.isSupported()
const isNotDisturb = getDoNotDisturb()
// Prevent multiple instances
if (!gotTheLock) {
app.quit()
}
function relaunchApp(){
mainWindow.close()
app.relaunch()
}
function resumeApp(){
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
}else{
createWindow()
}
}
function contextualMenu(payload){
if (!payload) {
return false
}
const menu = new Menu()
const menuItem = new MenuItem({
label: 'Inspect Element',
click: () => {
mainWindow.inspectElement(payload.x, payload.y)
}
})
menu.append(menuItem)
menu.popup(mainWindow)
}
function notify(params) {
if(!notifySupport || !params) return false
let options = {
title: "",
body: "",
icon: null,
timeoutType: "default"
}
const keys = Object.keys(params)
const values = Object.values(params)
for (let i = 0; i < keys.length; i++) {
options[keys[i]] = values[i]
}
new Notification(options).show()
}
async function __init() {
createWindow()
}
function createWindow() {
mainWindow = new BrowserWindow({
title: packagejson.title,
icon: path.join(__dirname, './icon.png'),
width: 1100,
height: 700,
minWidth: 1256,
minHeight: 755,
show: true,
frame: false,
transparent: false,
hasShadow: true,
//webgl: true,
visualEffectState: "followWindow",
backgroundColor: '#00ffffff',
webPreferences: {
//enableRemoteModule: true,
enableBlinkFeatures: true,
experimentalFeatures: true,
nodeIntegration: true,
// Disable in dev since I think hot reload is messing with it
webSecurity: !is.dev()
}
})
if (is.dev()) {
app.commandLine.appendSwitch('remote-debugging-port', '9222')
globalShortcut.register('CommandOrControl+R', () => {
mainWindow.reload()
})
globalShortcut.register('F5', () => {
mainWindow.reload()
})
}
mainWindow.webContents.session.webRequest.onHeadersReceived(
{
urls: ['http://*/*', 'https://*/*']
},
(details, callback) => {
delete details.responseHeaders['Access-Control-Allow-Origin']
delete details.responseHeaders['access-control-allow-origin']
if (details.url.includes('www.google-analytics.com')) {
details.responseHeaders['Access-Control-Allow-Origin'] = [ app_path ]
} else {
details.responseHeaders['Access-Control-Allow-Origin'] = ['*']
}
callback({
cancel: false,
responseHeaders: details.responseHeaders
})
}
)
tray = new Tray(
is.dev()
? path.join(__dirname, './icon.png')
: path.join(__dirname, '../build/icon.png')
);
const trayMenuTemplate = [
{
label: '🧰 Open DevTools',
click: () => mainWindow.webContents.openDevTools()
},
{
label: '🔄 Relaunch',
click: () => {
relaunchApp()
}
},
{
label: '🛑 Quit',
click: () => app.quit()
}
];
tray.setContextMenu(Menu.buildFromTemplate(trayMenuTemplate))
tray.setToolTip(packagejson.title)
tray.on('double-click', () => resumeApp())
mainWindow.loadURL(app_path)
mainWindow.focus()
if (is.dev()) {
mainWindow.webContents.openDevTools()
}
// const handleRedirect = (e, url) => {
// if (url !== mainWindow.webContents.getURL()) {
// e.preventDefault()
// shell.openExternal(url)
// }
// };
// mainWindow.webContents.on('will-navigate', handleRedirect)
// mainWindow.webContents.on('new-window', handleRedirect)
}
app.on('ready', () => {
if (is.dev()) {
loadWindow = new BrowserWindow({
width: 700,
height: 500,
frame: false,
resizable: false,
center: true,
transparent: true,
backgroundColor: "#00000000",
});
loadWindow.loadURL(`file://${__dirname}/statics/loading_dev.html`)
notify({title: "Starting development server..."})
waitOn({ resources: [app_path] }, function (err) {
if (err) {
return log.log(err)
}
__init()
loadWindow.close()
})
}else{
__init()
}
})
app.on('window-all-closed', () => {
mainWindow = null;
})
app.on('before-quit', async () => {
mainWindow.removeAllListeners('close');
mainWindow = null;
})
ipcMain.handle('update-progress-bar', (event, p) => {
mainWindow.setProgressBar(p);
})
ipcMain.handle('hide-window', () => {
if (mainWindow) {
mainWindow.hide();
}
})
ipcMain.handle('show-window', () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
})
ipcMain.handle('min-max-window', () => {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else if (mainWindow.maximizable) {
mainWindow.maximize();
}
})
ipcMain.handle('getSystemPreferences', () => {
return systemPreferences
})
ipcMain.handle('minimize-window', () => {
mainWindow.minimize();
})
ipcMain.handle('quit-app', () => {
app.quit();
})
ipcMain.handle('open-devtools', () => {
mainWindow.webContents.openDevTools({ mode: 'undocked' });
})
ipcMain.handle('appRelaunch', () => {
relaunchApp()
})
ipcMain.handle('app_notify', (event, payload) => {
notify(payload)
})
ipcMain.handle('contextualMenu', (event, payload) => {
contextualMenu(payload)
})
ipcMain.handle('inspectElement', (event, payload) => {
mainWindow.inspectElement(payload.x, payload.y)
})

126
package.json Executable file → Normal file
View File

@ -1,122 +1,20 @@
{
"name": "comty-development",
"UUID": "C8mVSr-4nmPp2-pr5Vrz-CU4kg4",
"title": "Comty™",
"DevBuild": true,
"version": "0.10.6",
"stage": "dev",
"description": "",
"author": "RageStudio",
"license": "ISC",
"license": "LGPL-2.1",
"types": "index.d.ts",
"private": true,
"main": "main/index.js",
"build": {
"files": [
"./dist/**/*",
"./pages/**/*",
"./node_modules/**/*"
],
"appId": "com.rstudio.comty",
"asar": false
},
"scripts": {
"start": "umi dev",
"debug": "node --inspect-brk ./node_modules/.bin/umi dev",
"postinstall": "electron-builder install-app-deps",
"electronDev": "concurrently \"electron .\" \"npm start\"",
"build": "npm run build:main && npm run build:renderer",
"build:main": "ESLINT=none roadhog build",
"build:renderer": "ESLINT=none umi build",
"pack": "npm run build && npm run rebuild && build",
"pack:dir": "npm run build && npm run rebuild && build --dir",
"pack:dirOnly": "build --dir"
"release": "node ./scripts/release.js"
},
"workspaces": [
"packages"
],
"dependencies": {
"@icons-pack/react-simple-icons": "^3.8.0",
"@lingui/react": "^2.9.2",
"@material-ui/core": "^4.9.9",
"@material-ui/icons": "^4.9.1",
"@ragestudio/ycorejs-lib": "^0.1.22",
"antd": "^4.6.6",
"axios": "^0.19.2",
"babel-core": "^6.26.3",
"classnames": "^2.2.6",
"concat-stream": "^2.0.0",
"cookie_js": "^1.4.0",
"dotenv": "^8.2.0",
"electron-config": "^2.0.0",
"electron-context-menu": "^2.3.0",
"electron-is": "^3.0.0",
"electron-log": "^4.2.4",
"electron-notification-state": "^1.0.4",
"electron-remote": "^1.3.0",
"electron-updater": "^4.3.4",
"enquire-js": "^0.2.1",
"feather-reactjs": "^2.0.13",
"html2canvas": "^1.0.0-rc.7",
"jquery": "^3.5.1",
"jsonwebtoken": "^8.5.1",
"less-vars-to-js": "^1.3.0",
"localforage": "^1.7.4",
"lodash": "^4.17.19",
"moment": "^2.28.0",
"node-sass": "^4.13.1",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.1.0",
"platform": "^1.3.6",
"radium": "^0.26.0",
"react": "^16.13.1",
"react-animations": "^1.0.0",
"react-color": "^2.18.1",
"react-dazzle": "^1.4.0",
"react-dom": "^16.12.0",
"react-emoji": "^0.5.0",
"react-google-recaptcha": "^2.0.1",
"react-helmet": "^5.2.1",
"react-linkify": "^1.0.0-alpha",
"react-perfect-scrollbar": "^1.5.8",
"react-redux": "^7.2.1",
"react-reveal": "^1.2.2",
"react-scripts": "^3.4.1",
"react-virtualized": "^9.21.2",
"redux-socket.io": "^1.4.0",
"redux-thunk": "^2.3.0",
"request": "^2.88.2",
"requirejs": "^2.3.6",
"simple-icons": "^3.8.0",
"socket.io-client": "^2.3.0",
"stack-trace": "0.0.10",
"store": "^2.0.12",
"styled-components": "^5.2.0",
"timeago.js": "^4.0.2",
"vm": "^0.1.0",
"wait-on": "^5.2.0"
"corenode": "^0.28.26",
"@octokit/rest": "19.0.4",
"dotenv": "16.0.3",
"7zip-min": "1.4.3",
"axios": "0.21.1"
},
"devDependencies": {
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@lingui/babel-preset-react": "^2.9.2",
"@lingui/cli": "^2.9.2",
"@lingui/loader": "^2.9.2",
"@types/electron-devtools-installer": "^2.2.0",
"@types/node": "^12.12.59",
"@umijs/preset-react": "^1.6.5",
"babel-eslint": "^10.0.1",
"babel-plugin-dev-expression": "^0.2.1",
"babel-plugin-module-resolver": "^4.0.0",
"concurrently": "^3.5.1",
"cross-env": "^6.0.0",
"dva-model-extend": "^0.1.2",
"electron": "^10.1.2",
"electron-builder": "^22.8.0",
"electron-debug": "^2.0.0",
"electron-devtools-installer": "^3.1.1",
"electron-rebuild": "^1.7.3",
"electron-reloader": "^1.0.1",
"jsdoc": "^3.6.5",
"less": "^3.12.2",
"less-loader": "^7.0.1",
"style-loader": "^1.2.1",
"typescript": "^3.8.3",
"umi": "^3.2.22"
}
"version": "0.20.2"
}

BIN
packages/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,66 @@
const path = require("path")
const { builtinModules } = require("module")
const aliases = {
"~": __dirname,
"~/": `${path.resolve(__dirname, "src")}/`,
"@src": path.join(__dirname, "src"),
cores: path.join(__dirname, "src/cores"),
schemas: path.join(__dirname, "constants"),
config: path.join(__dirname, "config"),
extensions: path.resolve(__dirname, "src/extensions"),
pages: path.join(__dirname, "src/pages"),
theme: path.join(__dirname, "src/theme"),
components: path.join(__dirname, "src/components"),
models: path.join(__dirname, "src/models"),
utils: path.join(__dirname, "src/utils"),
layouts: path.join(__dirname, "src/layouts"),
}
module.exports = (config = {}) => {
if (!config.resolve) {
config.resolve = {}
}
if (!config.server) {
config.server = {}
}
config.resolve.alias = aliases
config.server.port = process.env.listenPort ?? 8000
config.server.host = "0.0.0.0"
config.server.fs = {
allow: [".."]
}
config.envDir = path.join(__dirname, "environments")
config.css = {
preprocessorOptions: {
less: {
javascriptEnabled: true,
}
}
}
// config.build = {
// sourcemap: "inline",
// target: `node16`,
// outDir: "dist",
// assetsDir: ".",
// minify: process.env.MODE !== "development",
// rollupOptions: {
// external: [
// "electron",
// "electron-devtools-installer",
// ...builtinModules.flatMap(p => [p, `node:16`]),
// ],
// output: {
// entryFileNames: "[name].js",
// },
// },
// emptyOutDir: true,
// brotliSize: false,
// }
return config
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/App.jsx"></script>
</body>
</html>

View File

@ -0,0 +1,68 @@
{
"name": "@comty/console",
"version": "0.20.2",
"license": "MIT",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"peerDependencies": {
"react": "^16.8.6"
},
"dependencies": {
"@ant-design/icons": "4.7.0",
"@corenode/utils": "0.28.26",
"@foxify/events": "2.0.1",
"@loadable/component": "5.15.2",
"@vitejs/plugin-react": "^2.1.0",
"antd": "4.23.2",
"chart.js": "3.7.0",
"classnames": "2.3.1",
"evite": "0.12.3",
"feather-reactjs": "2.0.13",
"fuse.js": "6.5.3",
"global": "4.4.0",
"history": "5.2.0",
"js-cookie": "3.0.1",
"jwt-decode": "3.1.2",
"less": "4.1.2",
"linebridge": "0.13.0",
"moment": "2.29.1",
"nprogress": "^0.2.0",
"rc-animate": "^3.1.1",
"rc-util": "^5.19.3",
"rc-virtual-list": "^3.4.4",
"react-beautiful-dnd": "13.1.0",
"react-chartjs-2": "4.0.1",
"react-color": "2.19.3",
"react-contexify": "5.0.0",
"react-draggable": "4.4.4",
"react-helmet": "6.1.0",
"react-i18next": "11.15.3",
"react-icons": "4.3.1",
"react-intersection-observer": "8.33.1",
"react-json-view": "1.21.3",
"react-lazy-load-image-component": "^1.5.4",
"react-loadable": "^5.5.0",
"react-motion": "0.5.2",
"react-reveal": "1.2.2",
"react-rnd": "10.3.5",
"rxjs": "^7.5.5",
"store": "^2.0.12"
},
"devDependencies": {
"@types/jest": "^26.0.24",
"@types/node": "^16.4.10",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"@types/react-router-config": "^5.0.3",
"@types/react-router-dom": "^5.1.8",
"@typescript-eslint/eslint-plugin": "^4.29.0",
"concurrently": "^7.4.0",
"corenode": "0.28.26",
"cors": "2.8.5",
"cross-env": "^7.0.3",
"express": "^4.17.1",
"vite": "3.1.4"
}
}

View File

@ -0,0 +1,366 @@
// Patch global prototypes
Array.prototype.findAndUpdateObject = function (discriminator, obj) {
let index = this.findIndex(item => item[discriminator] === obj[discriminator])
if (index !== -1) {
this[index] = obj
}
return index
}
Array.prototype.move = function (from, to) {
this.splice(to, 0, this.splice(from, 1)[0])
return this
}
String.prototype.toTitleCase = function () {
return this.replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
})
}
Promise.tasked = function (promises) {
return new Promise(async (resolve, reject) => {
let rejected = false
for await (let promise of promises) {
if (rejected) {
return
}
try {
await promise()
} catch (error) {
rejected = true
return reject(error)
}
}
if (!rejected) {
return resolve()
}
})
}
import React from "react"
import { EviteRuntime } from "evite"
import { Helmet } from "react-helmet"
import * as antd from "antd"
import { Translation } from "react-i18next"
import config from "config"
import { Session, User } from "models"
import { NotFound, RenderError, Crash, Login } from "components"
import { Icons } from "components/Icons"
import Layout from "./layout"
import * as Router from "./router"
import "theme/index.less"
class App extends React.Component {
sessionController = new Session()
userController = new User()
state = {
session: null,
user: null,
}
static async initialize() {
window.app.version = config.package.version
}
static publicEvents = {
"clearAllOverlays": function () {
window.app.DrawerController.closeAll()
},
}
eventsHandlers = {
"app.close": () => {
if (window.isElectron) {
window.electron.ipcRenderer.invoke("app.close")
}
},
"app.minimize": () => {
if (window.isElectron) {
window.electron.ipcRenderer.invoke("app.minimize")
}
},
"app.openCreator": (...args) => {
return App.publicMethods.openCreator(...args)
},
"app.createLogin": async () => {
app.DrawerController.open("login", Login, {
componentProps: {
sessionController: this.sessionController
}
})
},
"session.logout": async () => {
await this.sessionController.logout()
},
"session.created": async () => {
await this.flushState()
await this.initialization()
// if is `/login` move to `/`
if (window.location.pathname === "/login") {
app.setLocation("/")
}
},
"session.destroyed": async () => {
await this.flushState()
app.eventBus.emit("app.forceToLogin")
},
"session.regenerated": async () => {
//await this.flushState()
//await this.initialization()
},
"session.invalid": async (error) => {
const token = await Session.token
if (!this.state.session && !token) {
return false
}
await this.sessionController.forgetLocalSession()
await this.flushState()
app.eventBus.emit("app.forceToLogin")
antd.notification.open({
message: <Translation>
{(t) => t("Invalid Session")}
</Translation>,
description: <Translation>
{(t) => t(error)}
</Translation>,
icon: <Icons.MdOutlineAccessTimeFilled />,
})
},
"app.forceToLogin": () => {
window.app.setLocation("/login")
},
"websocket_connected": () => {
if (this.wsReconnecting) {
this.wsReconnectingTry = 0
this.wsReconnecting = false
this.initialization()
setTimeout(() => {
console.log("WS Reconnected")
}, 500)
}
},
"websocket_connection_error": () => {
if (!this.wsReconnecting) {
this.latencyWarning = null
this.wsReconnectingTry = 0
this.wsReconnecting = true
console.log("WS Reconnecting")
}
this.wsReconnectingTry = this.wsReconnectingTry + 1
if (this.wsReconnectingTry > 3 && app.settings.get("app.reloadOnWSConnectionError")) {
window.location.reload()
}
},
"websocket_latency_too_high": () => {
if (!this.latencyWarning) {
this.latencyWarning = true
antd.notification.open({
message: <Translation>
{(t) => t("Latency Warning")}
</Translation>,
description: <Translation>
{(t) => t("The latency between your computer and the server is too high. This may cause some issues. Please check your internet connection.")}
</Translation>,
icon: <Icons.MdOutlineAccessTimeFilled />,
})
}
},
"websocket_latency_normal": () => {
if (this.latencyWarning) {
this.latencyWarning = null
antd.notification.close("latency-warning")
}
},
}
static staticRenders = {
PageLoad: () => {
return <antd.Skeleton active />
},
NotFound: (props) => {
return <NotFound />
},
RenderError: (props) => {
return <RenderError {...props} />
},
Crash: Crash.CrashWrapper,
Initialization: () => {
return <div className="app_splash_wrapper">
<div className="splash_logo">
<img src={config.logo.alt} />
</div>
<div className="splash_label">
<Icons.LoadingOutlined />
</div>
</div>
}
}
static publicMethods = {
}
constructor(props) {
super(props)
Object.keys(this.eventsHandlers).forEach((event) => {
app.eventBus.on(event, this.eventsHandlers[event])
})
}
flushState = async () => {
await this.setState({ session: null, user: null })
}
componentDidMount = async () => {
app.eventBus.emit("app.initialization.start")
await this.initialization()
app.eventBus.emit("app.initialization.finish")
}
initialization = async () => {
console.debug(`[App] Initializing app`)
const initializationTasks = [
async () => {
try {
// get remotes origins from config
const defaultRemotes = config.remotes
// get storaged remotes origins
const storedRemotes = await app.settings.get("remotes") ?? {}
// mount main api bridge
await this.props.cores.ApiCore.connectBridge("main", {
origin: storedRemotes.mainApi ?? defaultRemotes.mainApi,
locked: true,
})
await this.props.cores.ApiCore.namespaces["main"].initialize()
app.eventBus.emit("app.initialization.api_success")
} catch (error) {
app.eventBus.emit("app.initialization.api_error", error)
console.error(`[App] Error while initializing api`, error)
throw {
cause: "Cannot connect to API",
details: `Sorry but we cannot connect to the API. Please try again later. [${config.remotes.mainApi}]`,
}
}
},
async () => {
try {
await this.__SessionInit()
app.eventBus.emit("app.initialization.session_success")
} catch (error) {
app.eventBus.emit("app.initialization.session_error", error)
throw {
cause: "Cannot initialize session",
details: error.message,
}
}
},
async () => {
try {
await this.__UserInit()
app.eventBus.emit("app.initialization.user_success")
} catch (error) {
app.eventBus.emit("app.initialization.user_error", error)
throw {
cause: "Cannot initialize user data",
details: error.message,
}
}
},
]
await Promise.tasked(initializationTasks).catch((reason) => {
console.error(`[App] Initialization failed: ${reason.cause}`)
app.eventBus.emit("runtime.crash", {
message: `App initialization failed (${reason.cause})`,
details: reason.details,
})
})
}
__SessionInit = async () => {
const token = await Session.token
if (!token || token == null) {
app.eventBus.emit("app.forceToLogin")
return false
}
const session = await this.sessionController.getCurrentSession().catch((error) => {
console.error(`[App] Cannot get current session: ${error.message}`)
return false
})
await this.setState({ session })
}
__UserInit = async () => {
if (!this.state.session) {
return false
}
const user = await User.data()
await this.setState({ user })
}
render() {
return <React.Fragment>
<Helmet>
<title>{config.app.siteName}</title>
</Helmet>
<Router.InternalRouter>
<Layout
user={this.state.user}
staticRenders={App.staticRenders}
bindProps={{
staticRenders: App.staticRenders,
user: this.state.user,
session: this.state.session,
sessionController: this.sessionController,
userController: this.userController,
}}
>
<Router.PageRender />
</Layout>
</Router.InternalRouter>
</React.Fragment>
}
}
export default new EviteRuntime(App)

View File

@ -0,0 +1,27 @@
import React from "react"
import classnames from "classnames"
import "./index.less"
export default (props) => {
const { children } = props
return <div
style={{
...props.style,
padding: props.padding,
}}
className={classnames(
"actionsBar",
[props.mode],
{["transparent"]: props.type === "transparent"},
{["spaced"]: props.spaced},
)}
>
<div
style={props.wrapperStyle}
className="wrapper"
>
{children}
</div>
</div>
}

View File

@ -0,0 +1,100 @@
@actionsBar_height: fit-content;
.actionsBar {
--ignore-dragger: true;
display: inline-block;
white-space: nowrap;
overflow-x: overlay;
padding: 15px;
width: 100%;
height: @actionsBar_height;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: #0c0c0c15;
backdrop-filter: blur(10px);
--webkit-backdrop-filter: blur(10px);
transition: all 200ms ease-in-out;
::-webkit-scrollbar {
position: absolute;
display: none;
width: 0;
height: 0;
z-index: 0;
}
.wrapper {
display: flex;
flex-direction: row;
align-items: center;
height: 100%;
transition: all 200ms ease-in-out;
> div {
margin: 0 5px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
--ignore-dragger: true;
span {
height: fit-content;
}
}
}
&.float {
z-index: 1000;
position: sticky;
bottom: 0;
top: 0;
right: 0;
width: 100%;
}
&.fixedBottom {
z-index: 1000;
position: fixed;
bottom: 0;
right: 0;
left: 0;
width: 100%;
margin-bottom: 10px;
}
&.fixedTop {
z-index: 1000;
position: fixed;
top: 0;
right: 0;
left: 0;
width: 100%;
margin-bottom: 10px;
}
&.transparent {
background-color: transparent;
border: none;
backdrop-filter: none;
--webkit-backdrop-filter: none;
}
&.spaced {
.wrapper {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}
}

View File

@ -0,0 +1,38 @@
import React from "react"
import { Result, Button } from "antd"
import "./index.less"
export const CrashComponent = (props) => {
const {
crash = {
details: "Unknown error",
}
} = props
return <Result
status="error"
title="Well, we're sorry! The application has a fatal crash."
subTitle={crash.message}
extra={[
<Button type="primary" key="reload" onClick={() => window.location.reload()}>
Reload app
</Button>
]}
>
{crash.details &&
<div>
<code>{crash.details}</code>
</div>}
</Result>
}
export const CrashWrapper = (props) => {
const { crash } = props
return <div className="crashWrapper">
<CrashComponent crash={crash} />
</div>
}
export default CrashComponent

View File

@ -0,0 +1,62 @@
.crashWrapper {
position: absolute;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 10000;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
color: #fff;
.ant-result,
.ant-result-title,
.ant-result-subtitle,
.ant-result-extra {
color: #fff;
}
.ant-result-content {
background-color: rgba(0, 0, 0, 0.7) !important;
}
code {
user-select: text;
color: #fff;
font-family: "DM Mono", monospace;
}
h1,
h2,
h3,
h4,
h5,
h6,
p,
span,
pre {
color: #fff;
}
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
animation: opacity-fade-in 0.3s ease-in-out;
}
@keyframes opacity-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@ -0,0 +1,23 @@
import React from "react"
import { Button } from "antd"
import classnames from "classnames"
import "./index.less"
export default (props) => {
return <div className="followButton">
<div className="counter">
{props.count}
</div>
<Button
type="ghost"
onClick={props.onClick}
className={classnames(
"btn",
{ ["followed"]: props.followed }
)}
>
<span>{props.followed ? "Following" : "Follow"}</span>
</Button>
</div>
}

View File

@ -0,0 +1,82 @@
@borderRadius: 8px;
.followButton {
display: inline-flex;
flex-direction: row;
align-items: center;
transition: all 150ms ease-in-out;
outline: 1px solid var(--border-color);
border-radius: @borderRadius;
.counter {
padding: 5px 15px;
}
.btn {
cursor: pointer;
width: 100px;
padding: 5px 15px;
transition: all 150ms ease-in-out;
border-radius: @borderRadius;
border-left: 1.5px solid var(--border-color);
border-top: 0;
border-right: 0;
border-bottom: 0;
text-align: center;
letter-spacing: 1px;
span {
user-select: none !important;
transition: all 150ms ease-in-out;
color: white !important;
}
opacity: 0.9;
background : linear-gradient(120deg, #6559ae, #ff7159, #6559ae);
background-size : 400% 400%;
background-position: 14% 0%;
&:hover {
opacity: 1;
animation: gradientMove 2s forwards ease-in-out infinite;
background: linear-gradient(120deg, #6559ae, #ff7159, #6559ae);
background-size: 400% 400%;
background-position: 14% 0%;
}
// make an click animation transforming size
&:active {
transform: scale(0.95);
}
&.followed {
background: none;
span {
color: #7e7e7e !important;
}
}
}
}
@keyframes gradientMove {
0% {
background-position: 14% 0%;
}
50% {
background-position: 87% 100%;
}
100% {
background-position: 14% 0%;
}
}

View File

@ -0,0 +1,43 @@
import React from "react"
import * as antd from "antd"
import { Icons } from "components/Icons"
import "./index.less"
export default (props) => {
if (props.followers.length === 0) {
return <antd.Result
icon={<Icons.UserX style={{ fontSize: "50px" }} />}
>
<h2>
It's seems this user has no followers, yet.
</h2>
<h3>
Maybe you can help them out?
</h3>
</antd.Result>
}
return <div className="followersList">
{props.followers.map((follower) => {
return <div className="follower">
<div className="avatar">
<antd.Avatar shape="square" src={follower.avatar} />
</div>
<div className="names">
<div>
<h2>
{follower.fullName ?? follower.username}
</h2>
</div>
<div>
<span>
@{follower.username}
</span>
</div>
</div>
</div>
})}
</div>
}

View File

@ -0,0 +1,65 @@
@borderRadius: 12px;
.ant-result {
display : flex;
flex-direction: column;
align-items : center;
}
.ant-result-icon {
background-color: var(--background-color-accent);
border-radius : @borderRadius;
width : fit-content;
padding : 20px;
svg {
margin: 0;
color : var(--background-color-contrast);
}
}
.ant-result-content {
background-color: var(--background-color-accent);
border-radius : @borderRadius;
h2 {
color: var(--background-color-contrast);
}
h3 {
color : var(--background-color-contrast);
font-weight: 100;
}
}
.followersList {
.follower {
width : 100%;
//max-width: 50vw;
padding : 10px;
display : inline-flex;
align-items : center;
border-radius: 8px;
border : 1px solid var(--border-color);
h2 {
margin: 0;
font-size: 22px;
line-height: 26px;
}
>div {
margin-right: 10px;
}
.names {
}
}
>div {
margin-bottom: 20px;
}
}

View File

@ -0,0 +1,29 @@
import React from "react"
import config from "config"
import "./index.less"
export default (props) => {
const renderLinks = () => {
return config.footerLinks.map((link, index) => {
let linkProps = {
key: index,
}
if (link.url) {
linkProps.href = link.url
}
if (link.location) {
linkProps.onClick = () => {
app.setLocation(link.location)
}
}
return <>| <a {...linkProps}>{link.label}</a> {index === config.footerLinks.length - 1 ? "|" : ""} </>
})
}
return <div className="footer">
{config.app.copyright} {config.footerLinks ? renderLinks() : null}
</div>
}

View File

@ -0,0 +1,9 @@
.footer {
position: fixed;
bottom: 0;
padding: 10px;
font-family: "DM Mono", monospace;
font-size: 12px;
font-weight: 200;
}

View File

@ -0,0 +1,400 @@
import React from "react"
import { Icons } from "components/Icons"
import {
Form,
Input,
Button,
Checkbox,
Select,
Dropdown,
Slider,
InputNumber,
DatePicker,
AutoComplete,
Divider,
Switch,
} from "antd"
import HeadShake from "react-reveal/HeadShake"
import "./index.less"
const allComponents = {
Input,
Button,
Checkbox,
Select,
Dropdown,
Slider,
InputNumber,
DatePicker,
AutoComplete,
Divider,
Switch,
}
export default class FormGenerator extends React.Component {
ref = React.createRef()
fieldsReferences = {}
unsetValues = {}
discardedValues = []
state = {
validating: false,
shakeItem: false,
failed: {},
}
ctx = {
clearErrors: () => {
this.setState({ failed: {} })
},
clearForm: () => {
this.ctx.clearErrors()
this.ctx.toogleValidation(false)
this.ref.current.resetFields()
},
finish: () => this.ref.current.submit(),
error: (id, error) => {
this.handleFormError(id, error)
},
shake: (id) => {
this.formItemShake(id)
},
toogleValidation: (to) => {
if (typeof to !== "undefined") {
return this.setState({ validating: to })
}
this.setState({ validating: !this.state.validating })
return this.state.validating
},
formRef: this.ref,
}
handleFinish(payload) {
if (typeof this.props.onFinish !== "function") {
console.error(`onFinish is not an function`)
return false
}
// try to read unset values
Object.keys(this.fieldsReferences).forEach((key) => {
const ref = this.fieldsReferences[key].current
if (typeof ref.state !== "undefined") {
this.unsetValues[key] = ref.state?.value || ref.state?.checked
}
})
// filter discarded values
try {
const keys = Object.keys(payload)
this.discardedValues.forEach((id) => {
if (keys.includes(id)) {
delete payload[id]
}
})
} catch (error) {
// terrible
}
// fulfil unset values
payload = { ...payload, ...this.unsetValues }
return this.props.onFinish(payload, this.ctx)
}
formItemShake(id) {
this.setState({ shakeItem: id })
setTimeout(() => {
this.setState({ shakeItem: false })
}, 50)
}
handleFormError(item, error) {
let fails = this.state.failed
fails[item] = error ?? true
this.setState({ failed: fails })
this.formItemShake(item)
}
handleFailChange(event) {
const itemID = event.target.id
if (itemID) {
let fails = this.state.failed
if (fails["all"]) {
fails["all"] = false
this.setState({ failed: fails })
}
if (fails[itemID]) {
// try deactivate failed statement
fails[itemID] = false
this.setState({ failed: fails })
}
}
}
shouldShakeItem(id) {
try {
const mutation = false
if (this.state.shakeItem === "all") {
return mutation
}
if (this.state.shakeItem == id) {
return mutation
}
} catch (error) {
// not returning
}
}
discardValueFromId = (id) => {
let ids = []
if (Array.isArray(id)) {
ids = id
} else {
ids.push(id)
}
ids.forEach((_id) => {
const value = this.discardedValues ?? []
value.push(_id)
this.discardedValues = value
})
}
renderValidationIcon() {
if (this.props.renderLoadingIcon && this.state.validating) {
return <Icons.LoadingOutlined spin style={{ margin: 0 }} />
}
return null
}
renderElementPrefix = (element) => {
if (element.icon) {
let renderIcon = null
const iconType = typeof element.icon
switch (iconType) {
case "string": {
if (typeof Icons[element.icon] !== "undefined") {
renderIcon = React.createElement(Icons[element.icon])
} else {
console.warn("provided icon is not available on icons libs")
}
break
}
case "object": {
renderIcon = element.icon
break
}
default: {
console.warn(`cannot mutate icon cause type (${iconType}) is not handled`)
break
}
}
if (renderIcon) {
// try to generate icon with props
return React.cloneElement(renderIcon, element.iconProps ? { ...element.iconProps } : null)
}
} else {
return element.prefix ?? null
}
}
renderItems(elements) {
if (Array.isArray(elements)) {
try {
return elements.map((field) => {
let { item, element } = field
// if item has no id, return an uncontrolled field
if (typeof field.id === "undefined") {
return React.createElement(allComponents[element.component], element.props)
}
// fulfill
if (typeof item === "undefined") {
item = {}
}
if (typeof element === "undefined") {
element = {}
}
// check if component is available on library
if (typeof allComponents[element.component] === "undefined") {
console.warn(`[${element.component}] is not an valid component`)
return null
}
// handle groups
if (typeof field.group !== "undefined") {
return (
<div style={{ display: "flex" }} key={field.id}>
{this.renderItems(field.group)}
</div>
)
}
//* RENDER
const failStatement = this.state.failed["all"] ?? this.state.failed[field.id]
const rules = item.rules
const hasFeedback = item.hasFeedback ?? false
let elementProps = {
disabled: this.state.validating,
...element.props,
}
let itemProps = {
...item.props,
}
switch (element.component) {
case "Checkbox": {
elementProps.onChange = (e) => {
this.unsetValues[field.id] = e.target.checked
elementProps.checked = e.target.checked
elementProps.value = e.target.checked
}
break
}
case "Button": {
this.discardValueFromId(field.id)
if (field.withValidation) {
elementProps.icon = this.state.validating ? (
<Icons.LoadingOutlined spin style={{ marginRight: "7px" }} />
) : null
}
break
}
case "Input": {
itemProps = {
...itemProps,
hasFeedback,
rules,
onChange: (e) => this.handleFailChange(e),
help: failStatement ? failStatement : null,
validateStatus: failStatement ? "error" : null,
}
elementProps = {
...elementProps,
id: field.id,
prefix: this.renderElementPrefix(element) ?? null,
placeholder: element.placeholder,
}
break
}
case "Select": {
if (typeof element.renderItem !== "undefined") {
elementProps.children = element.renderItem
}
if (typeof element.options !== "undefined" && !element.renderItem) {
if (!Array.isArray(element.options)) {
console.warn(
`Invalid options data type, expecting Array > received ${typeof element.options}`,
)
return null
}
elementProps.children = element.options.map((option) => {
return (
<Select.Option key={option.id ?? Math.random} value={option.value ?? option.id}>
{option.name ?? null}
</Select.Option>
)
})
}
itemProps = {
...itemProps,
hasFeedback,
rules,
validateStatus: failStatement ? "error" : null,
help: failStatement ? failStatement : null,
}
break
}
default: {
itemProps = {
...itemProps,
hasFeedback,
rules,
validateStatus: failStatement ? "error" : null,
help: failStatement ? failStatement : null,
}
break
}
}
// set reference
this.fieldsReferences[field.id] = elementProps.ref = React.createRef()
// return field
return (
<div className={field.className} style={field.style} key={field.id}>
{field.title ?? null}
<HeadShake spy={this.shouldShakeItem(field.id)}>
<Form.Item label={field.label} name={field.id} key={field.id} {...itemProps}>
{React.createElement(allComponents[element.component], elementProps)}
</Form.Item>
</HeadShake>
</div>
)
})
} catch (error) {
console.log(error)
return null
}
}
}
componentDidMount() {
if (!this.props.items) {
console.warn(`items not provided, nothing to render`)
return null
}
// handle discardedValues
if (Array.isArray(this.props.items)) {
this.props.items.forEach((item) => {
if (item.ignoreValue) {
this.discardValueFromId(item.id)
}
})
}
}
render() {
const helpStatus = this.state.failed["all"] ?? this.state.failed["result"]
const validateStatus = this.state.failed["all"] || this.state.failed["result"] ? "error" : null
if (!this.props.items) {
console.warn(`Nothing to render`)
return null
}
return <div
key={this.props.id}
id={this.props.id}
className="formWrapper"
>
<Form
hideRequiredMark={this.props.hideRequiredMark ?? false}
name={this.props.name ?? "new_form"}
onFinish={(e) => this.handleFinish(e)}
ref={this.ref}
{...this.props.formProps}
>
{this.renderItems(this.props.items)}
<Form.Item key="result" help={helpStatus} validateStatus={validateStatus} />
</Form>
{this.renderValidationIcon()}
</div>
}
}

View File

@ -0,0 +1,5 @@
.formWrapper {
svg {
margin: 0;
}
}

View File

@ -0,0 +1,29 @@
import React from "react"
// import icons lib
import * as lib1 from "feather-reactjs"
import * as lib2 from "react-icons/md"
import * as lib3 from "@ant-design/icons"
const marginedStyle = { width: "1em", height: "1em", marginRight: "10px", verticalAlign: "-0.125em" }
const customs = {
verifiedBadge: () => <svg style={marginedStyle} xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"> <path d="M23 12l-2.44-2.78.34-3.68-3.61-.82-1.89-3.18L12 3 8.6 1.54 6.71 4.72l-3.61.81.34 3.68L1 12l2.44 2.78-.34 3.69 3.61.82 1.89 3.18L12 21l3.4 1.46 1.89-3.18 3.61-.82-.34-3.68L23 12m-13 5l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"></path></svg>,
}
export const Icons = {
...customs,
...lib1,
...lib2,
...lib3,
}
export function createIconRender(icon, props) {
if (typeof Icons[icon] !== "undefined") {
return React.createElement(Icons[icon], props)
}
return null
}
export default Icons

View File

@ -0,0 +1,6 @@
import React from "react"
import { LazyLoadImage } from "react-lazy-load-image-component"
import "react-lazy-load-image-component/src/effects/blur.css"
export default (props) => <LazyLoadImage {...props} effect="blur" />

View File

@ -0,0 +1,172 @@
import React from "react"
import { DraggableDrawer } from "components"
import EventEmitter from "@foxify/events"
import "./index.less"
export default class DrawerController extends React.Component {
constructor(props) {
super(props)
this.state = {
addresses: {},
refs: {},
drawers: [],
}
this.DrawerController = {
open: this.open,
close: this.close,
closeAll: this.closeAll,
}
window.app["DrawerController"] = this.DrawerController
}
sendEvent = (id, ...context) => {
const ref = this.state.refs[id]?.current
return ref.events.emit(...context)
}
open = (id, component, options) => {
const refs = this.state.refs ?? {}
const drawers = this.state.drawers ?? []
const addresses = this.state.addresses ?? {}
const instance = {
id,
key: id,
ref: React.createRef(),
children: component,
options,
controller: this,
}
if (typeof addresses[id] === "undefined") {
drawers.push(<Drawer {...instance} />)
addresses[id] = drawers.length - 1
refs[id] = instance.ref
} else {
const ref = refs[id].current
const isLocked = ref.state.locked
if (!isLocked) {
drawers[addresses[id]] = <Drawer {...instance} />
refs[id] = instance.ref
} else {
console.warn("Cannot update an locked drawer.")
}
}
this.setState({ refs, addresses, drawers })
}
destroy = (id) => {
let { addresses, drawers, refs } = this.state
const index = addresses[id]
if (typeof drawers[index] !== "undefined") {
drawers = drawers.filter((value, i) => i !== index)
}
delete addresses[id]
delete refs[id]
this.setState({ addresses, drawers })
}
close = (id) => {
const ref = this.state.refs[id]?.current
if (typeof ref !== "undefined") {
if (ref.state.locked && ref.state.visible) {
return console.warn("This drawer is locked and cannot be closed")
} else {
return ref.close()
}
} else {
return console.warn("This drawer not exists")
}
}
closeAll = () => {
this.state.drawers.forEach((drawer) => {
drawer.ref.current.close()
})
}
render() {
return this.state.drawers
}
}
export class Drawer extends React.Component {
options = this.props.options ?? {}
events = new EventEmitter()
state = {
visible: true,
}
componentDidMount = async () => {
if (typeof this.props.controller === "undefined") {
throw new Error(`Cannot mount an drawer without an controller`)
}
if (typeof this.props.children === "undefined") {
throw new Error(`Empty component`)
}
}
toogleVisibility = (to) => {
this.setState({ visible: to ?? !this.state.visible })
}
close = () => {
this.toogleVisibility(false)
this.events.emit("beforeClose")
setTimeout(() => {
if (typeof this.options.onClose === "function") {
this.options.onClose()
}
this.props.controller.destroy(this.props.id)
}, 500)
}
sendEvent = (...context) => {
return this.props.controller.sendEvent(this.props.id, ...context)
}
handleDone = (...context) => {
if (typeof this.options.onDone === "function") {
this.options.onDone(this, ...context)
}
}
handleFail = (...context) => {
if (typeof this.options.onFail === "function") {
this.options.onFail(this, ...context)
}
}
render() {
const drawerProps = {
...this.options.props,
ref: this.props.ref,
key: this.props.id,
onRequestClose: this.close,
open: this.state.visible,
containerElementClass: "drawer",
modalElementClass: "body",
}
const componentProps = {
...this.options.componentProps,
events: this.events,
close: this.close,
handleDone: this.handleDone,
handleFail: this.handleFail,
}
return <DraggableDrawer {...drawerProps}>
{React.createElement(this.props.children, componentProps)}
</DraggableDrawer>
}
}

View File

@ -0,0 +1,26 @@
.drawer {
.body {
position: absolute;
top: 50px;
padding: 30px 10px 10px 10px;
background-color: var(--background-color-primary);
width: 100%;
max-width: 700px;
min-height: 100%;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.body::before{
content: "";
background-color: var(--background-color-contrast);
width: 100px;
height: 8px;
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
border-radius: 8px;
}
}

View File

@ -0,0 +1,49 @@
import React from "react"
import * as antd from "antd"
import { Icons } from "components/Icons"
import { AppSearcher } from "components"
import classnames from "classnames"
import "./index.less"
export default class Header extends React.Component {
controller = window.app["HeaderController"] = {
toggleVisibility: (to) => {
if (window.isMobile) {
to = true
}
this.setState({ visible: to ?? !this.state.visible })
},
isVisible: () => this.state.visible,
}
state = {
visible: false, // set by default to create an animation
}
componentDidMount = async () => {
setTimeout(() => {
this.controller.toggleVisibility(true)
}, 100)
}
render() {
return (
<antd.Layout.Header className={classnames(`app_header`, { ["hidden"]: !window.isMobile && !this.state.visible })}>
<div>
<antd.Button
onClick={window.app.openCreateNew}
type="primary"
shape="circle"
icon={<Icons.Plus style={{ margin: 0 }} />}
/>
</div>
{!window.isMobile &&
<div>
<AppSearcher />
</div>}
</antd.Layout.Header>
)
}
}

View File

@ -0,0 +1,32 @@
@import "theme/index.less";
.app_header {
user-select: none;
--webkit-user-select: none;
display: flex;
flex-direction: row;
align-items: center;
z-index: 100;
height: @app_header_height !important;
padding: 10px;
transition: all ease-in-out 150ms;
background-color: transparent;
background: transparent !important;
border-bottom: 1px var(--border-color) solid;
>div {
margin-right: 16px;
}
&.hidden {
opacity: 0;
height: 0 !important;
padding: 0 !important;
border: 0 !important;
}
}

View File

@ -0,0 +1,5 @@
export { default as Header } from "./header"
export { default as Drawer } from "./drawer"
export { default as Sidebar } from "./sidebar"
export { default as Sidedrawer } from "./sidedrawer"
export { default as Modal } from "./modal"

View File

@ -0,0 +1,74 @@
import React from "react"
import { Modal, Button } from "antd"
import { Icons } from "components/Icons"
import "./index.less"
export default class AppModal extends React.Component {
constructor(props) {
super(props)
this.controller = app.ModalController = {
open: this.open,
close: this.close,
modalRef: this.modalRef,
}
}
state = {
currentRender: null,
renderParams: {}
}
modalRef = React.createRef()
open = (render, params = {}) => {
this.setState({
currentRender: render,
renderParams: params
})
}
close = () => {
this.setState({
currentRender: null,
renderParams: {}
})
}
handleModalClose = () => {
this.close()
}
renderModal = () => {
return <div className="appModalWrapper">
<Button
icon={<Icons.X />}
className="closeButton"
onClick={this.handleModalClose}
shape="circle"
/>
<div className="appModal" ref={this.modalRef}>
{React.createElement(this.state.currentRender, {
...this.state.renderParams.props ?? {},
close: this.close,
})}
</div>
</div>
}
render() {
return <Modal
open={this.state.currentRender}
maskClosable={this.state.renderParams.maskClosable ?? true}
modalRender={this.renderModal}
maskStyle={{
backgroundColor: "rgba(0, 0, 0, 0.7)",
backdropFilter: "blur(5px)"
}}
destroyOnClose
centered
/>
}
}

View File

@ -0,0 +1,43 @@
.appModalWrapper {
.closeButton {
background-color: var(--background-color-primary);
pointer-events: all;
font-size: 1.2rem;
transform: translate(-25px, -10px);
svg {
margin: 0 !important;
color: var(--text-color);
}
&:hover {
background-color: var(--background-color-primary);
}
}
.appModal {
pointer-events: all;
display: flex;
flex-direction: column;
align-self: center;
border-radius: 8px;
width: fit-content;
height: fit-content;
min-height: 500px;
min-width: 600px;
padding: 30px;
background-color: var(--background-color-accent);
color: var(--text-color);
}
}

View File

@ -0,0 +1,266 @@
import React from "react"
import { Button } from "antd"
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"
import { ActionsBar } from "components"
import { Icons, createIconRender } from "components/Icons"
import sidebarItems from "schemas/routes.json"
import { sidebarKeys as defaultSidebarKeys } from "schemas/defaultSettings"
import Selector from "../selector"
import "./index.less"
const allItemsMap = [...sidebarItems].map((item, index) => {
item.key = index.toString()
item.index = index
return item
})
const getAllItems = () => {
let items = {}
allItemsMap.forEach((item) => {
items[item.id] = {
...item,
content: (
<>
{createIconRender(item.icon)} {item.title}
</>
),
}
})
return items
}
const allItems = getAllItems()
export default class SidebarEditor extends React.Component {
state = {
items: [],
lockedIndex: [],
}
componentDidMount() {
this.loadItems()
}
loadItems = () => {
const storagedKeys = window.app.settings.get("sidebarKeys") ?? defaultSidebarKeys
const active = []
const lockedIndex = []
// set current active items
storagedKeys.forEach((key) => {
if (typeof allItems[key] !== "undefined") {
if (allItems[key].locked) {
lockedIndex.push(allItems[key].index)
}
active.push(key)
}
})
this.setState({ items: active, lockedIndex })
}
onSave = () => {
window.app.settings.set("sidebarKeys", this.state.items)
window.app.SidebarController.toggleEdit(false)
}
onDiscard = () => {
window.app.SidebarController.toggleEdit(false)
}
onSetDefaults = () => {
window.app.settings.set("sidebarKeys", defaultSidebarKeys)
this.loadItems()
}
reorder = (list, startIndex, endIndex) => {
const result = Array.from(list)
const [removed] = result.splice(startIndex, 1)
result.splice(endIndex, 0, removed)
return result
}
onDragEnd = (result) => {
if (!result.destination) {
return false
}
if (this.state.lockedIndex.includes(result.destination.index)) {
return false
}
if (allItems[result.draggableId].locked) {
console.warn("Cannot move an locked item")
return false
}
const items = this.reorder(this.state.items, result.source.index, result.destination.index)
this.setState({ items })
}
deleteItem = (key) => {
// check if item is locked
if (allItems[key].locked) {
console.warn("Cannot delete an locked item")
return false
}
this.setState({ items: this.state.items.filter((item) => item !== key) })
}
addItem = () => {
const keys = []
// filter by active keys
allItemsMap.forEach((item) => {
if (!this.state.items.includes(item.id)) {
keys.push(item.id)
}
})
window.app.DrawerController.open("sidebar_item_selector", Selector, {
props: {
width: "65%",
},
componentProps: {
items: keys
},
onDone: (drawer, selectedKeys) => {
drawer.close()
if (Array.isArray(selectedKeys)) {
const update = this.state.items ?? []
selectedKeys.forEach((key) => {
if (update.includes(key)) {
return false
}
update.push(key)
})
this.setState({ items: update })
}
},
})
}
render() {
const grid = 6
const getItemStyle = (isDragging, draggableStyle, component, isDraggingOver) => ({
cursor: component.locked ? "not-allowed" : "grab",
userSelect: "none",
padding: grid * 2,
margin: `0 0 ${grid}px 0`,
borderRadius: "6px",
transition: "150ms all ease-in-out",
width: "100%",
border: isDraggingOver ? "2px dashed #e0e0e0" : "none",
color: component.locked ? "rgba(145,145,145,0.6)" : "#000",
background: component.locked
? "rgba(145, 145, 145, 0.2)"
: isDragging
? "rgba(145, 145, 145, 0.5)"
: "transparent",
...draggableStyle,
})
const getListStyle = (isDraggingOver) => ({
background: "transparent",
transition: "150ms all ease-in-out",
padding: grid,
width: "100%",
})
return (
<div>
<DragDropContext onDragEnd={this.onDragEnd}>
<Droppable droppableId="droppable">
{(droppableProvided, droppableSnapshot) => (
<div
ref={droppableProvided.innerRef}
style={getListStyle(droppableSnapshot.isDraggingOver)}
>
{this.state.items.map((key, index) => {
const itemComponent = allItems[key]
return (
<Draggable
isDragDisabled={itemComponent.locked}
key={key}
draggableId={key}
index={index}
>
{(draggableProvided, draggableSnapshot) => (
<div
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
style={getItemStyle(
draggableSnapshot.isDragging,
draggableProvided.draggableProps.style,
itemComponent,
droppableSnapshot.isDraggingOver,
)}
>
{!allItems[key].locked &&
<Icons.Trash
onClick={() => this.deleteItem(key)}
className="sidebar_editor_deleteBtn"
/>
}
{itemComponent.icon && createIconRender(itemComponent.icon)}
{itemComponent.title ?? itemComponent.id}
</div>
)}
</Draggable>
)
})}
{droppableProvided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<ActionsBar
style={{ position: "absolute", bottom: 0, left: 0, width: "100%", borderRadius: "12px 12px 0 0" }}
>
<div>
<Button
style={{ lineHeight: 0 }}
icon={<Icons.Plus style={{ margin: 0, padding: 0 }} />}
onClick={this.addItem}
/>
</div>
<div>
<Button
style={{ lineHeight: 0 }}
icon={<Icons.Check />}
type="primary"
onClick={this.onSave}
>
Done
</Button>
</div>
<div>
<Button onClick={this.onDiscard} icon={<Icons.XCircle />} >Cancel</Button>
</div>
<div>
<Button type="link" onClick={this.onSetDefaults}>Set defaults</Button>
</div>
</ActionsBar>
</div>
)
}
}

View File

@ -0,0 +1,14 @@
.app_sidebar_sider_edit .ant-layout-sider-children{
margin-top: 15px!important;
.app_sidebar_menu_wrapper {
opacity: 0;
height: 0;
overflow: hidden;
}
}
.sidebar_editor_deleteBtn:hover{
color: red;
cursor: pointer;
}

View File

@ -0,0 +1 @@
export { default as SidebarEditor } from './editor';

View File

@ -0,0 +1,76 @@
import React from "react"
import { Icons, createIconRender } from "components/Icons"
import { SelectableList } from "components"
import { List } from "antd"
import sidebarItems from "schemas/routes.json"
import "./index.less"
const getStoragedKeys = () => {
return window.app.settings.get("sidebarKeys") ?? []
}
const getAllItems = () => {
const obj = {}
sidebarItems.forEach((item) => {
obj[item.id] = item
})
return obj
}
const allItems = getAllItems()
export default class SidebarItemSelector extends React.Component {
state = {
items: null,
}
componentDidMount = () => {
const source = (this.props.items ?? getStoragedKeys() ?? []).map((key) => {
return { key }
})
this.setState({ items: source })
}
handleDone = (selectedKeys) => {
if (typeof this.props.onDone === "function") {
this.props.onDone(selectedKeys)
}
}
render() {
return (
<div>
<h1>
<Icons.PlusCircle /> Select items to add
</h1>
{this.state.items && (
<SelectableList
itemLayout="vertical"
size="large"
pagination={{
pageSize: 10,
}}
onDone={this.handleDone}
items={this.state.items ?? []}
itemClassName="sidebar_selector_item"
renderItem={(i) => {
const item = allItems[i.key]
return (
<List.Item key={item.title} className="sidebar_selector_item">
{createIconRender(item.icon)}
{item.title ?? item.id}
</List.Item>
)
}}
/>
)}
</div>
)
}
}

View File

@ -0,0 +1,4 @@
.sidebar_selector_item{
height: fit-content;
padding: 0;
}

View File

@ -0,0 +1,381 @@
import React from "react"
import { Layout, Menu, Avatar } from "antd"
import classnames from "classnames"
import config from "config"
import { Icons, createIconRender } from "components/Icons"
import { sidebarKeys as defaultSidebarItems } from "schemas/defaultSettings"
import sidebarItems from "schemas/routes.json"
import { Translation } from "react-i18next"
import { SidebarEditor } from "./components"
import "./index.less"
const { Sider } = Layout
const onClickHandlers = {
settings: (event) => {
window.app.openSettings()
},
}
export default class Sidebar extends React.Component {
constructor(props) {
super(props)
this.controller = window.app["SidebarController"] = {
toggleVisibility: this.toggleVisibility,
toggleEdit: this.toggleEditMode,
toggleElevation: this.toggleElevation,
attachElement: this.attachElement,
isVisible: () => this.state.visible,
isEditMode: () => this.state.visible,
isCollapsed: () => this.state.collapsed,
}
this.state = {
editMode: false,
visible: false,
loading: true,
collapsed: window.app.settings.get("collapseOnLooseFocus") ?? false,
pathResolve: {},
menus: {},
extraItems: {
bottom: [],
top: [],
},
elevated: false,
additionalElements: [],
}
window.app.eventBus.on("edit_sidebar", () => this.toggleEditMode())
window.app.eventBus.on("settingChanged.sidebar_collapse", (value) => {
this.toggleCollapse(value)
})
// handle sidedrawer open/close
window.app.eventBus.on("sidedrawer.hasDrawers", () => {
this.toggleElevation(true)
})
window.app.eventBus.on("sidedrawer.noDrawers", () => {
this.toggleElevation(false)
})
}
collapseDebounce = null
componentDidMount = async () => {
await this.loadSidebarItems()
setTimeout(() => {
this.controller.toggleVisibility(true)
}, 100)
}
getStoragedKeys = () => {
return window.app.settings.get("sidebarKeys")
}
attachElement = (element) => {
this.setState({
additionalElements: [...this.state.additionalElements, element],
})
}
appendItem = (item = {}) => {
const { position } = item
if (typeof position === "undefined" && typeof this.state.extraItems[position] === "undefined") {
console.error("Invalid position")
return false
}
const state = this.state.extraItems
state[position].push(item)
this.setState({ extraItems: state })
}
loadSidebarItems = () => {
const items = {}
const itemsMap = []
// parse all items from schema
sidebarItems.forEach((item, index) => {
items[item.id] = {
...item,
index,
content: (
<>
{createIconRender(item.icon)} {item.title}
</>
),
}
})
// filter undefined to avoid error
let keys = (this.getStoragedKeys() ?? defaultSidebarItems).filter((key) => {
if (typeof items[key] !== "undefined") {
return true
}
})
// short items
keys.forEach((id, index) => {
const item = items[id]
if (item.locked) {
if (item.index !== index) {
keys = keys.move(index, item.index)
//update index
window.app.settings.set("sidebarKeys", keys)
}
}
})
// set items from scoped keys
keys.forEach((key, index) => {
const item = items[key]
try {
// avoid if item is duplicated
if (itemsMap.includes(item)) {
return false
}
let valid = true
if (typeof item.requireState === "object") {
const { key, value } = item.requireState
//* TODO: check global state
}
// end validation
if (!valid) {
return false
}
if (typeof item.path !== "undefined") {
let resolvers = this.state.pathResolve ?? {}
resolvers[item.id] = item.path
this.setState({ pathResolve: resolvers })
}
itemsMap.push(item)
} catch (error) {
return console.log(error)
}
})
// update states
this.setState({ items, menus: itemsMap, loading: false })
}
renderMenuItems(items) {
const handleRenderIcon = (icon) => {
if (typeof icon === "undefined") {
return null
}
return createIconRender(icon)
}
return items.map((item) => {
if (Array.isArray(item.children)) {
return (
<Menu.SubMenu
key={item.id}
icon={handleRenderIcon(item.icon)}
title={<span>
<Translation>
{t => t(item.title)}
</Translation>
</span>}
{...item.props}
>
{this.renderMenuItems(item.children)}
</Menu.SubMenu>
)
}
return (
<Menu.Item key={item.id} icon={handleRenderIcon(item.icon)} {...item.props}>
<Translation>
{t => t(item.title ?? item.id)}
</Translation>
</Menu.Item>
)
})
}
handleClick = (e) => {
if (e.item.props.overrideEvent) {
return app.eventBus.emit(e.item.props.overrideEvent, e.item.props.overrideEventProps)
}
if (typeof e.key === "undefined") {
window.app.eventBus.emit("invalidSidebarKey", e)
return false
}
if (typeof onClickHandlers[e.key] === "function") {
return onClickHandlers[e.key](e)
}
if (typeof this.state.pathResolve[e.key] !== "undefined") {
return window.app.setLocation(`/${this.state.pathResolve[e.key]}`, 150)
}
return window.app.setLocation(`/${e.key}`, 150)
}
toggleEditMode = (to) => {
if (typeof to === "undefined") {
to = !this.state.editMode
}
if (to) {
window.app.eventBus.emit("clearAllOverlays")
} else {
if (this.itemsMap !== this.getStoragedKeys()) {
this.loadSidebarItems()
}
}
this.setState({ editMode: to, collapsed: false })
}
toggleCollapse = (to) => {
if (!this.state.editMode) {
this.setState({ collapsed: to ?? !this.state.collapsed })
}
}
toggleVisibility = (to) => {
this.setState({ visible: to ?? !this.state.visible })
}
toggleElevation = (to) => {
this.setState({ elevated: to ?? !this.state.elevated })
}
onMouseEnter = () => {
if (window.app.settings.is("collapseOnLooseFocus", false)) {
return false
}
clearTimeout(this.collapseDebounce)
this.collapseDebounce = null
if (this.state.collapsed) {
this.toggleCollapse(false)
}
}
handleMouseLeave = () => {
if (window.app.settings.is("collapseOnLooseFocus", false)) {
return false
}
if (!this.state.collapsed) {
this.collapseDebounce = setTimeout(() => { this.toggleCollapse(true) }, window.app.settings.get("autoCollapseDelay") ?? 500)
}
}
renderExtraItems = (position) => {
return this.state.extraItems[position].map((item = {}) => {
if (typeof item.icon !== "undefined") {
if (typeof item.props !== "object") {
item.props = Object()
}
item.props["icon"] = createIconRender(item.icon)
}
return <Menu.Item key={item.id} {...item.props}>{item.children}</Menu.Item>
})
}
render() {
if (this.state.loading) return null
const { user } = this.props
return (
<Sider
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.handleMouseLeave}
theme={this.props.theme}
width={this.state.editMode ? 400 : 200}
collapsed={this.state.editMode ? false : this.state.collapsed}
onCollapse={() => this.props.onCollapse()}
className={
classnames(
"sidebar",
{
["edit_mode"]: this.state.editMode,
["hidden"]: !this.state.visible,
["elevated"]: this.state.elevated
}
)
}
>
<div className="app_sidebar_header">
<div className={classnames("app_sidebar_header_logo", { ["collapsed"]: this.state.collapsed })}>
<img src={this.state.collapsed ? config.logo?.alt : config.logo?.full} />
</div>
</div>
{this.state.editMode && (
<div style={{ height: "100%" }}>
<SidebarEditor />
</div>
)}
{!this.state.editMode && (
<div key="menu" className="app_sidebar_menu">
<Menu selectable={true} mode="inline" theme={this.props.theme} onClick={this.handleClick}>
{this.renderMenuItems(this.state.menus)}
{this.renderExtraItems("top")}
</Menu>
</div>
)}
{!this.state.editMode && <div key="additionalElements" className="additionalElements">
{this.state.additionalElements}
</div>}
{!this.state.editMode && (
<div key="bottom" className="app_sidebar_bottom">
<Menu selectable={false} mode="inline" theme={this.props.theme} onClick={this.handleClick}>
<Menu.Item key="create" icon={<Icons.PlusCircle />} overrideEvent="app.openCreator" >
<Translation>
{(t) => t("Create")}
</Translation>
</Menu.Item>
<Menu.Item key="notifications" icon={<Icons.Bell />}>
<Translation>
{t => t("Notifications")}
</Translation>
</Menu.Item>
<Menu.Item key="settings" icon={<Icons.Settings />}>
<Translation>
{t => t("Settings")}
</Translation>
</Menu.Item>
<Menu.Item key="account">
<div className="user_avatar">
<Avatar shape="square" src={user?.avatar} />
</div>
</Menu.Item>
{this.renderExtraItems("bottom")}
</Menu>
</div>
)}
</Sider>
)
}
}

View File

@ -0,0 +1,160 @@
@import "theme/vars.less";
// SIDEBAR
.ant-layout-sider {
z-index: 50;
background: var(--sidebar-background-color) !important;
background-color: var(--sidebar-background-color) !important;
border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0;
overflow: hidden;
border: 1px solid var(--sidebar-background-color);
transition: all 150ms ease-in-out;
&.hidden {
flex: 0 !important;
min-width: 0 !important;
background-color: transparent !important;
width: 0 !important;
}
&.elevated {
box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.1) !important;
}
}
.ant-menu-item {
color: var(--background-color-contrast);
h1,
h2,
h3,
h4,
h5,
h6,
span,
p {
color: var(--background-color-contrast);
}
}
.ant-menu,
.ant-menu ul {
background: transparent !important;
background-color: transparent !important;
border-right: 0 !important;
}
.sidebar .ant-layout-sider-children {
margin-top: 15px !important;
margin-bottom: 15px !important;
background: transparent !important;
background-color: transparent !important;
user-select: none;
--webkit-user-select: none;
transition: all 150ms ease-in-out;
height: 100%;
display: flex;
flex-direction: column;
&.edit_mode .ant-layout-sider-children {
background: transparent !important;
background-color: transparent !important;
margin-top: 15px !important;
.app_sidebar_menu_wrapper {
opacity: 0;
height: 0;
overflow: hidden;
}
}
}
.app_sidebar_menu_wrapper {
transition: all 450ms ease-in-out;
height: 100%;
width: 100%;
}
.app_sidebar_header {
background: transparent !important;
background-color: transparent !important;
user-select: none;
--webkit-user-select: none;
display: flex;
flex-direction: column;
height: 15%;
margin-top: 5%;
margin-bottom: 5%;
}
.app_sidebar_header_logo {
user-select: none;
--webkit-user-select: none;
display: flex;
align-items: center;
justify-content: center;
img {
user-select: none;
--webkit-user-select: none;
width: 80%;
max-height: 80px;
}
&.collapsed {
img {
max-width: 40px;
}
}
}
.app_sidebar_menu {
background: transparent !important;
background-color: transparent !important;
height: 65%;
overflow: overlay;
overflow-x: hidden;
}
.app_sidebar_bottom {
position: absolute;
bottom: 0;
padding-bottom: 30px;
z-index: 50;
left: 0;
background: transparent !important;
background-color: transparent !important;
backdrop-filter: blur(10px);
--webkit-backdrop-filter: blur(10px);
width: 100%;
height: fit-content;
.ant-menu,
ul {
background: transparent !important;
background-color: transparent !important;
}
}
.user_avatar {
display: flex;
align-items: center;
justify-content: center;
padding: 0 !important;
}

View File

@ -0,0 +1,226 @@
import React from "react"
import classnames from "classnames"
import "./index.less"
export const Sidedrawer = (props) => {
const sidedrawerId = props.id ?? props.key
return <div
key={sidedrawerId}
id={sidedrawerId}
className={
classnames("sidedrawer", {
"hided": !props.defaultVisible,
})
}
>
{
React.createElement(props.children, {
...props.props,
close: props.close,
})
}
</div>
}
export default class SidedrawerController extends React.Component {
state = {
drawers: [],
lockedIds: [],
}
constructor(props) {
super(props)
this.controller = window.app["SidedrawerController"] = {
open: this.open,
close: this.close,
closeAll: this.closeAll,
hasDrawers: this.state.drawers.length > 0,
}
}
componentDidMount = () => {
this.listenEscape()
}
componentDidUpdate() {
this.controller.hasDrawers = this.state.drawers.length > 0
if (this.controller.hasDrawers) {
window.app.eventBus.emit("sidedrawer.hasDrawers")
} else {
window.app.eventBus.emit("sidedrawer.noDrawers")
}
}
componentWillUnmount = () => {
this.unlistenEscape()
}
drawerIsLocked = (id) => {
return this.state.lockedIds.includes(id)
}
lockDrawerId = (id) => {
this.setState({
lockedIds: [...this.state.lockedIds, id],
})
}
unlockDrawer = (id) => {
this.setState({
lockedIds: this.state.lockedIds.filter(lockedId => lockedId !== id),
})
}
open = async (id, component, options = {}) => {
if (typeof id !== "string") {
options = component
component = id
id = component.key ?? component.id ?? Math.random().toString(36).substr(2, 9)
}
let drawers = this.state.drawers
// check if id is already in use
// but only if its allowed to be used multiple times
const existentDrawer = drawers.find((drawer) => drawer.props.id === id)
if (existentDrawer) {
if (!existentDrawer.props.allowMultiples) {
console.warn(`Sidedrawer with id "${id}" already exists.`)
return false
}
// fix id appending the corresponding array index at the end of the id
// ex ["A", "B", "C"] => ["A", "B", "C", "A-1"]
// to prevent id collisions
let index = 0
let newId = id
while (drawers.find(drawer => drawer.props.id === newId)) {
index++
newId = id + "-" + index
}
id = newId
}
drawers.push(React.createElement(
Sidedrawer,
{
key: id,
id: id,
allowMultiples: options.allowMultiples ?? false,
...options.props,
close: this.close,
escClosable: options.escClosable ?? true,
defaultVisible: false,
selfLock: () => {
this.lockDrawerId(id)
},
selfUnlock: () => {
this.unlockDrawer(id)
}
},
component
))
if (options.lock) {
this.lockDrawerId(id)
}
await this.setState({ drawers })
setTimeout(() => {
this.toggleDrawerVisibility(id, true)
}, 10)
window.app.eventBus.emit("sidedrawer.open")
}
toggleDrawerVisibility = (id, to) => {
const drawer = document.getElementById(id)
const drawerClasses = drawer.classList
if (to) {
drawerClasses.remove("hided")
} else {
drawerClasses.add("hided")
}
}
close = (id) => {
// if an id is provided filter by key
// else close the last opened drawer
let drawers = this.state.drawers
let drawerId = id ?? drawers[drawers.length - 1].props.id
// check if id is locked
if (this.drawerIsLocked(id)) {
console.warn(`Sidedrawer with id "${id}" is locked.`)
return false
}
// check if id exists
const drawer = drawers.find(drawer => drawer.props.id === drawerId)
if (!drawer) {
console.warn(`Sidedrawer with id "${id}" does not exist.`)
return false
}
// emit event
window.app.eventBus.emit("sidedrawer.close")
// toogleVisibility off
this.toggleDrawerVisibility(drawerId, false)
// await drawer transition
setTimeout(() => {
// remove drawer
drawers = drawers.filter(drawer => drawer.props.id !== drawerId)
this.setState({ drawers })
}, 500)
}
listenEscape = () => {
document.addEventListener("keydown", this.handleEscKeyPress)
}
unlistenEscape = () => {
document.removeEventListener("keydown", this.handleEscKeyPress)
}
handleEscKeyPress = (event) => {
// avoid handle keypress when is nothing to render
if (this.state.drawers.length === 0) {
return false
}
let isEscape = false
if ("key" in event) {
isEscape = event.key === "Escape" || event.key === "Esc"
} else {
isEscape = event.keyCode === 27
}
if (isEscape) {
// close the last opened drawer
this.close()
}
}
render() {
return <div
className="sidedrawers-wrapper"
>
{this.state.drawers}
</div>
}
}

View File

@ -0,0 +1,44 @@
@import "theme/vars.less";
.sidedrawers-wrapper {
display: flex;
flex-direction: row;
> div {
transform: translate(-50px, 0);
}
.sidedrawer {
position: relative;
width: 30vw; // by default
height: 100vh;
min-width: 700px;
z-index: 20;
background-color: var(--sidedrawer-background-color);
border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0;
padding: 20px;
padding-left: 70px;
transform: translate(-50px, 0);
overflow-x: hidden;
overflow-y: overlay;
word-break: break-all;
transition: all 150ms ease-in-out;
// create shadow on the right side
box-shadow : 0 0 5px 4px rgba(0, 0, 0, 0.1) !important;
&.hided {
width: 0;
min-width: 0;
padding: 0;
}
}
}

View File

@ -0,0 +1,134 @@
import React from "react"
import * as antd from "antd"
import { FormGenerator } from "components"
import { Icons } from "components/Icons"
import config from "config"
import "./index.less"
const formInstance = [
{
id: "username",
element: {
component: "Input",
icon: "User",
placeholder: "Username",
props: {
autoCorrect: "off",
autoCapitalize: "none",
className: "login-form-username",
},
},
item: {
hasFeedback: true,
rules: [
{
required: true,
message: 'Please input your Username!',
},
],
}
},
{
id: "password",
element: {
component: "Input",
icon: "Lock",
placeholder: "Password",
props: {
type: "password"
}
},
item: {
hasFeedback: true,
rules: [
{
required: true,
message: 'Please input your Password!',
},
],
}
},
{
id: "login_btn",
withValidation: true,
element: {
component: "Button",
props: {
icon: "User",
children: "Login",
type: "primary",
htmlType: "submit"
}
}
},
]
export default class Login extends React.Component {
static pageStatement = {
bottomBarAllowed: false
}
handleFinish = async (values, ctx) => {
ctx.toogleValidation(true)
const payload = {
username: values.username,
password: values.password,
allowRegenerate: values.allowRegenerate,
}
this.props.sessionController.login(payload, (error, response) => {
ctx.toogleValidation(false)
ctx.clearErrors()
if (error) {
ctx.shake("all")
return ctx.error("result", error)
} else {
if (response.status === 200) {
this.onDone()
}
}
})
}
onDone = () => {
if (typeof this.props.onDone === "function") {
this.props.onDone()
}
if (typeof this.props.close === "function") {
this.props.close()
}
}
render() {
return <div className="login">
<div className="header">
<div className="logo">
<img src={config.logo?.full} />
</div>
</div>
{this.props.session && <div className="session_available">
<h3><Icons.AlertCircle /> You already have a valid session.</h3>
<div className="session_card">
@{this.props.session.username}
</div>
<antd.Button
type="primary"
onClick={() => window.app.setLocation(config.app?.mainPath ?? "/home")} >
Go to home
</antd.Button>
</div>}
<FormGenerator
name="normal_login"
renderLoadingIcon
className="login-form"
items={formInstance}
onFinish={this.handleFinish}
/>
</div>
}
}

View File

@ -0,0 +1,63 @@
.login {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
> div {
margin-bottom: 20px;
}
.header {
.logo {
width: 15vw;
img {
width: 100%;
height: 100%;
}
}
}
}
.login-form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.login-form-username {
font-size: 20px!important;
input {
padding: 20px!important;
font-size: 20px!important;
}
}
.session_available {
width: fit-content;
height: fit-content;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid var(--border-color);
border-radius: 8px;
.session_card {
width: fit-content;
height: fit-content;
margin: 10px;
padding: 5px 10px;
border: 1px solid #e5e5e5;
border-radius: 8px;
}
}

View File

@ -0,0 +1,76 @@
import React from "react"
import * as antd from "antd"
import { Icons, createIconRender } from "components/Icons"
export default (props) => {
const [loading, setLoading] = React.useState(false)
const [options, setOptions] = React.useState([])
const [value, setValue] = React.useState(null)
const onChangeProperties = async (update) => {
if (props.eventDisable) {
return false
}
setLoading(true)
await props.onChangeProperties(update)
.then((data) => {
return setValue(update.join("-"))
})
.catch((error) => {
return
})
setLoading(false)
}
const getTagColor = () => {
if (props.colors) {
return props.colors[value]
}
return "default"
}
const handleOptionsLoad = async (fn) => {
setLoading(true)
const result = await fn()
setOptions(result)
setLoading(false)
}
const handleDefaultValueLoad = async (fn) => {
const result = await fn()
setValue(result)
}
React.useEffect(() => {
if (typeof props.options === "function") {
handleOptionsLoad(props.options)
} else {
setOptions(props.options)
}
if (typeof props.defaultValue === "function") {
handleDefaultValueLoad(props.defaultValue)
} else {
setValue(props.defaultValue)
}
}, [])
return <antd.Cascader options={options} onChange={(update) => onChangeProperties(update)} >
<antd.Tag color={getTagColor()}>
{loading ? <Icons.LoadingOutlined spin /> :
<>
{Icons[props.icon] && createIconRender(props.icon)}
<h4>
{value}
</h4>
</>
}
</antd.Tag>
</antd.Cascader>
}

View File

@ -0,0 +1,12 @@
import React from "react"
import { Result } from "antd"
export default () => {
return (
<Result
status="404"
title="404"
subTitle="Sorry, the page you visited does not exist."
/>
)
}

View File

@ -0,0 +1,91 @@
import React from "react"
import * as antd from "antd"
import { decycle } from "@corenode/utils"
import { Icons } from "components/Icons"
function parseTreeData(data, backKey) {
const keys = Object.keys(data)
let result = Array()
keys.forEach((key) => {
const value = data[key]
const valueType = typeof value
const obj = Object()
obj.key = backKey ? `${backKey}-${key}` : key
obj.title = key
obj.type = valueType
if (valueType === "object") {
obj.children = parseTreeData(value)
} else {
obj.children = [
{
key: `${obj.key}-value`,
title: "value",
icon: <Icons.Box />,
children: [
{
key: `${obj.key}-value-indicator`,
title: String(value),
icon: <Icons.Box />,
},
],
},
{
key: `${obj.key}-type`,
title: "type",
children: [
{
key: `${obj.key}-type-indicator`,
title: valueType,
},
],
},
]
}
result.push(obj)
})
return result
}
export default class ObjectInspector extends React.Component {
state = {
data: null,
expandedKeys: [],
autoExpandParent: true,
}
componentDidMount() {
const raw = decycle(this.props.data)
const data = parseTreeData(raw)
this.setState({ raw, data })
}
onExpand = (expandedKeys) => {
this.setState({
expandedKeys,
autoExpandParent: false,
})
}
render() {
const { expandedKeys, autoExpandParent } = this.state
return (
<div>
<antd.Tree
//showLine
switcherIcon={<Icons.DownOutlined />}
onExpand={this.onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={this.state.data}
/>
</div>
)
}
}

View File

@ -0,0 +1,73 @@
import React from "react"
import { Result, Button, Typography } from "antd"
import { CloseCircleOutlined } from "@ant-design/icons"
import config from "config"
import "./index.less"
const { Paragraph, Text } = Typography
const ErrorEntry = (props) => {
const { error } = props
if (!error) {
return <div className="error">
<CloseCircleOutlined />
Unhandled error
</div>
}
return <div className="error">
<CloseCircleOutlined />
{error.info.toString()}
</div>
}
export default (props) => {
let errors = []
if (Array.isArray(props.error)) {
errors = props.error
} else {
errors.push(props.error)
}
const onClickGoMain = () => {
window.app.setLocation(config.app.mainPath ?? "/main")
}
const onClickReload = () => {
window.location.reload()
}
return (
<div className="app_render_error">
<Result
status="error"
title="Render Error"
subTitle="It seems that the application is having problems displaying this page, we have detected some unrecoverable errors due to a bug. (This error should be automatically reported to the developers to find a solution as soon as possible)"
extra={[
<Button type="primary" key="gomain" onClick={onClickGoMain}>
Go Main
</Button>,
<Button key="reload" onClick={onClickReload}>Reload</Button>,
]}
>
<Paragraph>
<Text
strong
style={{
fontSize: 16,
}}
>
We catch the following errors:
</Text>
<div className="errors">
{errors.map((error, index) => {
return <ErrorEntry key={index} error={error} />
})}
</div>
</Paragraph>
</Result>
</div>
)
}

View File

@ -0,0 +1,16 @@
.errors {
margin-top: 12px;
.error {
margin-bottom: 10px;
svg {
color: red;
margin-right: 10px;
}
.stack {
margin-left: 24px;
word-wrap: break-word;
}
}
}

View File

@ -0,0 +1,198 @@
import React from "react"
import ReactDOM from "react-dom"
import { Rnd } from "react-rnd"
import { Icons } from "components/Icons"
import "./index.less"
class DOMWindow {
constructor(props) {
this.props = { ...props }
this.id = this.props.id
this.key = 0
this.root = document.getElementById("app_windows")
this.element = document.getElementById(this.id)
// handle root container
if (!this.root) {
this.root = document.createElement("div")
this.root.setAttribute("id", "app_windows")
document.body.append(this.root)
}
// get all windows opened has container
const rootNodes = this.root.childNodes
// ensure this window has last key from rootNode
if (rootNodes.length > 0) {
const lastChild = rootNodes[rootNodes.length - 1]
const lastChildKey = Number(lastChild.getAttribute("key"))
this.key = lastChildKey + 1
}
this.element = document.createElement("div")
this.element.setAttribute("id", this.id)
this.element.setAttribute("key", this.key)
this.element.setAttribute("classname", this.props.className)
this.root.appendChild(this.element)
}
render = (fragment) => {
ReactDOM.render(
fragment,
this.element,
)
return this
}
create = () => {
// set render
this.render(<WindowRender {...this.props} id={this.id} key={this.key} destroy={this.destroy} />)
return this
}
destroy = () => {
this.element.remove()
return this
}
}
class WindowRender extends React.Component {
state = {
actions: [],
dimensions: {
height: this.props.height ?? 600,
width: this.props.width ?? 400,
},
position: this.props.defaultPosition,
visible: false,
}
componentDidMount = () => {
this.setDefaultActions()
if (typeof this.props.actions !== "undefined") {
if (Array.isArray(this.props.actions)) {
const actions = this.state.actions ?? []
this.props.actions.forEach((action) => {
actions.push(action)
})
this.setState({ actions })
}
}
if (!this.state.position) {
this.setState({ position: this.getCenterPosition() })
}
this.toogleVisibility(true)
}
toogleVisibility = (to) => {
this.setState({ visible: to ?? !this.state.visible })
}
getCenterPosition = () => {
const dimensions = this.state?.dimensions ?? {}
const windowHeight = dimensions.height ?? 600
const windowWidth = dimensions.width ?? 400
return {
x: window.innerWidth / 2 - windowWidth / 2,
y: window.innerHeight / 2 - windowHeight / 2,
}
}
setDefaultActions = () => {
const { actions } = this.state
actions.push({
key: "close",
render: () => <Icons.XCircle style={{ margin: 0, padding: 0 }} />,
onClick: () => {
this.props.destroy()
},
})
this.setState({ actions })
}
renderActions = () => {
const actions = this.state.actions
if (Array.isArray(actions)) {
return actions.map((action) => {
return (
<div key={action.key} onClick={action.onClick} {...action.props}>
{React.isValidElement(action.render) ? action.render : React.createElement(action.render)}
</div>
)
})
}
return null
}
getComponentRender = () => {
return React.isValidElement(this.props.children)
? React.cloneElement(this.props.children, this.props.renderProps)
: React.createElement(this.props.children, this.props.renderProps)
}
render() {
const { position, dimensions, visible } = this.state
if (!visible) {
return null
}
return (
<Rnd
default={{
...position,
...dimensions,
}}
onResize={(e, direction, ref, delta, position) => {
this.setState({
dimensions: {
width: ref.offsetWidth,
height: ref.offsetHeight,
},
position,
})
}}
dragHandleClassName="window_topbar"
minWidth={this.props.minWidth ?? "300px"}
minHeight={this.props.minHeight ?? "200px"}
>
<div
style={{
height: dimensions.height,
width: dimensions.width,
}}
className="window_wrapper"
>
<div className="window_topbar">
<div className="title">{this.props.id}</div>
<div className="actions">{this.renderActions()}</div>
</div>
<div className="window_body">{this.getComponentRender()}</div>
</div>
</Rnd>
)
}
}
export { DOMWindow, WindowRender }

View File

@ -0,0 +1,93 @@
@wrapper_background: rgba(255, 255, 255, 1);
@topbar_height: 30px;
@topbar_background: rgba(0, 0, 0, 0.4);
.window_wrapper {
border-radius: 12px;
background-color: @wrapper_background;
border: 1px solid rgba(161, 133, 133, 0.2);
overflow: hidden;
&.translucid {
border: unset;
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
--webkit-backdrop-filter: blur(10px);
filter: drop-shadow(8px 8px 10px rgba(0, 0, 0, 0.5));
}
}
.window_topbar {
position: sticky;
z-index: 51;
background-color: @topbar_background;
height: @topbar_height;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
> div {
margin: 0 5px;
line-height: 0;
}
.title {
margin-left: 20px;
color: #fff - @topbar_background;
font-size: 13px;
font-style: italic;
font-family: "JetBrains Mono", monospace;
}
.actions {
display: flex;
flex-direction: row-reverse;
align-items: center;
justify-content: end;
color: #fff - @topbar_background;
> div {
transition: all 150ms ease-in-out;
margin-right: 10px;
cursor: pointer;
height: fit-content;
width: fit-content;
line-height: 0;
display: flex;
align-items: center;
justify-content: end;
}
> div:hover {
color: var(--primary-color);
}
}
}
.window_body {
z-index: 50;
padding: 10px 20px;
height: calc(100% - @topbar_height);
width: 100%;
overflow: overlay;
user-select: text !important;
--webkit-user-select: text !important;
> div {
user-select: text !important;
--webkit-user-select: text !important;
}
}

View File

@ -0,0 +1,360 @@
import React from "react"
import * as antd from "antd"
import { Button } from "antd"
import classnames from "classnames"
import _ from "lodash"
import { Translation } from "react-i18next"
import { Icons, createIconRender } from "components/Icons"
import { ActionsBar, } from "components"
import { useLongPress, Haptics } from "utils"
import "./index.less"
const ListItem = React.memo((props) => {
let { item } = props
if (!item.key) {
item.key = item._id ?? item.id
}
const doubleClickSpeed = 400
let delayedClick = null
let clickedOnce = null
const handleOnceClick = () => {
clickedOnce = null
if (typeof props.onClickItem === "function") {
return props.onClickItem(item.key)
}
}
const handleDoubleClick = () => {
if (typeof props.onDoubleClickItem === "function") {
return props.onDoubleClickItem(item.key)
}
}
const handleLongPress = () => {
if (typeof props.onLongPressItem === "function") {
return props.onLongPressItem(item.key)
}
}
const renderChildren = props.renderChildren(item)
const isDisabled = renderChildren.props.disabled
return React.createElement("div", {
id: item.key,
key: item.key,
disabled: isDisabled,
className: classnames("selectableList_item", {
["selected"]: props.selected,
["disabled"]: isDisabled,
}),
onDoubleClick: () => {
if (isDisabled) {
return false
}
handleDoubleClick()
},
...useLongPress(
// onLongPress
() => {
if (isDisabled) {
return false
}
if (props.onlyClickSelection) {
return false
}
handleLongPress()
},
// onClick
() => {
if (isDisabled) {
return false
}
if (props.onlyClickSelection) {
return handleOnceClick()
}
if (!delayedClick) {
delayedClick = _.debounce(handleOnceClick, doubleClickSpeed)
}
if (clickedOnce) {
delayedClick.cancel()
clickedOnce = false
handleDoubleClick()
} else {
clickedOnce = true
delayedClick()
}
},
{
shouldPreventDefault: true,
delay: props.longPressDelay ?? 300,
}
),
}, renderChildren)
})
export default class SelectableList extends React.Component {
state = {
selectedKeys: [],
selectionEnabled: false,
}
componentDidMount() {
if (typeof this.props.defaultSelected !== "undefined" && Array.isArray(this.props.defaultSelected)) {
this.setState({
selectedKeys: [...this.props.defaultSelected],
})
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.selectionEnabled !== this.state.selectionEnabled) {
if (this.state.selectionEnabled) {
this.handleFeedbackEvent("selectionStart")
} else {
this.handleFeedbackEvent("selectionEnd")
}
}
}
handleFeedbackEvent = (event) => {
if (typeof Haptics[event] === "function") {
return Haptics[event]()
}
}
isKeySelected = (key) => {
return this.state.selectedKeys.includes(key)
}
isAllSelected = () => {
return this.state.selectedKeys.length === this.props.items.length
}
selectAll = () => {
if (this.props.items.length > 0) {
let updatedSelectedKeys = [...this.props.items.map((item) => item.key ?? item.id ?? item._id)]
if (typeof this.props.disabledKeys !== "undefined") {
updatedSelectedKeys = updatedSelectedKeys.filter((key) => {
return !this.props.disabledKeys.includes(key)
})
}
this.handleFeedbackEvent("selectionChanged")
this.setState({
selectionEnabled: true,
selectedKeys: updatedSelectedKeys,
})
}
}
unselectAll = () => {
this.setState({
selectionEnabled: false,
selectedKeys: [],
})
}
selectKey = (key) => {
let list = this.state.selectedKeys ?? []
list.push(key)
this.handleFeedbackEvent("selectionChanged")
return this.setState({ selectedKeys: list })
}
unselectKey = (key) => {
let list = this.state.selectedKeys ?? []
list = list.filter((_key) => key !== _key)
this.handleFeedbackEvent("selectionChanged")
return this.setState({ selectedKeys: list })
}
onDone = () => {
if (typeof this.props.onDone === "function") {
this.props.onDone(this.state.selectedKeys)
}
this.unselectAll()
}
onDiscard = () => {
if (typeof this.props.onDiscard === "function") {
this.props.onDiscard(this.state.selectedKeys)
}
this.unselectAll()
}
onDoubleClickItem = (key) => {
if (typeof this.props.onDoubleClick === "function") {
this.props.onDoubleClick(key)
}
}
onClickItem = (key) => {
if (this.props.overrideSelectionEnabled || this.state.selectionEnabled) {
if (this.isKeySelected(key)) {
this.unselectKey(key)
} else {
this.selectKey(key)
}
} else {
if (typeof this.props.onClickItem === "function") {
this.props.onClickItem(key)
}
}
}
onLongPressItem = (key) => {
if (this.props.overrideSelectionEnabled) {
return false
}
if (!this.state.selectionEnabled) {
this.selectKey(key)
this.setState({ selectionEnabled: true })
}
}
renderProvidedActions = () => {
return this.props.actions.map((action) => {
return (
<div key={action.key}>
<Button
type={action.props.type}
shape={action.props.shape}
size={action.props.size}
style={{
...action.props.style,
}}
onClick={() => {
if (typeof this.props.events === "undefined") {
console.error("No events provided to SelectableList")
return false
}
if (typeof action.onClick === "function") {
action.onClick(this.state.selectedKeys)
}
if (typeof this.props.events[action.props.call] === "function") {
this.props.events[action.props.call]({
onDone: this.onDone,
onDiscard: this.onDiscard,
onCancel: this.onCancel,
selectKey: this.selectKey,
unselectKey: this.unselectKey,
selectAll: this.selectAll,
unselectAll: this.unselectAll,
isKeySelected: this.isKeySelected,
isAllSelected: this.isAllSelected,
}, this.state.selectedKeys)
}
}}
>
{action}
</Button>
</div>
)
})
}
getLongPressDelay = () => {
return window.app.settings.get("selection_longPress_timeout")
}
renderItems = (data) => {
return data.length > 0 ? data.map((item, index) => {
item.key = item.key ?? item.id ?? item._id
if (item.children && Array.isArray(item.children)) {
return <div className="selectableList_group">
<h1>
{React.isValidElement(item.icon) ? item.icon : Icons[item.icon] && createIconRender(item.icon)}
<Translation>
{t => t(item.label)}
</Translation>
</h1>
<div className="selectableList_subItems">
{this.renderItems(item.children)}
</div>
</div>
}
let selected = this.isKeySelected(item.key)
return <ListItem
item={item}
selected={selected}
longPressDelay={this.getLongPressDelay()}
onClickItem={this.onClickItem}
onDoubleClickItem={this.onDoubleClickItem}
onLongPressItem={this.onLongPressItem}
renderChildren={this.props.renderItem}
onlyClickSelection={this.props.onlyClickSelection || this.state.selectionEnabled}
/>
}) : <antd.Empty image={antd.Empty.PRESENTED_IMAGE_SIMPLE} />
}
render() {
if (!this.props.overrideSelectionEnabled && this.state.selectionEnabled && this.state.selectedKeys.length === 0) {
this.setState({ selectionEnabled: false })
this.unselectAll()
}
const isAllSelected = this.isAllSelected()
let items = this.renderItems(this.props.items)
return <div className={classnames("selectableList", { ["selectionEnabled"]: this.props.overrideSelectionEnabled ?? this.state.selectionEnabled })}>
<div className="selectableList_content">
{items}
</div>
{this.props.items.length > 0 && (this.props.overrideSelectionEnabled || this.state.selectionEnabled) && !this.props.actionsDisabled &&
<ActionsBar mode="float">
<div key="discard">
<Button
shape="round"
onClick={this.onDiscard}
{...this.props.onDiscardProps}
>
{this.props.onDiscardRender ?? <Icons.X />}
<Translation>
{(t) => t("Discard")}
</Translation>
</Button>
</div>
{this.props.bulkSelectionAction &&
<div key="allSelection">
<Button
shape="round"
onClick={() => isAllSelected ? this.unselectAll() : this.selectAll()}
>
<Translation>
{(t) => t(isAllSelected ? "Unselect all" : "Select all")}
</Translation>
</Button>
</div>}
{Array.isArray(this.props.actions) && this.renderProvidedActions()}
</ActionsBar>
}
</div>
}
}

View File

@ -0,0 +1,82 @@
@selectableList_item_borderColor_active: rgba(51, 51, 51, 1);
@selectableList_item_borderColor_normal: rgba(51, 51, 51, 0.3);
.selectableList {
.selectableList_content {
.selectableList_item {
--ignore-dragger: true;
display: inline-flex;
overflow-x: overlay;
align-items: center;
user-select: none;
--webkit-user-select: none;
width: 100%;
height: fit-content;
border: @selectableList_item_borderColor_normal 1px solid;
border-radius: 4px;
margin-bottom: 6px;
padding: 7px;
transition: all 150ms ease-in-out;
&.selected {
background-color: #f5f5f5;
transform: scale(0.98);
margin-bottom: 3px;
}
&.disabled {
opacity: 0.5;
pointer-events: none;
}
::-webkit-scrollbar {
position: absolute;
display: none;
width: 0;
height: 0;
z-index: 0;
}
}
.selectableList_item:active {
background-color: #f5f5f5;
transform: scale(0.98);
margin-bottom: 3px;
}
}
&.selectionEnabled {
.selectableList_content {
.selectableList_item {
cursor: pointer;
border: rgba(51, 51, 51, 0.3) 1px solid;
border-radius: 8px;
margin-bottom: 12px;
h1, h3 {
user-select: none;
--webkit-user-select: none;
}
}
}
}
}
.selectableList_group {
display: flex;
flex-direction: column;
.selectableList_subItems {
margin-left: 10px;
}
margin-bottom: 10px;
}

View File

@ -0,0 +1,244 @@
import React from "react"
import * as antd from "antd"
import loadable from "@loadable/component"
import { Translation } from "react-i18next"
import { Icons, createIconRender } from "components/Icons"
import { ActionsBar } from "components"
import "./index.less"
export default class StepsForm extends React.Component {
state = {
steps: [...(this.props.steps ?? []), ...(this.props.children ?? [])],
step: 0,
values: {},
canNext: true,
renderStep: null,
}
api = window.app.api.withEndpoints("main")
componentDidMount = async () => {
if (this.props.defaultValues) {
await this.setState({ values: this.props.defaultValues })
}
await this.handleNext(0)
}
next = (to) => {
if (!this.state.canNext) {
return antd.message.error("Please complete the step.")
}
return this.handleNext(to)
}
prev = () => this.handlePrev()
handleNext = (to) => {
const index = to ?? (this.state.step + 1)
this.setState({ step: index, renderStep: this.renderStep(index) })
}
handlePrev = () => {
this.handleNext(this.state.step - 1)
}
handleError = (error) => {
this.setState({ submitting: false, submittingError: error })
}
handleUpdate = (key, value) => {
this.setState({ values: { ...this.state.values, [key]: value } }, () => {
if (typeof this.props.onChange === "function") {
this.props.onChange(this.state.values)
}
})
}
handleValidation = (result) => {
this.setState({ canNext: result })
}
canSubmit = () => {
if (typeof this.props.canSubmit === "function") {
return this.props.canSubmit(this.state.values)
}
return true
}
onSubmit = async () => {
if (!this.state.canNext) {
console.warn("Cannot submit form, validation failed")
return false
}
if (typeof this.props.onSubmit === "function") {
this.setState({ submitting: true, submittingError: null })
await this.props.onSubmit(this.state.values).catch((error) => {
console.error(error)
this.handleError(error)
})
}
}
renderStep = (stepIndex) => {
const step = this.state.steps[stepIndex]
let content = step.content
let value = this.state.values[step.key]
if (typeof step.key === "undefined") {
console.error("[StepsForm] step.key is required")
return null
}
if (typeof step.required !== "undefined" && step.required) {
this.handleValidation(Boolean(value && value.length > 0))
} else {
this.setState({ canNext: true })
}
if (typeof step.stateValidation === "function") {
const validationResult = step.stateValidation(value)
this.handleValidation(validationResult)
}
const componentProps = {
handleUpdate: (to) => {
value = to
if (typeof step.onUpdateValue === "function") {
value = step.onUpdateValue(value, to)
}
let validationResult = true
if (typeof step.stateValidation === "function") {
validationResult = step.stateValidation(to)
}
if (typeof step.required !== "undefined" && step.required) {
validationResult = Boolean(to && to.length > 0)
}
this.handleUpdate(step.key, to)
this.handleValidation(validationResult)
},
handleError: (error) => {
if (typeof props.handleError === "function") {
this.handleError(error)
}
},
onPressEnter: () => this.next(),
value: value,
}
if (typeof step.content === "function") {
content = loadable(async () => {
try {
const component = React.createElement(step.content, componentProps)
return () => component
} catch (error) {
console.log(error)
antd.notification.error({
message: "Error",
description: "Error loading step content",
})
return () => <div>
<Icons.XCircle /> Error
</div>
}
}, {
fallback: <div>Loading...</div>,
})
}
return React.createElement(React.memo(content), componentProps)
}
render() {
if (this.state.steps.length === 0) {
return null
}
const steps = this.state.steps
const current = steps[this.state.step]
return (
<div className="steps_form">
<div className="steps_form steps">
<antd.Steps responsive={false} direction="horizontal" className="steps_form steps header" size="small" current={this.state.step}>
{steps.map(item => (
<antd.Steps.Step key={item.title} />
))}
</antd.Steps>
<div className="steps_form steps step">
<div className="title">
<h1>{current.icon && createIconRender(current.icon)}
<Translation>
{t => t(current.title)}
</Translation>
</h1>
<antd.Tag color={current.required ? "volcano" : "default"}>
<Translation>
{t => t(current.required ? "Required" : "Optional")}
</Translation>
</antd.Tag>
</div>
{current.description && <div className="description">
<Translation>
{t => t(current.description)}
</Translation>
</div>}
{this.state.renderStep}
</div>
</div>
{this.state.submittingError && (
<div style={{ color: "#f5222d" }}>
<Translation>
{t => t(String(this.state.submittingError))}
</Translation>
</div>
)}
<ActionsBar mode="float">
{this.state.step > 0 && (
<antd.Button style={{ margin: "0 8px" }} onClick={() => this.prev()}>
<Icons.ChevronLeft />
<Translation>
{t => t("Previous")}
</Translation>
</antd.Button>
)}
{this.state.step < steps.length - 1 && (
<antd.Button disabled={!this.state.canNext} type="primary" onClick={() => this.next()}>
<Icons.ChevronRight />
<Translation>
{t => t("Next")}
</Translation>
</antd.Button>
)}
{this.state.step === steps.length - 1 && (
<antd.Button disabled={!this.state.canNext || this.state.submitting || !this.canSubmit()} type="primary" onClick={this.onSubmit}>
{this.state.submitting && <Icons.LoadingOutlined spin />}
<Translation>
{t => t("Done")}
</Translation>
</antd.Button>
)}
</ActionsBar>
</div>
)
}
}

View File

@ -0,0 +1,91 @@
.steps_form {
.ant-steps-icon {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
svg {
margin: 0;
}
}
.steps {
display: flex;
flex-direction: column;
width: 100%;
.header {
//position: fixed;
width: 100%;
height: fit-content;
flex-direction: row;
}
.ant-select {
width: 100%;
}
.step {
padding: 0 10px;
display: inline-flex;
flex-direction: column;
width: 100%;
height: 100%;
align-items: flex-start;
h1 {
margin: 0;
}
.title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
}
.description {
color: var(--background-color-contrast);
}
.content {
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
.ant-list {
width: 100%;
}
}
.actions {
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: center;
> div {
margin-right: 10px;
}
}
> div {
margin-bottom: 0;
}
}
> div {
margin-bottom: 20px;
}
}
}

View File

@ -0,0 +1,122 @@
import React from "react"
import * as antd from "antd"
import { Translation } from "react-i18next"
import { SelectableList, Skeleton } from "components"
import { debounce } from "lodash"
import fuse from "fuse.js"
import "./index.less"
export default class UserSelector extends React.Component {
state = {
loading: true,
data: [],
searchValue: null,
}
api = window.app.api.withEndpoints("main")
componentDidMount = async () => {
this.toogleLoading(true)
await this.fetchUsers()
}
toogleLoading = (to) => {
this.setState({ loading: to ?? !this.state.loading })
}
fetchUsers = async () => {
const data = await this.api.get.users(undefined, { select: this.props.select }).catch((err) => {
console.error(err)
antd.message.error("Error fetching operators")
})
this.setState({ data: data, loading: false })
}
isExcludedId = (id) => {
if (this.props.excludedIds) {
return this.props.excludedIds.includes(id)
}
return false
}
renderItem = (item) => {
return <div disabled={this.isExcludedId(item._id)} className="user" >
<div><antd.Avatar shape="square" src={item.avatar} /></div>
<div><h1>{item.fullName ?? item.username}</h1></div>
</div>
}
search = (value) => {
if (typeof value !== "string") {
if (typeof value.target?.value === "string") {
value = value.target.value
}
}
if (value === "") {
return this.setState({ searchValue: null })
}
const searcher = new fuse(this.state.data, {
includeScore: true,
keys: ["username", "fullName"],
})
const result = searcher.search(value)
this.setState({
searchValue: result.map((entry) => {
return entry.item
}),
})
}
debouncedSearch = debounce((value) => this.search(value), 500)
onSearch = (event) => {
if (event === "" && this.state.searchValue) {
return this.setState({ searchValue: null })
}
this.debouncedSearch(event.target.value)
}
render() {
if (this.state.loading) {
return <Skeleton />
}
return <div className="users_selector">
<div className="users_selector header">
<div>
<antd.Input.Search
placeholder="Search"
allowClear
onSearch={this.onSearch}
onChange={this.onSearch}
/>
</div>
</div>
<SelectableList
onlyClickSelection
overrideSelectionEnabled
bulkSelectionAction
items={this.state.searchValue ?? this.state.data}
renderItem={this.renderItem}
actions={[
<div type="primary" call="onDone" key="done">
<Translation>
{t => t("Done")}
</Translation>
</div>
]}
events={{
onDone: (ctx, keys) => this.props.handleDone(keys),
}}
/>
</div>
}
}

View File

@ -0,0 +1,19 @@
.users_selector {
.header {
margin-bottom: 10px;
}
.user {
display: flex;
flex-direction: row;
align-items: center;
h1 {
margin: 0;
}
> div {
margin-right: 8px;
}
}
}

View File

@ -0,0 +1,17 @@
import * as Layout from "./Layout"
export { default as Footer } from "./Footer"
// COMPLEX COMPONENTS
export { default as FormGenerator } from "./FormGenerator"
export { default as NotFound } from "./NotFound"
export { default as RenderError } from "./RenderError"
export { default as SelectableList } from "./SelectableList"
export { default as ObjectInspector } from "./ObjectInspector"
export { default as ModifierTag } from "./ModifierTag"
export { default as StepsForm } from "./StepsForm"
export * as Crash from "./Crash"
export { default as Login } from "./Login"
export * as Window from "./RenderWindow"
export { Layout }

View File

@ -0,0 +1,262 @@
import Core from "evite/src/core"
import config from "config"
import { Bridge } from "linebridge/dist/client"
import { Session } from "models"
export default class ApiCore extends Core {
constructor(props) {
super(props)
this.namespaces = Object()
this.onExpiredExceptionEvent = false
this.excludedExpiredExceptionURL = ["/regenerate_session_token"]
this.ctx.registerPublicMethod("api", this)
}
async customRequest(
namepace = undefined,
payload = {
method: "GET",
},
...args
) {
if (typeof namepace === "undefined") {
throw new Error("Namespace must be defined")
}
if (typeof this.namespaces[namepace] === "undefined") {
throw new Error("Namespace not found")
}
if (typeof payload === "string") {
payload = {
url: payload,
}
}
if (typeof payload.headers !== "object") {
payload.headers = {}
}
const sessionToken = await Session.token
if (sessionToken) {
payload.headers["Authorization"] = `Bearer ${sessionToken}`
} else {
console.warn("Making a request with no session token")
}
return await this.namespaces[namepace].httpInterface(payload, ...args)
}
request = (namespace = "main", method, endpoint, ...args) => {
if (!this.namespaces[namespace]) {
throw new Error(`Namespace ${namespace} not found`)
}
if (!this.namespaces[namespace].endpoints[method]) {
throw new Error(`Method ${method} not found`)
}
if (!this.namespaces[namespace].endpoints[method][endpoint]) {
throw new Error(`Endpoint ${endpoint} not found`)
}
return this.namespaces[namespace].endpoints[method][endpoint](...args)
}
withEndpoints = (namespace = "main") => {
if (!this.namespaces[namespace]) {
throw new Error(`Namespace ${namespace} not found`)
}
return this.namespaces[namespace].endpoints
}
handleBeforeRequest = async (request) => {
if (this.onExpiredExceptionEvent) {
if (this.excludedExpiredExceptionURL.includes(request.url)) return
await new Promise((resolve) => {
app.eventBus.once("session.regenerated", () => {
console.log(`Session has been regenerated, retrying request`)
resolve()
})
})
}
}
handleRegenerationEvent = async (refreshToken, makeRequest) => {
window.app.eventBus.emit("session.expiredExceptionEvent", refreshToken)
this.onExpiredExceptionEvent = true
const expiredToken = await Session.token
// exclude regeneration endpoint
// send request to regenerate token
const response = await this.request("main", "post", "regenerateSessionToken", {
expiredToken: expiredToken,
refreshToken,
}).catch((error) => {
console.error(`Failed to regenerate token: ${error.message}`)
return false
})
if (!response) {
return window.app.eventBus.emit("session.invalid", "Failed to regenerate token")
}
// set new token
Session.token = response.token
//this.namespaces["main"].internalAbortController.abort()
this.onExpiredExceptionEvent = false
// emit event
window.app.eventBus.emit("session.regenerated")
}
connectBridge = (key, params) => {
this.namespaces[key] = this.createBridge(params)
}
createBridge(params = {}) {
const getSessionContext = async () => {
const obj = {}
const token = await Session.token
if (token) {
// append token to context
obj.headers = {
Authorization: `Bearer ${token ?? null}`,
}
}
return obj
}
const handleResponse = async (data, makeRequest) => {
// handle 401 responses
if (data instanceof Error) {
if (data.response.status === 401) {
// check if the server issue a refresh token on data
if (data.response.data.refreshToken) {
// handle regeneration event
await this.handleRegenerationEvent(data.response.data.refreshToken, makeRequest)
return await makeRequest()
} else {
return window.app.eventBus.emit("session.invalid", "Session expired, but the server did not issue a refresh token")
}
}
if (data.response.status === 403) {
return window.app.eventBus.emit("session.invalid", "Session not valid or not existent")
}
}
}
if (typeof params !== "object") {
throw new Error("Params must be an object")
}
const bridgeOptions = {
wsOptions: {
autoConnect: false,
},
onBeforeRequest: this.handleBeforeRequest,
onRequest: getSessionContext,
onResponse: handleResponse,
...params,
origin: params.httpAddress ?? config.remotes.mainApi,
}
const bridge = new Bridge(bridgeOptions)
// handle main ws events
const mainWSSocket = bridge.wsInterface.sockets["main"]
mainWSSocket.on("authenticated", () => {
console.debug("[WS] Authenticated")
})
mainWSSocket.on("authenticateFailed", (error) => {
console.error("[WS] Authenticate Failed", error)
})
mainWSSocket.on("connect", () => {
this.ctx.eventBus.emit(`api.ws.${mainWSSocket.id}.connect`)
})
mainWSSocket.on("disconnect", (...context) => {
this.ctx.eventBus.emit(`api.ws.${mainWSSocket.id}.disconnect`, ...context)
})
mainWSSocket.on("connect_error", (...context) => {
this.ctx.eventBus.emit(`api.ws.${mainWSSocket.id}.connect_error`, ...context)
})
// generate functions
bridge.listenEvent = this.generateMainWSEventListener(bridge.wsInterface)
bridge.unlistenEvent = this.generateMainWSEventUnlistener(bridge.wsInterface)
// return bridge
return bridge
}
generateMainWSEventListener(obj) {
return (to, fn) => {
if (typeof to === "undefined") {
console.error("handleWSListener: to must be defined")
return false
}
if (typeof fn !== "function") {
console.error("handleWSListener: fn must be function")
return false
}
let ns = "main"
let event = null
if (typeof to === "string") {
event = to
} else if (typeof to === "object") {
ns = to.ns
event = to.event
}
return obj.sockets[ns].on(event, async (...context) => {
return await fn(...context)
})
}
}
generateMainWSEventUnlistener(obj) {
return (to, fn) => {
if (typeof to === "undefined") {
console.error("handleWSListener: to must be defined")
return false
}
if (typeof fn !== "function") {
console.error("handleWSListener: fn must be function")
return false
}
let ns = "main"
let event = null
if (typeof to === "string") {
event = to
} else if (typeof to === "object") {
ns = to.ns
event = to.event
}
return obj.sockets[ns].removeListener(event, fn)
}
}
}

View File

@ -0,0 +1,271 @@
import Core from "evite/src/core"
import React from "react"
import { Howl } from "howler"
import { EmbbededMediaPlayer } from "components"
import { DOMWindow } from "components/RenderWindow"
export default class AudioPlayerCore extends Core {
audioMuted = false
audioVolume = 1
audioQueueHistory = []
audioQueue = []
currentAudio = null
currentDomWindow = null
preloadAudioDebounce = null
publicMethods = {
AudioPlayer: this,
}
async initialize() {
app.eventBus.on("audioPlayer.end", () => {
this.nextAudio()
})
}
toogleMute() {
this.audioMuted = !this.audioMuted
if (this.currentAudio) {
this.currentAudio.instance.mute(this.audioMuted)
}
// apply to all audio in queue
this.audioQueue.forEach((audio) => {
audio.instance.mute(this.audioMuted)
})
app.eventBus.emit("audioPlayer.muted", this.audioMuted)
return this.audioMuted
}
setVolume(volume) {
if (typeof volume !== "number") {
console.warn("Volume must be a number")
return false
}
if (volume > 1) {
volume = 1
}
if (volume < 0) {
volume = 0
}
this.audioVolume = volume
if (this.currentAudio) {
this.currentAudio.instance.volume(volume)
}
// apply to all audio in queue
this.audioQueue.forEach((audio) => {
audio.instance.volume(volume)
})
app.eventBus.emit("audioPlayer.volumeChanged", volume)
return volume
}
async preloadAudio() {
// debounce to prevent multiple preload
if (this.preloadAudioDebounce) {
clearTimeout(this.preloadAudioDebounce)
}
this.preloadAudioDebounce = setTimeout(async () => {
// load the first 2 audio in queue
const audioToLoad = this.audioQueue.slice(0, 2)
// filter undefined
const audioToLoadFiltered = audioToLoad.filter((audio) => audio.instance)
audioToLoad.forEach(async (audio) => {
const audioState = audio.instance.state()
if (audioState !== "loaded" && audioState !== "loading") {
await audio.instance.load()
}
})
}, 600)
}
startPlaylist = async (data) => {
if (typeof data === "undefined") {
console.warn("No data provided")
return false
}
if (!Array.isArray(data)) {
data = [data]
}
await this.clearAudioQueues()
this.attachEmbbededMediaPlayer()
for await (const item of data) {
const audioInstance = await this.createAudioInstance(item)
await this.audioQueue.push({
data: item,
instance: audioInstance,
})
}
await this.preloadAudio()
this.currentAudio = this.audioQueue.shift()
this.playCurrentAudio()
}
clearAudioQueues() {
if (this.currentAudio) {
this.currentAudio.instance.stop()
}
this.audioQueueHistory = []
this.audioQueue = []
this.currentAudio = null
}
async playCurrentAudio() {
if (!this.currentAudio) {
console.warn("No audio playing")
return false
}
const audioState = this.currentAudio.instance.state()
console.log(`Current Audio State: ${audioState}`)
// check if the instance is loaded
if (audioState !== "loaded") {
console.warn("Audio not loaded")
app.eventBus.emit("audioPlayer.loading", this.currentAudio)
await this.currentAudio.instance.load()
app.eventBus.emit("audioPlayer.loaded", this.currentAudio)
}
this.currentAudio.instance.play()
}
pauseAudioQueue() {
if (!this.currentAudio) {
console.warn("No audio playing")
return false
}
this.currentAudio.instance.pause()
}
previousAudio() {
// check if there is audio playing
if (this.currentAudio) {
this.currentAudio.instance.stop()
}
// check if there is audio in queue
if (!this.audioQueueHistory[0]) {
console.warn("No audio in queue")
return false
}
// move current audio to queue
this.audioQueue.unshift(this.currentAudio)
this.currentAudio = this.audioQueueHistory.pop()
this.playCurrentAudio()
}
nextAudio() {
// check if there is audio playing
if (this.currentAudio) {
this.currentAudio.instance.stop()
}
// check if there is audio in queue
if (!this.audioQueue[0]) {
console.warn("No audio in queue")
this.currentAudio = null
// if there is no audio in queue, close the embbeded media player
this.destroyPlayer()
return false
}
// move current audio to history
this.audioQueueHistory.push(this.currentAudio)
this.currentAudio = this.audioQueue.shift()
this.playCurrentAudio()
this.preloadAudio()
}
destroyPlayer() {
this.currentDomWindow.destroy()
this.currentDomWindow = null
}
async createAudioInstance(data) {
const audio = new Howl({
src: data.src,
preload: false,
//html5: true,
mute: this.audioMuted,
volume: this.audioVolume,
onplay: () => {
app.eventBus.emit("audioPlayer.playing", data)
},
onend: () => {
app.eventBus.emit("audioPlayer.end", data)
},
onload: () => {
app.eventBus.emit("audioPlayer.preloaded", data)
},
onpause: () => {
app.eventBus.emit("audioPlayer.paused", data)
},
onstop: () => {
app.eventBus.emit("audioPlayer.stopped", data)
},
onseek: () => {
app.eventBus.emit("audioPlayer.seeked", data)
},
onvolume: () => {
app.eventBus.emit("audioPlayer.volumeChanged", data)
},
})
return audio
}
attachEmbbededMediaPlayer() {
if (this.currentDomWindow) {
console.warn("EmbbededMediaPlayer already attached")
return false
}
this.currentDomWindow = new DOMWindow({
id: "mediaPlayer"
})
this.currentDomWindow.render(<EmbbededMediaPlayer />)
}
}

View File

@ -0,0 +1,71 @@
import Core from "evite/src/core"
import config from "config"
import i18n from "i18next"
import { initReactI18next } from "react-i18next"
export const SUPPORTED_LANGUAGES = config.i18n?.languages ?? {}
export const SUPPORTED_LOCALES = SUPPORTED_LANGUAGES.map((l) => l.locale)
export const DEFAULT_LOCALE = config.i18n?.defaultLocale
export function extractLocaleFromPath(path = "") {
const [_, maybeLocale] = path.split("/")
return SUPPORTED_LOCALES.includes(maybeLocale) ? maybeLocale : DEFAULT_LOCALE
}
const messageImports = import.meta.glob("./translations/*.json")
export default class I18nCore extends Core {
events = {
"changeLanguage": (locale) => {
this.loadAsyncLanguage(locale)
}
}
initialize = async () => {
let locale = app.settings.get("language") ?? DEFAULT_LOCALE
if (!SUPPORTED_LOCALES.includes(locale)) {
locale = DEFAULT_LOCALE
}
const messages = await this.importLocale(locale)
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
// debug: true,
resources: {
[locale]: { translation: messages.default || messages },
},
lng: locale,
//fallbackLng: DEFAULT_LOCALE,
interpolation: {
escapeValue: false, // react already safes from xss
},
})
}
importLocale = async (locale) => {
const [, importLocale] =
Object.entries(messageImports).find(([key]) =>
key.includes(`/${locale}.`)
) || []
return importLocale && importLocale()
}
loadAsyncLanguage = async function (locale) {
locale = locale ?? DEFAULT_LOCALE
try {
const result = await this.importLocale(locale)
if (result) {
i18n.addResourceBundle(locale, "translation", result.default || result)
i18n.changeLanguage(locale)
}
} catch (error) {
console.error(error)
}
}
}

View File

@ -0,0 +1,103 @@
{
"main_welcome": "Welcome back,",
"assigned_for_you": "Assigned for you",
"no_assigned_workorders": "No assigned workorders",
"new": "New",
"close": "Close",
"done": "Done",
"edit": "Edit",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"import": "Import",
"export": "Export",
"refresh": "Refresh",
"reload": "Reload",
"search": "Search",
"status": "Status",
"type": "Type",
"about": "About",
"current": "Current",
"statistics": "Statistics",
"name": "Name",
"username": "Username",
"email": "Email",
"password": "Password",
"sessions": "Sessions",
"compact_view": "Compact view",
"sign_in": "Sign in",
"sign_out": "Sign out",
"sign_up": "Sign up",
"all_sessions": "All sessions",
"destroy_all_sessions": "Destroy all sessions",
"account_info": "Account info",
"password_confirmation": "Password confirmation",
"new_product": "New product",
"description": "Description",
"describe_something": "Describe something",
"operations": "Operations",
"select_operation": "Select operation",
"select_operations": "Select operations",
"tasks": "Tasks",
"select_task": "Select task",
"select_tasks": "Select tasks",
"add_task": "Add task",
"add_tasks": "Add tasks",
"location": "Location",
"select_location": "Select location",
"select_locations": "Select locations",
"add_location": "Add location",
"add_locations": "Add locations",
"materials": "Materials",
"select_material": "Select material",
"select_materials": "Select materials",
"add_material": "Add material",
"add_materials": "Add materials",
"quantity": "Quantity",
"select_quantity": "Select quantity",
"select_quantities": "Select quantities",
"add_quantity": "Add quantity",
"add_quantities": "Add quantities",
"units": "Units",
"select_unit": "Select unit",
"select_units": "Select units",
"add_unit": "Add unit",
"add_units": "Add units",
"suppliers": "Suppliers",
"select_supplier": "Select supplier",
"select_suppliers": "Select suppliers",
"add_supplier": "Add supplier",
"add_suppliers": "Add suppliers",
"customers": "Customers",
"select_customer": "Select customer",
"select_customers": "Select customers",
"add_customer": "Add customer",
"add_customers": "Add customers",
"employees": "Employees",
"select_employee": "Select employee",
"select_employees": "Select employees",
"add_employee": "Add employee",
"add_employees": "Add employees",
"equipment": "Equipment",
"select_equipment": "Select equipment",
"select": "Select",
"variants": "Variants",
"select_variant": "Select variant",
"select_variants": "Select variants",
"settins_group_general": "General",
"settings_general_language": "Language",
"settings_general_language_description": "Choose language for using in application.",
"settings_general_sidebarAutoCollapse": "Sidebar auto collapse",
"settings_general_sidebarAutoCollapse_description": "Collapse sidebar when loose focus.",
"settings_group_aspect": "Aspect",
"settings_aspect_reduceAnimations": "Reduce animations",
"settings_aspect_reduceAnimations_description": "Reduce animation of the application.",
"settings_aspect_darkMode": "Dark mode",
"settings_aspect_darkMode_description": "Enable dark mode for the application.",
"settings_aspect_primaryColor": "Primary color",
"settings_aspect_primaryColor_description": "Change primary color of the application.",
"settings_aspect_resetTheme": "Reset theme",
"settings_aspect_resetTheme_description": "Reset theme to default."
}

View File

@ -0,0 +1,214 @@
{
"Dashboard": "Inicio",
"main_welcome": "Bienvenido,",
"assigned_for_you": "Asignado para usted",
"no_assigned_workorders": "No hay trabajos asignados",
"Start": "Iniciar",
"End": "Finalizar",
"Stop": "Parar",
"Started": "Iniciado",
"started": "iniciado",
"Ended": "Finalizado",
"ended": "finalizado",
"Expired": "Expirado",
"expired": "expirado",
"Stopped": "Parado",
"stopped": "parado",
"Pending": "Pendiente",
"pending": "pendiente",
"Finished": "Finalizado",
"finished": "terminado",
"Cancelled": "Cancelado",
"cancelled": "cancelado",
"Assigned": "Asignado",
"assigned": "asignado",
"Ready": "Listo",
"ready": "listo",
"No description": "Sin descripción",
"All": "Todos",
"all": "todos",
"or": "o",
"Browse": "Buscar",
"Create new": "Crear nuevo",
"New": "Nuevo",
"Close": "Cerrar",
"Done": "Listo",
"Next": "Siguiente",
"Previous": "Anterior",
"Schedule": "Plazo",
"Edit": "Modificar",
"Save": "Guardar",
"Cancel": "Cancelar",
"Delete": "Eliminar",
"State": "Estado",
"Modify": "Modificar",
"modify": "modificar",
"Notifications": "Notificaciones",
"Notification": "Notificación",
"Haptic": "Vibración",
"Haptic Feedback": "Vibración de respuesta",
"Enable haptic feedback on touch events.": "Habilitar vibración de respuesta cuando exista un evento de toque.",
"Selection press delay": "Retraso de presión de selección",
"Set the delay before the selection trigger is activated.": "Establecer el retraso antes de que el disparador de selección sea activado.",
"Force Mobile Mode": "Forzar modo móvil",
"Force the application to run in mobile mode.": "Forzar la aplicación a ejecutarse en modo móvil.",
"Manage operators": "Administrar operadores",
"Manage users": "Administrar usuarios",
"Manage groups": "Administrar grupos",
"Manage workflows": "Administrar flujos de trabajo",
"Manage roles": "Administrar roles",
"Manage permissions": "Administrar permisos",
"Disable": "Deshabilitar",
"Disabled": "Deshabilitado",
"Discard": "Descartar",
"Unselect all": "Deseleccionar todo",
"Select all": "Seleccionar todo",
"Add commit": "Añadir registro",
"Commit": "Registrar",
"Commits": "Registros",
"Assistant mode": "Modo asistente",
"View finished": "Ver terminados",
"View pending": "Ver pendientes",
"View assigned": "Ver asignados",
"View ready": "Ver listos",
"View cancelled": "Ver cancelados",
"View all": "Ver todos",
"View": "Ver",
"Mark produced quantity": "Marcar cantidad producida",
"Mark remaining amount": "Marcar cantidad restante",
"Quantity": "Cantidad",
"Quantity produced": "Cantidad producida",
"Quantity left": "Cantidad restante",
"Production target": "Objectivo de producción",
"Section": "Sección",
"Sections": "Secciones",
"Workshift": "Turno",
"workshift": "turno",
"Workshifts": "Turnos",
"workshifts": "turnos",
"Operation": "Operación",
"Operations": "Operaciones",
"Stock Target": "Stock objetivo",
"Vault item": "Artículo de bóveda",
"Stock item": "Artículo de stock",
"Vault": "Bóveda",
"Phase": "Fase",
"Variants": "Variantes",
"Variant": "Variante",
"Description": "Descripción",
"Task": "Tarea",
"Tasks": "Tareas",
"Product": "Producto",
"Products": "Productos",
"Operator": "Operador",
"Operators": "Operadores",
"Workload": "Carga de trabajo",
"workload": "carga de trabajo",
"Workloads": "Cargas de trabajo",
"workloads": "cargas de trabajo",
"Workorder": "Orden de trabajo",
"workorder": "orden de trabajo",
"Workorders": "Ordenes de trabajo",
"workorders": "ordenes de trabajo",
"Workpart": "Parte de trabajo",
"workpart": "parte de trabajo",
"Workparts": "Partes de trabajo",
"workparts": "parte de trabajo",
"Payload": "Carga",
"payload": "carga",
"Payloads": "Cargas",
"payloads": "carga",
"Commit all": "Registrar todo",
"Mark quantity": "Marcar cantidad",
"Mark": "Marcar",
"Marked": "Marcado",
"Marked quantity": "Cantidad marcada",
"Marked amount": "Cantidad marcada",
"Marked amount left": "Cantidad restante marcada",
"Marked amount produced": "Cantidad producida marcada",
"Marked amount remaining": "Cantidad restante marcada",
"Marked amount target": "Cantidad objetivo marcada",
"Marked amount stock": "Cantidad stock marcada",
"Notifications Sound": "Sonido de notificación",
"Play a sound when a notification is received.": "Reproducir un sonido cuando se recibe una notificación.",
"Vibration": "Vibración",
"Vibrate the device when a notification is received.": "Vibrar el dispositivo cuando se recibe una notificación.",
"Sound Volume": "Volumen de sonido",
"Set the volume of the sound when a notification is received.": "Establecer el volumen del sonido cuando se recibe una notificación.",
"Workorder Notifications": "Notificaciones de orden de trabajo",
"Display in-app notifications for workorders updates.": "Mostrar notificaciones para las actualizaciones de las ordenes de trabajo.",
"Accounts": "Cuentas",
"Import": "Importar",
"Export": "Exportar",
"Refresh": "Actualizar",
"Reload": "Recargar",
"Required": "Requerido",
"Optional": "Opcional",
"Search": "Buscar",
"Status": "Estado",
"Type": "Tipo",
"About": "Acerca de",
"Current": "Actual",
"Statistics": "Estadísticas",
"Name": "Nombre",
"Users": "Usuarios",
"Username": "Nombre de usuario",
"Settings": "Configuración",
"Email": "Correo electrónico",
"Password": "Contraseña",
"Sessions": "Sesiones",
"Compact view": "Vista compacta",
"Add to catalog": "Añadir al catálogo",
"Fabric": "Fabric",
"Press and hold for 2 seconds to toggle running": "Pulse y mantenga pulsado durante 2 segundos para alternar el funcionamiento",
"Production quantity already has been reached": "La cantidad de producción ya ha sido alcanzada",
"Production quantity is not enough": "La cantidad de producción no es suficiente",
"Are you sure you want to commit for this workpart?": "¿Está seguro de que desea consolidar esta parte de trabajo?",
"Are you sure you want to commit all quantity left?": "¿Está seguro de que desea consolidar toda la cantidad restante?",
"This will commit all quantity left and finish the production for this workload.": "Esto consolidará toda la cantidad restante y terminará la producción para esta carga de trabajo.",
"Are you sure you want to commit for this workorder?": "¿Está seguro de que desea consolidar esta orden de trabajo?",
"Enter the name or a reference for the workorder.": "Introduzca el nombre o una referencia para la orden de trabajo.",
"Select the section where the workorder will be deployed.": "Seleccione la sección donde se desplegará la orden de trabajo.",
"Select the schedule for the workorder.": "Seleccione el plazo para la orden de trabajo.",
"Assign the operators for the workorder.": "Asigne los operadores para la orden de trabajo.",
"Define the payloads for the workorder.": "Defina las cargas para la orden de trabajo.",
"Define the workloads for the workorder.": "Defina las cargas de trabajo para la orden de trabajo.",
"Leaving process running on background, dont forget to stop it when you are done": "Dejando el proceso en ejecución en segundo plano, no olvide detenerlo cuando haya terminado",
"Task remains opened, dont forget to stop it when you are done": "La tarea permanece abierta, no olvide detenerla cuando haya terminado",
"Select a option": "Seleccione una opción",
"Set the quantity produced": "Marque la cantidad producida",
"Experimental": "Experimental",
"New workorder assigned": "Nueva orden de trabajo asignada",
"Check the new list of workorder": "Compruebe la nueva lista de ordenes de trabajo",
"Do you want to delete these items?": "¿Desea eliminar estos elementos?",
"This action cannot be undone, and will permanently delete the selected items.": "Esta acción no se puede deshacer, y eliminará permanentemente los elementos seleccionados.",
"Assigned operators": "Operadores asignados",
"Assigments": "Asignaciones",
"Working Tasks": "Tareas en ejecución",
"You are not working on any task": "No estás trabajando en ninguna tarea",
"Update": "Actualizar",
"Update status": "Actualizar estado",
"Archived": "Archivado",
"archived": "archivado",
"Archive": "Archivar",
"archive": "archivar",
"General": "General",
"Sidebar": "Barra lateral",
"Aspect": "Aspecto",
"Language": "Idioma",
"Choose a language for the application": "Elige un idioma para la aplicación.",
"Edit Sidebar": "Editar barra lateral",
"Auto Collapse": "Auto colapsar",
"Collapse the sidebar when loose focus": "Colapsar la barra lateral cuando pierda el foco.",
"Reduce animation": "Reducir animación",
"Primary color": "Color primario",
"Change primary color of the application.": "Cambia el color primario de la aplicación.",
"Dark mode": "Modo oscuro",
"Images": "Imágenes",
"Add others": "Añadir otros",
"Export tokens": "Exportar bonos de trabajo",
"Description of the task. It should be a general description of the product. Do not include information that may vary. e.g. 'The product is a white shirt with a elastic red collar, size M'": "Descripción de la tarea. Debe ser una descripción general del producto. No incluya información que pueda variar. Por ejemplo, 'El producto es una camisa blanca con un collar de color rojo, tamaño M'",
"Define variants for this item. Only the types of variations that may exist of a product should be included. e.g. Size, Color, Material, etc.": "Defina las variantes para este artículo. Sólo deben incluirse los tipos de variaciones que pueden existir de un producto. Por ejemplo, Tamaño, Color, Material, etc.",
"Append some images to describe this item.": "Agregue algunas imágenes para describir este artículo."
}

View File

@ -0,0 +1,25 @@
import SettingsCore from "./settings"
import APICore from "./api"
import StyleCore from "./style"
import PermissionsCore from "./permissions"
import I18nCore from "./i18n"
import NotificationsCore from "./notifications"
import ShortcutsCore from "./shortcuts"
import SoundCore from "./sound"
import AudioPlayer from "./audioPlayer"
// DEFINE LOAD ORDER HERE
export default [
SettingsCore,
APICore,
PermissionsCore,
StyleCore,
I18nCore,
SoundCore,
NotificationsCore,
ShortcutsCore,
AudioPlayer,
]

View File

@ -0,0 +1,74 @@
import Core from "evite/src/core"
import React from "react"
import { notification as Notf } from "antd"
import { Icons, createIconRender } from "components/Icons"
import { Translation } from "react-i18next"
import { Haptics } from "@capacitor/haptics"
export default class NotificationCore extends Core {
events = {
"changeNotificationsSoundVolume": (value) => {
this.playAudio({ soundVolume: value })
},
"changeNotificationsVibrate": (value) => {
this.playHaptic({
vibrationEnabled: value,
})
}
}
publicMethods = {
notification: this
}
getSoundVolume = () => {
return (window.app.settings.get("notifications_sound_volume") ?? 50) / 100
}
new = (notification, options = {}) => {
this.notify(notification, options)
this.playHaptic(options)
this.playAudio(options)
}
notify = (notification, options = {}) => {
if (typeof notification === "string") {
notification = {
title: "New notification",
description: notification
}
}
Notf.open({
message: <Translation>
{(t) => t(notification.title)}
</Translation>,
description: <Translation>
{(t) => t(notification.description)}
</Translation>,
duration: notification.duration ?? 4,
icon: React.isValidElement(notification.icon) ? notification.icon : (createIconRender(notification.icon) ?? <Icons.Bell />),
})
}
playHaptic = async (options = {}) => {
const vibrationEnabled = options.vibrationEnabled ?? window.app.settings.get("notifications_vibrate")
if (vibrationEnabled) {
await Haptics.vibrate()
}
}
playAudio = (options = {}) => {
const soundEnabled = options.soundEnabled ?? window.app.settings.get("notifications_sound")
const soundVolume = options.soundVolume ? options.soundVolume / 100 : this.getSoundVolume()
if (soundEnabled) {
if (typeof window.app.sound?.play === "function") {
window.app.sound.play("notification", {
volume: soundVolume,
})
}
}
}
}

View File

@ -0,0 +1,43 @@
import Core from "evite/src/core"
import UserModel from "models/user"
export default class PermissionsCore extends Core {
publicMethods = {
permissions: this
}
isUserAdmin = "unchecked"
// this will works with a newer version of evite
async initializeBeforeRuntimeInit() {
this.isUserAdmin = await UserModel.hasAdmin()
}
hasAdmin = async () => {
return await UserModel.hasAdmin()
}
hasPermission = async (permission) => {
let query = []
if (Array.isArray(permission)) {
query = permission
} else {
query = [permission]
}
// create a promise and check if the user has all the permission in the query
const result = await Promise.all(query.map(async (permission) => {
const hasPermission = await UserModel.hasRole(permission)
return hasPermission
}))
// if the user has all the permission in the query, return true
if (result.every((hasPermission) => hasPermission)) {
return true
}
return false
}
}

View File

@ -0,0 +1,76 @@
import Core from "evite/src/core"
import store from "store"
import defaultSettings from "schemas/defaultSettings.json"
import { Observable } from "rxjs"
export default class SettingsCore extends Core {
storeKey = "app_settings"
settings = store.get(this.storeKey) ?? {}
publicMethods = {
settings: this
}
initialize() {
this.fulfillUndefinedWithDefaults()
}
fulfillUndefinedWithDefaults = () => {
Object.keys(defaultSettings).forEach((key) => {
const value = defaultSettings[key]
// Only set default if value is undefined
if (typeof this.settings[key] === "undefined") {
this.settings[key] = value
}
})
}
is = (key, value) => {
return this.settings[key] === value
}
set = (key, value) => {
this.settings[key] = value
store.set(this.storeKey, this.settings)
window.app.eventBus.emit("setting.update", { key, value })
window.app.eventBus.emit(`setting.update.${key}`, value)
return this.settings
}
get = (key) => {
if (typeof key === "undefined") {
return this.settings
}
return this.settings[key]
}
getDefaults = (key) => {
if (typeof key === "undefined") {
return defaultSettings
}
return defaultSettings[key]
}
withEvent = (listenEvent, defaultValue) => {
let value = defaultValue ?? this.settings[key] ?? false
const observable = new Observable((subscriber) => {
subscriber.next(value)
window.app.eventBus.on(listenEvent, (to) => {
value = to
subscriber.next(value)
})
})
return observable.subscribe((value) => {
return value
})
}
}

View File

@ -0,0 +1,74 @@
import Core from "evite/src/core"
export default class ShortcutsCore extends Core {
shortcuts = {}
publicMethods = {
shortcuts: this
}
initialize() {
document.addEventListener("keydown", this.handleEvent)
}
handleEvent = (event) => {
// FIXME: event.key sometimes is not defined
//event.key = event.key.toLowerCase()
const shortcut = this.shortcuts[event.key]
if (shortcut) {
if (typeof shortcut.ctrl === "boolean" && event.ctrlKey !== shortcut.ctrl) {
return
}
if (typeof shortcut.shift === "boolean" && event.shiftKey !== shortcut.shift) {
return
}
if (typeof shortcut.alt === "boolean" && event.altKey !== shortcut.alt) {
return
}
if (typeof shortcut.meta === "boolean" && event.metaKey !== shortcut.meta) {
return
}
if (shortcut.preventDefault) {
event.preventDefault()
}
if (typeof shortcut.fn === "function") {
shortcut.fn()
}
}
}
register = (keybind = {}, fn) => {
if (typeof keybind === "string") {
keybind = {
key: keybind,
}
}
this.shortcuts[keybind.key] = {
...keybind,
fn,
}
}
remove = (array) => {
if (typeof array === "string") {
array = [array]
}
array.forEach(key => {
delete this.shortcuts[key]
})
}
window = {
ShortcutsController: this
}
}

View File

@ -0,0 +1,41 @@
import Core from "evite/src/core"
import { Howl } from "howler"
import config from "config"
export default class SoundCore extends Core {
sounds = {}
publicMethods = {
sound: this,
}
async initialize() {
this.sounds = await this.getSounds()
}
getSounds = async () => {
// TODO: Load custom soundpacks manifests
let soundPack = config.defaultSoundPack ?? {}
Object.keys(soundPack).forEach((key) => {
const src = soundPack[key]
soundPack[key] = (options) => new Howl({
volume: window.app.settings.get("generalAudioVolume") ?? 0.5,
...options,
src: [src],
})
})
return soundPack
}
play = (name, options) => {
if (this.sounds[name]) {
return this.sounds[name](options).play()
} else {
console.error(`Sound [${name}] not found or is not available.`)
return false
}
}
}

View File

@ -0,0 +1,199 @@
import Core from "evite/src/core"
import config from "config"
import store from "store"
import { ConfigProvider } from "antd"
export default class StyleCore extends Core {
themeManifestStorageKey = "theme"
modificationStorageKey = "themeModifications"
theme = null
mutation = null
currentVariant = null
events = {
"style.compactMode": (value = !window.app.settings.get("style.compactMode")) => {
if (value) {
return this.update({
layoutMargin: 0,
layoutPadding: 0,
})
}
return this.update({
layoutMargin: this.getValue("layoutMargin"),
layoutPadding: this.getValue("layoutPadding"),
})
},
"style.autoDarkModeToogle": (value) => {
if (value === true) {
this.handleAutoColorScheme()
} else {
this.applyVariant(this.getStoragedVariant())
}
},
"theme.applyVariant": (value) => {
this.applyVariant(value)
this.setVariant(value)
},
"modifyTheme": (value) => {
this.update(value)
this.setModifications(this.mutation)
},
"resetTheme": () => {
this.resetDefault()
}
}
publicMethods = {
style: this
}
static get currentVariant() {
return document.documentElement.style.getPropertyValue("--themeVariant")
}
handleAutoColorScheme() {
const prefered = window.matchMedia("(prefers-color-scheme: light)")
if (!prefered.matches) {
this.applyVariant("dark")
} else {
this.applyVariant("light")
}
}
initialize = async () => {
let theme = this.getStoragedTheme()
const modifications = this.getStoragedModifications()
const variantKey = this.getStoragedVariant()
if (!theme) {
// load default theme
theme = this.getDefaultTheme()
} else {
// load URL and initialize theme
}
// set global theme
this.theme = theme
// override with static vars
if (theme.staticVars) {
this.update(theme.staticVars)
}
// override theme with modifications
if (modifications) {
this.update(modifications)
}
// apply variation
this.applyVariant(variantKey)
// handle auto prefered color scheme
window.matchMedia("(prefers-color-scheme: light)").addListener(() => {
console.log(`[THEME] Auto color scheme changed`)
if (window.app.settings.get("auto_darkMode")) {
this.handleAutoColorScheme()
}
})
if (window.app.settings.get("auto_darkMode")) {
this.handleAutoColorScheme()
}
}
getRootVariables = () => {
let attributes = document.documentElement.getAttribute("style").trim().split(";")
attributes = attributes.slice(0, (attributes.length - 1))
attributes = attributes.map((variable) => {
let [key, value] = variable.split(":")
key = key.split("--")[1]
return [key, value]
})
return Object.fromEntries(attributes)
}
getDefaultTheme = () => {
// TODO: Use evite CONSTANTS_API
return config.defaultTheme
}
getStoragedTheme = () => {
return store.get(this.themeManifestStorageKey)
}
getStoragedModifications = () => {
return store.get(this.modificationStorageKey) ?? {}
}
getStoragedVariant = () => {
return app.settings.get("themeVariant")
}
getValue = (key) => {
if (typeof key === "undefined") {
return {
...staticValues,
...storagedModifications
}
}
const storagedModifications = this.getStoragedModifications()
const staticValues = this.theme.staticVars
return storagedModifications[key] || staticValues[key]
}
setVariant = (variationKey) => {
return app.settings.set("themeVariant", variationKey)
}
setModifications = (modifications) => {
return store.set(this.modificationStorageKey, modifications)
}
resetDefault = () => {
store.remove(this.themeManifestStorageKey)
store.remove(this.modificationStorageKey)
window.app.settings.set("primaryColor", this.theme.staticVars.primaryColor)
this.initialize()
}
update = (update) => {
if (typeof update !== "object") {
return false
}
this.mutation = {
...this.theme.staticVars,
...this.mutation,
...update
}
Object.keys(this.mutation).forEach(key => {
document.documentElement.style.setProperty(`--${key}`, this.mutation[key])
})
document.documentElement.className = `theme-${this.currentVariant}`
document.documentElement.style.setProperty(`--themeVariant`, this.currentVariant)
ConfigProvider.config({ theme: this.mutation })
}
applyVariant = (variant = (this.theme.defaultVariant ?? "light")) => {
const values = this.theme.variants[variant]
if (values) {
this.currentVariant = variant
this.update(values)
}
}
}

View File

@ -0,0 +1,124 @@
import React from "react"
import * as antd from "antd"
import progressBar from "nprogress"
import config from "config"
import routes from "schemas/routes"
import Layouts from "layouts"
export default class Layout extends React.Component {
progressBar = progressBar.configure({ parent: "html", showSpinner: false })
state = {
layoutType: "default",
renderLock: true,
renderError: null,
}
events = {
"app.initialization.start": () => {
this.setState({
renderLock: true,
})
},
"app.initialization.finish": () => {
this.setState({
renderLock: false,
})
},
"router.transitionStart": () => {
this.progressBar.start()
},
"router.transitionFinish": () => {
this.progressBar.done()
},
}
componentDidMount() {
// register events
Object.keys(this.events).forEach((event) => {
window.app.eventBus.on(event, this.events[event])
})
}
componentWillUnmount() {
// unregister events
Object.keys(this.events).forEach((event) => {
window.app.eventBus.off(event, this.events[event])
})
}
componentDidCatch(info, stack) {
this.setState({ renderError: { info, stack } })
}
setLayout = (layout) => {
if (typeof Layouts[layout] === "function") {
return this.setState({
layoutType: layout,
})
}
return console.error("Layout type not found")
}
render() {
let layoutType = this.state.layoutType
const InitializationComponent = this.props.staticRenders?.Initialization ? React.createElement(this.props.staticRenders.Initialization) : null
if (this.state.renderError) {
if (this.props.staticRenders?.RenderError) {
return React.createElement(this.props.staticRenders?.RenderError, { error: this.state.renderError })
}
return JSON.stringify(this.state.renderError)
}
console.debug(`Rendering layout [${this.state.layoutType}] for current route [${window.location.pathname}]`)
// check with the current route if it's a protected route or requires some permissions
const routeDeclaration = routes.find((route) => route.path === window.location.pathname)
if (routeDeclaration) {
if (typeof routeDeclaration.requiredRoles !== "undefined") {
const isAdmin = this.props.user?.roles?.includes("admin") ?? false
if (!isAdmin && !routeDeclaration.requiredRoles.some((role) => this.props.user?.roles?.includes(role))) {
return <antd.Result
status="403"
title="403"
subTitle="Sorry, you are not authorized to access this page."
extra={<antd.Button type="primary" onClick={() => window.app.setLocation("/")}>Back Home</antd.Button>}
/>
}
}
if (typeof routeDeclaration.useLayout !== "undefined") {
layoutType = routeDeclaration.useLayout
}
if (typeof routeDeclaration.webTitleAddition !== "undefined") {
document.title = `${routeDeclaration.webTitleAddition} - ${config.app.siteName}`
} else {
document.title = config.app.siteName
}
}
const layoutComponentProps = {
...this.props.bindProps,
...this.state,
}
const Layout = Layouts[layoutType]
if (!Layout) {
return app.eventBus.emit("runtime.crash", new Error(`Layout type [${layoutType}] not found`))
}
return <Layout {...layoutComponentProps}>
{this.state.renderLock ? InitializationComponent : this.props.children}
</Layout>
}
}

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