use evite as engine
36
packages/app/.config.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const aliases = {
|
||||||
|
schemas: path.resolve(__dirname, './schemas'),
|
||||||
|
controllers: path.resolve(__dirname, "./src/controllers"),
|
||||||
|
extensions: path.resolve(__dirname, './src/extensions'),
|
||||||
|
theme: path.join(__dirname, 'src/theme'),
|
||||||
|
locales: path.join(__dirname, 'src/locales'),
|
||||||
|
core: path.join(__dirname, 'src/core'),
|
||||||
|
pages: path.join(__dirname, 'src/pages'),
|
||||||
|
components: path.join(__dirname, 'src/components'),
|
||||||
|
models: path.join(__dirname, 'src/models'),
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (config) => {
|
||||||
|
if (typeof config.windowContext.process === 'undefined') {
|
||||||
|
config.windowContext.process = Object()
|
||||||
|
}
|
||||||
|
|
||||||
|
config.windowContext.process = config.windowContext.__evite
|
||||||
|
config.windowContext.process["versions"] = process.versions
|
||||||
|
config.resolve.alias = {
|
||||||
|
...config.resolve.alias,
|
||||||
|
...aliases,
|
||||||
|
}
|
||||||
|
|
||||||
|
config.css = {
|
||||||
|
preprocessorOptions: {
|
||||||
|
less: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
@ -1,35 +1,22 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
|
logo: {
|
||||||
|
alt: "https://dl.ragestudio.net/branding/comty/alt/SVG/t3t3.svg"
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
address: process.env.NODE_ENV !== 'production' ? `http://${window.location.hostname}:3000` : "https://api.amimet.es",
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
"primary-color": "#32b7bb",
|
||||||
|
},
|
||||||
app: {
|
app: {
|
||||||
guid: "7d6b74b5-1b3b-432f-97df-2c5fc2c2b6ae",
|
|
||||||
siteName: 'Comty™',
|
siteName: 'Comty™',
|
||||||
copyright: 'RageStudio©',
|
copyright: 'RageStudio©',
|
||||||
MainPath: '/',
|
mainPath: '/main',
|
||||||
defaultStyleClass: "app_",
|
|
||||||
|
|
||||||
LogoPath: '/logo.svg',
|
|
||||||
FullLogoPath: '/full_logo.svg',
|
|
||||||
DarkFullLogoPath: '/dark_full_logo.svg',
|
|
||||||
DarkLogoPath: '/dark_logo.svg',
|
|
||||||
|
|
||||||
endpoint_v3prefix: 'ycorejs_apiv3',
|
|
||||||
endpoint_websocket: 'eu_es01.ragestudio.net',
|
|
||||||
|
|
||||||
storage: {
|
|
||||||
theme: "app_theme",
|
|
||||||
settings: "app_settings",
|
|
||||||
token: "cid",
|
|
||||||
data: "data"
|
|
||||||
},
|
|
||||||
|
|
||||||
storage_authFrame: 'cid',
|
|
||||||
storage_dataFrame: 'data',
|
|
||||||
storage_theme: 'app_theme',
|
|
||||||
|
|
||||||
appTheme_desiredContrast: 7,
|
appTheme_desiredContrast: 7,
|
||||||
// Contrast level AA = 4.5, Level AAA = 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
|
// Reference: https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0&showtechniques=143#qr-visual-audio-contrast-contrast
|
||||||
},
|
},
|
||||||
|
|
||||||
i18n: {
|
i18n: {
|
||||||
languages: [
|
languages: [
|
||||||
{
|
{
|
||||||
@ -39,40 +26,6 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
defaultLanguage: 'en',
|
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,
|
|
||||||
sidebarCollaped: false,
|
|
||||||
session_noexpire: false,
|
|
||||||
search_ontype: false,
|
|
||||||
post_autoposition: true,
|
|
||||||
overlay_loosefocus: true,
|
|
||||||
render_pagetransition_preset: 'moveToRightScaleUp',
|
|
||||||
post_catchlimit: '20',
|
|
||||||
post_hidebar: true,
|
|
||||||
|
|
||||||
feed_autorefresh: false,
|
|
||||||
keybinds: {
|
|
||||||
nextElement: "J",
|
|
||||||
prevElement: "U",
|
|
||||||
createNew: "N"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stricts: {
|
stricts: {
|
||||||
post_maxlenght: '512',
|
post_maxlenght: '512',
|
||||||
// In KB
|
// In KB
|
||||||
|
@ -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!"}
|
|
||||||
]
|
|
||||||
|
|
3
packages/app/schemas/defaultSettings.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"collapseOnLooseFocus": true
|
||||||
|
}
|
4
packages/app/schemas/defaultSidebar.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[
|
||||||
|
"main",
|
||||||
|
"posts"
|
||||||
|
]
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"github":"https://github.com/srgooglo/comty",
|
|
||||||
"trellojoin": "https://trello.com/invite/b/UbwvlG1I/2bc02725b9b210d2e9e9a82c5040b895/comty-development",
|
|
||||||
"patreon": "https://www.patreon.com/rstudio"
|
|
||||||
}
|
|
22
packages/app/schemas/routesDecorators.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"main": {
|
||||||
|
"icon": "Home",
|
||||||
|
"title": "Main"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"icon": "User",
|
||||||
|
"title": "Account"
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"icon": "Users",
|
||||||
|
"title": "Users"
|
||||||
|
},
|
||||||
|
"regions": {
|
||||||
|
"icon": "Globe",
|
||||||
|
"title": "Regions"
|
||||||
|
},
|
||||||
|
"vault": {
|
||||||
|
"icon": "Archive",
|
||||||
|
"title": "Vault"
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,17 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "session_noexpire",
|
"id": "expire_session",
|
||||||
"icon": "Watch",
|
"icon": "Key",
|
||||||
|
"title": "Expire Session",
|
||||||
|
"group": "general",
|
||||||
|
"type": "Switch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "post_autoposition",
|
||||||
|
"icon": "AlignCenter",
|
||||||
"type": "Switch",
|
"type": "Switch",
|
||||||
"title": "No expire session",
|
"title": "Center on click",
|
||||||
"description": "Force the app to not expire any session"
|
"description": "Center posts element when then is clicked"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "search_ontype",
|
"id": "search_ontype",
|
||||||
@ -21,17 +28,22 @@
|
|||||||
"description": "Hide post actions bar when loose focus"
|
"description": "Hide post actions bar when loose focus"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "post_autoposition",
|
"id": "edit_sidebar",
|
||||||
"icon": "AlignCenter",
|
"group": "sidebar",
|
||||||
"type": "Switch",
|
"icon": "Edit",
|
||||||
"title": "Center on click",
|
"title": "Edit Sidebar",
|
||||||
"description": "Center posts element when then is clicked"
|
"type": "Button"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "verbosity",
|
"id": "collapseOnLooseFocus",
|
||||||
"icon": "Terminal",
|
"group": "sidebar",
|
||||||
"type": "Switch",
|
"title": "Collapse when loose focus",
|
||||||
"title": "Verbosity",
|
"type": "Switch"
|
||||||
"description": "Show all development logs of the application"
|
},
|
||||||
|
{
|
||||||
|
"id": "reduceAnimations",
|
||||||
|
"group": "aspect",
|
||||||
|
"title": "Reduce animations",
|
||||||
|
"type": "Switch"
|
||||||
}
|
}
|
||||||
]
|
]
|
14
packages/app/schemas/settingsGroups.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"icon": "Settings"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"title": "Sidebar",
|
||||||
|
"icon": "Layout"
|
||||||
|
},
|
||||||
|
"aspect": {
|
||||||
|
"title": "Aspect",
|
||||||
|
"icon": "Eye"
|
||||||
|
}
|
||||||
|
}
|
14
packages/app/schemas/sidebar.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "main",
|
||||||
|
"title": "Dashboard",
|
||||||
|
"icon": "Home",
|
||||||
|
"locked": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "posts",
|
||||||
|
"title": "Posts",
|
||||||
|
"icon": "Home",
|
||||||
|
"locked": true
|
||||||
|
}
|
||||||
|
]
|
@ -1,92 +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",
|
|
||||||
"attributes": {
|
|
||||||
"position": "bottom",
|
|
||||||
"require": "login"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "login",
|
|
||||||
"title": "Signin",
|
|
||||||
"icon": "LogIn",
|
|
||||||
"attributes": {
|
|
||||||
"position": "bottom",
|
|
||||||
"require": "guest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -4,19 +4,16 @@ import progressBar from "nprogress"
|
|||||||
import * as antd from "antd"
|
import * as antd from "antd"
|
||||||
import classnames from "classnames"
|
import classnames from "classnames"
|
||||||
|
|
||||||
import { Sidebar, Header, Drawer, Sidedrawer } from "./layout"
|
|
||||||
import { NotFound, RenderError } from "components"
|
|
||||||
import { Icons } from "components/Icons"
|
import { Icons } from "components/Icons"
|
||||||
|
import { CreateEviteApp, BindPropsProvider } from "evite"
|
||||||
|
|
||||||
import config from "config"
|
import config from "config"
|
||||||
import { Session, User } from "models"
|
import { Session, User } from "models"
|
||||||
|
import { NotFound, RenderError } from "components"
|
||||||
|
import { SettingsController, SidebarController } from "controllers"
|
||||||
|
import { API, Render, Debug, Sound } from "extensions"
|
||||||
|
|
||||||
import SidebarController from "core/models/sidebar"
|
import { Sidebar, Header, Drawer, Sidedrawer } from "./layout"
|
||||||
import SettingsController from "core/models/settings"
|
|
||||||
|
|
||||||
import { CreateEviteApp, BindPropsProvider } from "evite"
|
|
||||||
import { API, Render, Splash, Debug, theme, Sound } from "extensions"
|
|
||||||
|
|
||||||
import "theme/index.less"
|
import "theme/index.less"
|
||||||
|
|
||||||
// append method to array prototype
|
// append method to array prototype
|
||||||
@ -25,20 +22,6 @@ Array.prototype.move = function (from, to) {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
const SplashExtension = Splash.extension({
|
|
||||||
logo: config.logo.alt,
|
|
||||||
preset: "fadeOut",
|
|
||||||
velocity: 1000,
|
|
||||||
props: {
|
|
||||||
logo: {
|
|
||||||
style: {
|
|
||||||
marginBottom: "10%",
|
|
||||||
stroke: "black",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
class ThrowCrash {
|
class ThrowCrash {
|
||||||
constructor(message, description) {
|
constructor(message, description) {
|
||||||
this.message = message
|
this.message = message
|
||||||
@ -156,14 +139,14 @@ class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static staticRenders = {
|
static staticRenders = {
|
||||||
on404: (props) => {
|
NotFound: (props) => {
|
||||||
return <NotFound />
|
return <NotFound />
|
||||||
},
|
},
|
||||||
onRenderError: (props) => {
|
RenderError: (props) => {
|
||||||
return <RenderError {...props} />
|
return <RenderError {...props} />
|
||||||
},
|
},
|
||||||
initialization: () => {
|
initialization: () => {
|
||||||
return <Splash.SplashComponent logo={config.logo.alt} />
|
return <h1>Initializing...</h1>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +247,7 @@ class App {
|
|||||||
user={this.state.user}
|
user={this.state.user}
|
||||||
session={this.state.session}
|
session={this.state.session}
|
||||||
>
|
>
|
||||||
<Render.RenderController staticRenders={App.staticRenders} />
|
<Render.RenderRouter staticRenders={App.staticRenders} />
|
||||||
</BindPropsProvider>
|
</BindPropsProvider>
|
||||||
</div>
|
</div>
|
||||||
</antd.Layout.Content>
|
</antd.Layout.Content>
|
||||||
@ -277,5 +260,5 @@ class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default CreateEviteApp(App, {
|
export default CreateEviteApp(App, {
|
||||||
extensions: [Sound.extension, Render.extension, theme, API, SplashExtension, Debug],
|
extensions: [Sound.extension, Render.extension, API, Debug],
|
||||||
})
|
})
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
packages/app/src/assets/sounds/crash.wav
Normal file
5
packages/app/src/assets/sounds/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import crash from './crash.wav'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
"crash": crash
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './index.less'
|
|
||||||
import { clientInfo, GUID, package_json } from 'core'
|
|
||||||
import { Monitor, Package, Radio, Layers } from 'components/Icons'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
|
|
||||||
export default class App_About extends React.Component {
|
|
||||||
renderStableTag() {
|
|
||||||
return <antd.Tag color={clientInfo.buildStable? "blue" : "orange"}>{clientInfo.buildStable? "Stable" : "Not Stable"}</antd.Tag>
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.aboutWrapper}>
|
|
||||||
<img src={clientInfo.logo} />
|
|
||||||
<antd.Card>
|
|
||||||
<h1 className={styles.appName}> {clientInfo.siteName} </h1>
|
|
||||||
{GUID}
|
|
||||||
<br />
|
|
||||||
<antd.Tag color="green"><Monitor />{clientInfo.os.toString()}</antd.Tag>
|
|
||||||
<antd.Tag color="geekblue"><Package />v{clientInfo.version}</antd.Tag>
|
|
||||||
<antd.Tag color="red"><Radio />{clientInfo.packageStage}</antd.Tag>
|
|
||||||
<antd.Tag color="magenta"><Layers />Render with {clientInfo.layout}</antd.Tag>
|
|
||||||
{this.renderStableTag()}
|
|
||||||
|
|
||||||
</antd.Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
.aboutWrapper {
|
|
||||||
margin: auto;
|
|
||||||
max-width: 70vw;
|
|
||||||
width: 450px;
|
|
||||||
vertical-align: middle;
|
|
||||||
position: relative;
|
|
||||||
background-color: rgba(73, 72, 72, 0.349);
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.appName {
|
|
||||||
font-family: "Poppins", sans-serif;
|
|
||||||
font-size: 27px;
|
|
||||||
}
|
|
72
packages/app/src/components/AboutApp/index.jsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React from "react"
|
||||||
|
import ReactDOM from "react-dom"
|
||||||
|
|
||||||
|
import * as antd from "antd"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
import config from "config"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
export class AboutApp extends React.Component {
|
||||||
|
state = {
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.setState({ visible: false })
|
||||||
|
|
||||||
|
if (typeof this.props.onClose === "function") {
|
||||||
|
this.props.onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const eviteNamespace = window.__evite
|
||||||
|
const appConfig = config.app ?? {}
|
||||||
|
const isDevMode = eviteNamespace.env.NODE_ENV !== "production"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<antd.Modal
|
||||||
|
destroyOnClose
|
||||||
|
onCancel={this.onClose}
|
||||||
|
visible={this.state.visible}
|
||||||
|
centered
|
||||||
|
footer={false}
|
||||||
|
width="80%"
|
||||||
|
>
|
||||||
|
<div className="about_app_wrapper">
|
||||||
|
<div className="about_app_header">
|
||||||
|
<div>
|
||||||
|
<img src={config.logo.alt} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>{appConfig.siteName}</h1>
|
||||||
|
<div>
|
||||||
|
<antd.Tag>
|
||||||
|
<Icons.Tag />v{eviteNamespace.projectVersion}
|
||||||
|
</antd.Tag>
|
||||||
|
<antd.Tag color="geekblue">eVite v{eviteNamespace.eviteVersion}</antd.Tag>
|
||||||
|
<antd.Tag color="green">
|
||||||
|
<Icons.Hexagon /> v{eviteNamespace.versions.node}
|
||||||
|
</antd.Tag>
|
||||||
|
|
||||||
|
<antd.Tag color={isDevMode ? "magenta" : "green"}>
|
||||||
|
{isDevMode ? <Icons.Triangle /> : <Icons.CheckCircle />}
|
||||||
|
{isDevMode ? "development" : "stable"}
|
||||||
|
</antd.Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="about_app_info"></div>
|
||||||
|
</div>
|
||||||
|
</antd.Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openModal() {
|
||||||
|
const component = document.createElement("div")
|
||||||
|
document.body.appendChild(component)
|
||||||
|
|
||||||
|
ReactDOM.render(<AboutApp />, component)
|
||||||
|
}
|
36
packages/app/src/components/AboutApp/index.less
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
.about_app_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.about_app_header {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
margin: 20px 20px 80px 20px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: fit-content;
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
max-height: 200px;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.about_app_info {
|
||||||
|
|
||||||
|
}
|
14
packages/app/src/components/ActionsBar/index.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Card } from 'antd'
|
||||||
|
|
||||||
|
import './index.less'
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const { children } = props
|
||||||
|
|
||||||
|
return <Card style={props.style} className="actionsBar_card">
|
||||||
|
<div style={props.wrapperStyle} className="actionsBar_flexWrapper">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
}
|
15
packages/app/src/components/ActionsBar/index.less
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.actionsBar_card {
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionsBar_flexWrapper{
|
||||||
|
transition: all 200ms ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
9
packages/app/src/components/AppLoading/index.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { LoadingOutlined } from "@ant-design/icons"
|
||||||
|
import { Result } from "antd"
|
||||||
|
|
||||||
|
export default (props = {}) => {
|
||||||
|
return <div>
|
||||||
|
<Result title={props.title ?? "Loading"} icon={<LoadingOutlined spin />} />
|
||||||
|
</div>
|
||||||
|
}
|
62
packages/app/src/components/AppSearcher/index.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
class Results extends React.Component {
|
||||||
|
state = {
|
||||||
|
results: this.props.results ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResults = () => {
|
||||||
|
return this.state.results.map(result => {
|
||||||
|
return <div id={result.id}>
|
||||||
|
{result.title}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div>
|
||||||
|
{this.renderResults()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AppSearcher extends React.Component {
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
searchResult: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearch = (value) => {
|
||||||
|
let results = []
|
||||||
|
|
||||||
|
// get results
|
||||||
|
console.log(value)
|
||||||
|
results.push({ id: value, title: value })
|
||||||
|
|
||||||
|
// storage results
|
||||||
|
this.setState({ searchResult: results })
|
||||||
|
|
||||||
|
// open results onlayout drawer
|
||||||
|
this.openResults()
|
||||||
|
}
|
||||||
|
|
||||||
|
openResults = () => {
|
||||||
|
window.app.SidedrawerController.render(() => <Results results={this.state.searchResult} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<antd.Input.Search
|
||||||
|
style={{ width: this.props.width }}
|
||||||
|
className="search_bar"
|
||||||
|
placeholder="Search on app..."
|
||||||
|
loading={this.state.loading}
|
||||||
|
onSearch={this.handleSearch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
33
packages/app/src/components/AppSearcher/index.less
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@import "theme/index.less";
|
||||||
|
|
||||||
|
.search_bar {
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
height: fit-content;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 7px !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
background-color: @app_background_accent!important;
|
||||||
|
border-color: @app_background_accent!important;
|
||||||
|
color: @app_background_contrast!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-group-addon {
|
||||||
|
width: fit-content;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
background-color: #eeeeee!important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import { LoadingOutlined } from 'components/Icons'
|
|
||||||
|
|
||||||
interface CardComponent_props {
|
|
||||||
style: object;
|
|
||||||
type: string;
|
|
||||||
children: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CardComponent = (props: CardComponent_props) => {
|
|
||||||
let frag;
|
|
||||||
const rd_error = <antd.Result status="error" title="Failed Gathering, reload the page" />
|
|
||||||
const rd_loading = <LoadingOutlined spin />
|
|
||||||
|
|
||||||
if (props.type == "error") frag = (rd_error)
|
|
||||||
if (props.type == "skeleton") frag = (rd_loading)
|
|
||||||
if (!props.type) frag = (props.children)
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div {...props} style={props.style} className="cardComponent_wrapper">
|
|
||||||
{frag}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CardComponent.defaultProps = {
|
|
||||||
style: null,
|
|
||||||
type: null,
|
|
||||||
children: <h3>Empty</h3>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CardComponent
|
|
166
packages/app/src/components/ElementsList/index.jsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import { RefreshCw } from "feather-reactjs"
|
||||||
|
import { getCircularReplacer, decycle } from "@corenode/utils"
|
||||||
|
|
||||||
|
const serializeFlags = {
|
||||||
|
__cycle_flag: true, // with id 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFlagId(e, id) {
|
||||||
|
return serializeFlags[Object.keys(e)[id ?? 0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseError = (error) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "12px 16px",
|
||||||
|
height: "47px",
|
||||||
|
backgroundColor: "#d9d9d9",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
This could not be rendered
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<antd.Collapse>
|
||||||
|
<antd.Collapse.Panel header="See error">
|
||||||
|
<div style={{ margin: "0 5px 15px 5px", wordBreak: "break-all" }}>
|
||||||
|
<span>{error.toString()}</span>
|
||||||
|
</div>
|
||||||
|
</antd.Collapse.Panel>
|
||||||
|
</antd.Collapse>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseDecorator = (data, json) => {
|
||||||
|
const type = typeof data
|
||||||
|
console.log(type)
|
||||||
|
switch (type) {
|
||||||
|
case "string": {
|
||||||
|
return `(${json.length}) characters`
|
||||||
|
}
|
||||||
|
case "object": {
|
||||||
|
if (data == null) {
|
||||||
|
return `Empty (null/undefined)`
|
||||||
|
}
|
||||||
|
if (isFlagId(data, 0)) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<RefreshCw /> Cylic
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof data.length !== "undefined") {
|
||||||
|
return `Length (${data.length})`
|
||||||
|
}
|
||||||
|
if (typeof Object.keys(data).length !== "undefined") {
|
||||||
|
return `Length (${Object.keys(data).length})`
|
||||||
|
}
|
||||||
|
return `Immeasurable`
|
||||||
|
}
|
||||||
|
case "array": {
|
||||||
|
return `Length (${data})`
|
||||||
|
}
|
||||||
|
case "boolean": {
|
||||||
|
return <antd.Tag color={data ? "blue" : "volcano"}> {data ? "true" : "false"} </antd.Tag>
|
||||||
|
}
|
||||||
|
case "number": {
|
||||||
|
return <antd.Tag> {data} </antd.Tag>
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return `Immeasurable / Invalid`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseData = (data) => {
|
||||||
|
try {
|
||||||
|
switch (typeof data) {
|
||||||
|
case "object": {
|
||||||
|
if (data == null) {
|
||||||
|
return `${data}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFlagId(data, 0)) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "12px 16px",
|
||||||
|
height: "47px",
|
||||||
|
backgroundColor: "#d9d9d9",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RefreshCw /> Circular
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(data).length > 0) {
|
||||||
|
return <div>{ElementList(data)}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(data, getCircularReplacer())
|
||||||
|
}
|
||||||
|
case "array": {
|
||||||
|
return JSON.stringify(data, getCircularReplacer())
|
||||||
|
}
|
||||||
|
case "boolean": {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return `${data}`
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return parseError(data, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseType = (data) => {
|
||||||
|
if (data !== null && isFlagId(data, 0)) {
|
||||||
|
return `[loop]`
|
||||||
|
}
|
||||||
|
return `[${typeof data}]`
|
||||||
|
}
|
||||||
|
|
||||||
|
const excludedTypesFromContent = ["boolean"]
|
||||||
|
|
||||||
|
export default function ElementList(data) {
|
||||||
|
if (!data) return false
|
||||||
|
|
||||||
|
data = decycle(data)
|
||||||
|
const keys = Object.keys(data)
|
||||||
|
|
||||||
|
return keys.map((key) => {
|
||||||
|
const value = data[key]
|
||||||
|
const content = parseData(value)
|
||||||
|
const type = parseType(value)
|
||||||
|
const decorator = parseDecorator(value, content)
|
||||||
|
|
||||||
|
const header = (
|
||||||
|
<div>
|
||||||
|
{type} <strong>{key}</strong> | {decorator}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<antd.Collapse ghost expandIconPosition="right" bordered="false" style={{ border: "0px" }} key={key}>
|
||||||
|
{excludedTypesFromContent.includes(typeof value) ? (
|
||||||
|
<antd.Collapse.Panel key={key} header={header} />
|
||||||
|
) : (
|
||||||
|
<antd.Collapse.Panel key={key} header={header}>
|
||||||
|
<div style={{ margin: "0 5px 15px 5px", wordBreak: "break-all" }}>
|
||||||
|
<span>{content}</span>
|
||||||
|
</div>
|
||||||
|
</antd.Collapse.Panel>
|
||||||
|
)}
|
||||||
|
</antd.Collapse>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default as Operations } from './operations'
|
@ -0,0 +1,37 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const api = window.app.apiBridge
|
||||||
|
|
||||||
|
export default class Operations extends React.Component {
|
||||||
|
state = {
|
||||||
|
list: []
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
await this.loadOperations()
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOperations = async () => {
|
||||||
|
const operations = await api.get.operations()
|
||||||
|
console.log(operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem = (item) => {
|
||||||
|
console.log(item)
|
||||||
|
|
||||||
|
return <antd.List.Item>
|
||||||
|
{item}
|
||||||
|
</antd.List.Item>
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div className="operations">
|
||||||
|
<antd.List
|
||||||
|
dataSource={this.state.list}
|
||||||
|
renderItem={this.renderItem}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
299
packages/app/src/components/FabricCreator/index.jsx
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import * as antd from 'antd'
|
||||||
|
import { Icons as FIcons, createIconRender } from "components/Icons"
|
||||||
|
import * as MDIcons from "react-icons/md"
|
||||||
|
|
||||||
|
const Icons = {
|
||||||
|
...FIcons,
|
||||||
|
...MDIcons
|
||||||
|
}
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const FormComponents = {
|
||||||
|
"input": antd.Input,
|
||||||
|
"textarea": antd.Input.TextArea,
|
||||||
|
"select": antd.Select,
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIELDS
|
||||||
|
const FieldsForms = {
|
||||||
|
description: {
|
||||||
|
label: "Description",
|
||||||
|
component: "input",
|
||||||
|
updateEvent: "onChange",
|
||||||
|
onUpdate: (update) => {
|
||||||
|
return update.target.value
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
minWidth: "300px",
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
placeholder: "Describe something...",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
operations: {
|
||||||
|
label: "Operations",
|
||||||
|
component: "input",
|
||||||
|
updateEvent: "onChange",
|
||||||
|
onUpdate: (update) => {
|
||||||
|
return update.target.value
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vaultItemTypeSelector: {
|
||||||
|
label: "Type",
|
||||||
|
component: "select",
|
||||||
|
updateEvent: "onChange",
|
||||||
|
props: {
|
||||||
|
placeholder: "Select a type",
|
||||||
|
children: [
|
||||||
|
<antd.Select.OptGroup label="Computers">
|
||||||
|
<antd.Select.Option value="computers-desktop">Desktop</antd.Select.Option>
|
||||||
|
<antd.Select.Option value="computers-laptop">Laptop</antd.Select.Option>
|
||||||
|
<antd.Select.Option value="computers-phone">Phone</antd.Select.Option>
|
||||||
|
<antd.Select.Option value="computers-tablet">Tablet</antd.Select.Option>
|
||||||
|
<antd.Select.Option value="computers-other">Other</antd.Select.Option>
|
||||||
|
</antd.Select.OptGroup>,
|
||||||
|
<antd.Select.OptGroup label="Peripherals">
|
||||||
|
<antd.Select.Option value="peripherals-monitor">Monitor</antd.Select.Option>
|
||||||
|
<antd.Select.Option value="peripherals-printer">Printer</antd.Select.Option>
|
||||||
|
</antd.Select.OptGroup>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//FORMULAS
|
||||||
|
const ProductFormula = {
|
||||||
|
defaultFields: [
|
||||||
|
"description",
|
||||||
|
"operations",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const OperationFormula = {
|
||||||
|
defaultFields: [
|
||||||
|
"description",
|
||||||
|
"task",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const PhaseFormula = {
|
||||||
|
defaultFields: [
|
||||||
|
"description",
|
||||||
|
"task",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaskFormula = {
|
||||||
|
defaultFields: [
|
||||||
|
"description",
|
||||||
|
"tasks",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const VaultItemFormula = {
|
||||||
|
defaultFields: [
|
||||||
|
"vaultItemTypeSelector",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const FORMULAS = {
|
||||||
|
product: ProductFormula,
|
||||||
|
operation: OperationFormula,
|
||||||
|
phase: PhaseFormula,
|
||||||
|
task: TaskFormula,
|
||||||
|
vaultItem: VaultItemFormula,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPES
|
||||||
|
const FabricItemTypesIcons = {
|
||||||
|
"product": "Box",
|
||||||
|
"operation": "Settings",
|
||||||
|
"phase": "GitCommit",
|
||||||
|
"task": "Tool",
|
||||||
|
"vaultItem": "Archive",
|
||||||
|
}
|
||||||
|
|
||||||
|
const FabricItemTypes = ["product", "operation", "phase", "task", "vaultItem"]
|
||||||
|
|
||||||
|
export default class FabricCreator extends React.Component {
|
||||||
|
state = {
|
||||||
|
loading: true,
|
||||||
|
values: {},
|
||||||
|
|
||||||
|
fields: [],
|
||||||
|
|
||||||
|
name: null,
|
||||||
|
type: null,
|
||||||
|
uuid: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
await this.setItemType("product")
|
||||||
|
this.setState({ loading: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
clearValues = async () => {
|
||||||
|
await this.setState({ values: {} })
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFields = async () => {
|
||||||
|
await this.setState({ fields: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
setItemType = async (type) => {
|
||||||
|
const formulaKeys = Object.keys(FORMULAS)
|
||||||
|
|
||||||
|
if (formulaKeys.includes(type)) {
|
||||||
|
const formula = FORMULAS[type]
|
||||||
|
|
||||||
|
await this.clearValues()
|
||||||
|
await this.clearFields()
|
||||||
|
|
||||||
|
formula.defaultFields.forEach(field => {
|
||||||
|
this.appendFieldByType(field)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({ type: type, name: "New item" })
|
||||||
|
} else {
|
||||||
|
console.error(`Cannot load default fields from formula with type ${type}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appendFieldByType = (fieldType) => {
|
||||||
|
const form = FieldsForms[fieldType]
|
||||||
|
|
||||||
|
if (typeof form === "undefined") {
|
||||||
|
console.error(`No form available for field [${fieldType}]`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = this.state.fields
|
||||||
|
fields.push(this.generateFieldRender({ type: fieldType, ...form }))
|
||||||
|
|
||||||
|
this.setState({ fields: fields })
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFieldSelectorMenu = () => {
|
||||||
|
return <antd.Menu
|
||||||
|
onClick={(e) => {
|
||||||
|
this.appendFieldByType(e.key)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(FieldsForms).map((field) => {
|
||||||
|
const form = FieldsForms[field]
|
||||||
|
const icon = form.icon && createIconRender(form.icon)
|
||||||
|
|
||||||
|
return <antd.Menu.Item key={field}>
|
||||||
|
{icon ?? null}
|
||||||
|
{field.charAt(0).toUpperCase() + field.slice(1)}
|
||||||
|
</antd.Menu.Item>
|
||||||
|
})}
|
||||||
|
</antd.Menu>
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTypeMenuSelector = () => {
|
||||||
|
return <antd.Menu
|
||||||
|
onClick={(e) => {
|
||||||
|
this.setItemType(e.key)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{FabricItemTypes.map((type) => {
|
||||||
|
const TypeIcon = FabricItemTypesIcons[type] && createIconRender(FabricItemTypesIcons[type])
|
||||||
|
|
||||||
|
return <antd.Menu.Item key={type}>
|
||||||
|
{TypeIcon ?? null}
|
||||||
|
{type.charAt(0).toUpperCase() + type.slice(1)}
|
||||||
|
</antd.Menu.Item>
|
||||||
|
})}
|
||||||
|
</antd.Menu>
|
||||||
|
}
|
||||||
|
|
||||||
|
onDone = () => {
|
||||||
|
console.log(this.getValues())
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateValue = (event, value) => {
|
||||||
|
const { updateEvent, key } = event
|
||||||
|
|
||||||
|
let state = this.state
|
||||||
|
state.values[key] = value
|
||||||
|
|
||||||
|
this.setState(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeField = (key) => {
|
||||||
|
this.setState({ fields: this.state.fields.filter(field => field.key != key) })
|
||||||
|
}
|
||||||
|
|
||||||
|
getValues = () => {
|
||||||
|
return this.state.fields.map((field) => {
|
||||||
|
return {
|
||||||
|
type: field.props.type,
|
||||||
|
value: this.state.values[field.key],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
generateFieldRender = (field) => {
|
||||||
|
let { key, style, type, icon, component, label, updateEvent, props, onUpdate } = field
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
key = this.state.fields.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof FormComponents[component] === "undefined") {
|
||||||
|
console.error(`No component type available for field [${key}]`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div key={key} id={`${type}-${key}`} type={type} className="field" style={style}>
|
||||||
|
<div className="close" onClick={() => { this.removeField(key) }}><Icons.X /></div>
|
||||||
|
<h4>{icon && createIconRender(icon)}{label}</h4>
|
||||||
|
<div className="fieldContent">
|
||||||
|
{React.createElement(FormComponents[component], {
|
||||||
|
...props,
|
||||||
|
value: this.state.values[key],
|
||||||
|
[updateEvent]: (...args) => {
|
||||||
|
if (typeof onUpdate === "function") {
|
||||||
|
return this.onUpdateValue({ updateEvent, key }, onUpdate(...args))
|
||||||
|
}
|
||||||
|
return this.onUpdateValue({ updateEvent, key }, ...args)
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.loading) {
|
||||||
|
return <antd.Skeleton active />
|
||||||
|
}
|
||||||
|
|
||||||
|
const TypeIcon = FabricItemTypesIcons[this.state.type] && createIconRender(FabricItemTypesIcons[this.state.type])
|
||||||
|
|
||||||
|
return <div className="fabric_creator">
|
||||||
|
<div key="name" className="name">
|
||||||
|
<div className="type">
|
||||||
|
<antd.Dropdown trigger={['click']} overlay={this.renderTypeMenuSelector}>
|
||||||
|
{TypeIcon ?? <Icons.HelpCircle />}
|
||||||
|
</antd.Dropdown>
|
||||||
|
</div>
|
||||||
|
<antd.Input defaultValue={this.state.name} />
|
||||||
|
</div>
|
||||||
|
<div className="fields">
|
||||||
|
<div className="wrap">
|
||||||
|
{this.state.fields}
|
||||||
|
</div>
|
||||||
|
<div className="bottom_actions">
|
||||||
|
<antd.Dropdown trigger={['click']} placement="topCenter" overlay={this.renderFieldSelectorMenu}>
|
||||||
|
<Icons.Plus />
|
||||||
|
</antd.Dropdown>
|
||||||
|
<antd.Button onClick={this.onDone}>Done</antd.Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
115
packages/app/src/components/FabricCreator/index.less
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
.fabric_creator {
|
||||||
|
.name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 45px;
|
||||||
|
|
||||||
|
height: fit-content;
|
||||||
|
line-height: 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: unset;
|
||||||
|
line-height: 0;
|
||||||
|
font-size: 45px;
|
||||||
|
height: fit-content;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
color: #5e5e5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin: 0!important;
|
||||||
|
padding-right: 0!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
|
border-radius: 10px;
|
||||||
|
height: fit-content;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type:hover{
|
||||||
|
background-color: antiquewhite;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.field {
|
||||||
|
margin: 10px;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px;
|
||||||
|
border: transparent dashed 1px;
|
||||||
|
|
||||||
|
transition: all 150ms ease-out;
|
||||||
|
|
||||||
|
min-width: 100px;
|
||||||
|
|
||||||
|
.fieldContent {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: #8d8d8dc4;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-85%, -100%);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field:hover {
|
||||||
|
border-color: #8d8d8dc4;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom_actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: 100px;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
font-size: 25px;
|
||||||
|
color: #5e5e5e;
|
||||||
|
border: transparent solid 1px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin: 0!important;
|
||||||
|
padding-right: 0!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom_actions:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
border-color: #5e5e5e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,73 +0,0 @@
|
|||||||
import { verbosity } from '@nodecorejs/utils'
|
|
||||||
import { XCircle } from 'components/Icons'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import React from 'react'
|
|
||||||
import { Rnd } from 'react-rnd'
|
|
||||||
import { getDvaApp } from 'umi'
|
|
||||||
import { Provider } from 'react-redux'
|
|
||||||
|
|
||||||
const renderDiv = document.createElement('div')
|
|
||||||
class FloatComponent extends React.Component {
|
|
||||||
handleClose() {
|
|
||||||
Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const renderProps = this.props.renderBox ?? { }
|
|
||||||
const defaultBoxWidth = renderProps.width ?? 500
|
|
||||||
const defaultBoxHeight = renderProps.height ?? 600
|
|
||||||
return (
|
|
||||||
<Rnd
|
|
||||||
default={{
|
|
||||||
x: ((window.innerWidth / 2) - defaultBoxWidth / 2 ),
|
|
||||||
y: ((window.innerHeight / 2) - defaultBoxHeight / 2 ),
|
|
||||||
width: defaultBoxWidth,
|
|
||||||
height: defaultBoxHeight,
|
|
||||||
}}
|
|
||||||
maxHeight="60vh"
|
|
||||||
style={{ overflowY: "scroll", overflowX: "hidden", zIndex: 1000 }}
|
|
||||||
{...this.props.renderBox}
|
|
||||||
>
|
|
||||||
<div style={{ top: 0, position: "sticky", borderRadius: "8px 8px 0 0", background: "rgba(0, 0, 0, 0.4)", width: "100%", height: "35px", display: "flex", alignItems: "center", color: "#fff" }}>
|
|
||||||
<div style={{ fontSize: "15px", color: "#fff", display: "flex", height: "100%", padding: "0 10px", alignItems: "center", marginRight: "5px" }}>
|
|
||||||
<XCircle onClick={this.handleClose} style={{ cursor: "pointer" }} />
|
|
||||||
</div>
|
|
||||||
<div style={{ fontSize: "12px" }}>
|
|
||||||
{this.props.title ?? null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{this.props.children}
|
|
||||||
</Rnd>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Destroy() {
|
|
||||||
verbosity.log('destroying')
|
|
||||||
const unmountResult = ReactDOM.unmountComponentAtNode(renderDiv)
|
|
||||||
if (unmountResult && renderDiv.parentNode) {
|
|
||||||
renderDiv.parentNode.removeChild(renderDiv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function Open(props) {
|
|
||||||
const dvaApp = getDvaApp()
|
|
||||||
const divId = props.id ?? "floatComponent"
|
|
||||||
const MountParent = document.getElementById("root")
|
|
||||||
const thisChild = document.getElementById(divId)
|
|
||||||
|
|
||||||
verbosity.log(props)
|
|
||||||
|
|
||||||
if (thisChild) {
|
|
||||||
MountParent.removeChild(thisChild)
|
|
||||||
}
|
|
||||||
|
|
||||||
let RenderComponent = <FloatComponent {...props} />
|
|
||||||
MountParent.appendChild(renderDiv).setAttribute('id', divId)
|
|
||||||
ReactDOM.render(<Provider store={dvaApp._store}>{RenderComponent}</Provider>, renderDiv)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Open
|
|
@ -1,13 +0,0 @@
|
|||||||
const marginedStyle = { width: "1em", height: "1em", marginRight: "10px", verticalAlign: "-0.125em" }
|
|
||||||
|
|
||||||
export const verifiedBadge = () => <svg style={marginedStyle} xmlns="http://www.w3.org/2000/svg" fill="#55acee" 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 lightningBolt = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
|
||||||
export const sparkles = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" /></svg>
|
|
||||||
export const statusOnline = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" /></svg>
|
|
||||||
export const fingerprint = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4" /></svg>
|
|
||||||
export const colorSwatch = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" /></svg>
|
|
||||||
export const collection = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /></svg>
|
|
||||||
export const cubeTransparent = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5" /></svg>
|
|
||||||
export const keyRound = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>
|
|
||||||
export const searchCircles = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16l2.879-2.879m0 0a3 3 0 104.243-4.242 3 3 0 00-4.243 4.242zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
||||||
export const template = () => <svg style={marginedStyle} className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" /></svg>
|
|
@ -1,4 +1,22 @@
|
|||||||
export * from 'feather-reactjs'
|
import React from "react"
|
||||||
export * from '@ant-design/icons'
|
|
||||||
export * from './custom'
|
// import icons lib
|
||||||
export * from '@icons-pack/react-simple-icons'
|
import * as lib1 from "feather-reactjs"
|
||||||
|
import * as lib2 from "react-icons/md"
|
||||||
|
import * as lib3 from "@ant-design/icons"
|
||||||
|
|
||||||
|
export const Icons = {
|
||||||
|
...lib1,
|
||||||
|
...lib2,
|
||||||
|
...lib3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createIconRender(icon, props) {
|
||||||
|
if (typeof Icons[icon] !== "undefined") {
|
||||||
|
return React.createElement(Icons[icon], props)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Icons
|
@ -1,78 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import styles from './index.less'
|
|
||||||
import errNumbers from 'config/handlers/numToError.js'
|
|
||||||
import { Meh } from 'components/Icons'
|
|
||||||
|
|
||||||
const InvalidSkeleton = (props) => {
|
|
||||||
return(
|
|
||||||
<antd.Card className={styles.invalidSkeleton} bordered="false">
|
|
||||||
<antd.Skeleton active />
|
|
||||||
<antd.Result style={{
|
|
||||||
position: "absolute",
|
|
||||||
zIndex: "15",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
padding: "12px 24px"
|
|
||||||
}}>
|
|
||||||
Sorry but, something did not work as it should...
|
|
||||||
</antd.Result>
|
|
||||||
</antd.Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const InvalidSession = (props) => {
|
|
||||||
return(
|
|
||||||
<div className={styles.floatCardWrapper} bordered="false">
|
|
||||||
<antd.Result status="403" title="You need to login for view this!" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const InvalidIndex = (props) => {
|
|
||||||
return(
|
|
||||||
<div className={styles.floatCardWrapper} bordered="false">
|
|
||||||
<antd.Result>
|
|
||||||
Sorry but, We could not index this <antd.Tag style={{ marginLeft: "12px", lineHeight: "24px"}}>{props.messageProp1}</antd.Tag>
|
|
||||||
</antd.Result>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Custom = (props) => {
|
|
||||||
return(
|
|
||||||
<div className={styles.floatCardWrapper} style={props.style ?? null} >
|
|
||||||
<antd.Result icon={props.icon ?? null} status={props.status ?? "info"} title={props.title ?? ""}>
|
|
||||||
{props.message}
|
|
||||||
</antd.Result>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Invalid extends React.Component{
|
|
||||||
render(){
|
|
||||||
const Components = {
|
|
||||||
SESSION_INVALID: <InvalidSession />,
|
|
||||||
INVALID_INDEX: <InvalidIndex {...this.props} />,
|
|
||||||
skeleton: <InvalidSkeleton {...this.props} />,
|
|
||||||
custom: <Custom {...this.props} />
|
|
||||||
}
|
|
||||||
const { type, typeByCode } = this.props
|
|
||||||
if (type != null || typeByCode != null) {
|
|
||||||
let tmpType = null
|
|
||||||
|
|
||||||
type? tmpType = type : null
|
|
||||||
typeByCode? tmpType = errNumbers[typeByCode] : null
|
|
||||||
|
|
||||||
if (Components[tmpType] != null) {
|
|
||||||
return Components[tmpType]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return <Custom
|
|
||||||
icon={<Meh style={{ fontSize: "100px" }} />}
|
|
||||||
title="A function called this component due to an error, but apparently it also caused an error when configuring these parameters."
|
|
||||||
message="it seems that someone is not having a good day"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
.invalidSkeleton{
|
|
||||||
:global{
|
|
||||||
.ant-card{
|
|
||||||
border-radius: 7px;
|
|
||||||
}
|
|
||||||
.ant-card-body{
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.ant-skeleton{
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.floatCardWrapper{
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
.contextualMenu {
|
|
||||||
position: absolute;
|
|
||||||
background-color: rgba(36, 36, 36, 0.7);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 10px 5px;
|
|
||||||
z-index: 1000;
|
|
||||||
width: 250px;
|
|
||||||
height: auto;
|
|
||||||
color: #e3e3e3;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
font-size: 14px;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
> div{
|
|
||||||
transition: all 100ms linear;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0 0 0 10px;
|
|
||||||
color: #e3e3e3;
|
|
||||||
width: 100%;
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
> div:hover{
|
|
||||||
background-color: #e3e3e3;
|
|
||||||
color: rgba(36, 36, 36, 0.7);
|
|
||||||
}
|
|
||||||
> div:active{
|
|
||||||
transform: scale(0.98);
|
|
||||||
filter: brightness(110%);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
import { verbosity } from '@nodecorejs/utils'
|
|
||||||
import styles from './index.less'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
let onRend = false
|
|
||||||
const renderDiv = document.createElement('div')
|
|
||||||
export interface ContextMenuComponent_props {
|
|
||||||
renderList: any;
|
|
||||||
yPos: number;
|
|
||||||
xPos: number;
|
|
||||||
app: any;
|
|
||||||
dispatch: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContextMenuComponent extends React.PureComponent<ContextMenuComponent_props>{
|
|
||||||
listening: boolean
|
|
||||||
wrapperRef: any
|
|
||||||
eventListener: () => void
|
|
||||||
renderDiv: HTMLDivElement
|
|
||||||
state: any
|
|
||||||
|
|
||||||
constructor(props:any){
|
|
||||||
super(props)
|
|
||||||
this.state = {
|
|
||||||
renderList: null,
|
|
||||||
loading: true
|
|
||||||
}
|
|
||||||
|
|
||||||
this.renderDiv = renderDiv
|
|
||||||
this.listening = false
|
|
||||||
this.setWrapperRef = this.setWrapperRef.bind(this)
|
|
||||||
this.handleClickOutside = this.handleClickOutside.bind(this)
|
|
||||||
|
|
||||||
this.eventListener = () => {
|
|
||||||
document.addEventListener('click', this.handleClickOutside, false)
|
|
||||||
this.listening = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setWrapperRef(node){
|
|
||||||
this.wrapperRef = node
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClickOutside(event) {
|
|
||||||
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
|
|
||||||
this.listening = false
|
|
||||||
DestroyContextMenu()
|
|
||||||
document.removeEventListener('click', this.eventListener, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filterArray(data: any[]) {
|
|
||||||
let tmp: any = []
|
|
||||||
return new Promise(resolve => {
|
|
||||||
data.forEach(async (element: { require: string; }) => {
|
|
||||||
if (typeof(element.require) !== 'undefined') {
|
|
||||||
const validRequire = await window.requireQuery(element.require)
|
|
||||||
validRequire? tmp.push(element) : null
|
|
||||||
}else{
|
|
||||||
tmp.push(element)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
resolve(tmp)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async queryMenu(data){
|
|
||||||
this.setState({ renderList: await this.filterArray(data), loading: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
handle(e:any, props:any){
|
|
||||||
if(!e || typeof(e) == 'undefined') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
typeof(e.onClick) !== 'undefined' && e.onClick ? e.onClick(props) : null
|
|
||||||
typeof(e.keepOnClick) !== 'undefined' && e.keepOnClick ? null : DestroyContextMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderElements(){
|
|
||||||
if (!Array.isArray(this.state.renderList)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return this.state.renderList.map((e:any) => {
|
|
||||||
return(
|
|
||||||
<div {...e.params.itemProps} onClick={() => this.handle(e.params, this.props)} key={e.key}>
|
|
||||||
{e.icon}{e.title}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(){
|
|
||||||
if (this.props.renderList) {
|
|
||||||
this.queryMenu(this.props.renderList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(){
|
|
||||||
!this.listening ? this.eventListener() : null
|
|
||||||
}
|
|
||||||
|
|
||||||
render(){
|
|
||||||
if (this.state.loading) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="contextualMenu"
|
|
||||||
ref={this.setWrapperRef}
|
|
||||||
className={styles.contextualMenu}
|
|
||||||
style={{
|
|
||||||
top: this.props.yPos,
|
|
||||||
left: this.props.xPos,
|
|
||||||
}}>
|
|
||||||
{this.renderElements()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DestroyContextMenu(){
|
|
||||||
verbosity.log('destroying')
|
|
||||||
const unmountResult = ReactDOM.unmountComponentAtNode(renderDiv)
|
|
||||||
if (unmountResult && renderDiv.parentNode) {
|
|
||||||
renderDiv.parentNode.removeChild(renderDiv)
|
|
||||||
onRend = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OpenContextMenu(props){
|
|
||||||
verbosity.log(props)
|
|
||||||
const renderComponent = React.createElement(ContextMenuComponent, props)
|
|
||||||
if (onRend) {
|
|
||||||
DestroyContextMenu()
|
|
||||||
}
|
|
||||||
document.body.appendChild(renderDiv).setAttribute('id', 'contextMenu')
|
|
||||||
ReactDOM.render(renderComponent, renderDiv)
|
|
||||||
onRend = true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default OpenContextMenu
|
|
@ -1,36 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './__searchBar.less'
|
|
||||||
import { newSearch } from "core/models/overlay"
|
|
||||||
|
|
||||||
export default class __searchBar extends React.Component {
|
|
||||||
state = {
|
|
||||||
value: '',
|
|
||||||
}
|
|
||||||
openSearcher = () => {
|
|
||||||
const { value } = this.state
|
|
||||||
if (value.length < 1) return false
|
|
||||||
if (value == /\s/) return false
|
|
||||||
newSearch({ keyword: value });
|
|
||||||
}
|
|
||||||
onChange = e => {
|
|
||||||
const { value } = e.target
|
|
||||||
this.setState({ value: value })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKey = (e) => {
|
|
||||||
if (e.key == 'Enter') {
|
|
||||||
this.openSearcher()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.search_bar}>
|
|
||||||
<input
|
|
||||||
placeholder="Search on Comty..."
|
|
||||||
onChange={this.onChange}
|
|
||||||
onKeyPress={this.handleKey}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.search_bar {
|
|
||||||
height: 24px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
input {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0!important;
|
|
||||||
outline: 0!important;
|
|
||||||
color: @__app_backgroundAccent;
|
|
||||||
padding: 0 0 0 48px;
|
|
||||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 56.966 56.966' fill='%23c1c7cd'%3e%3cpath d='M55.146 51.887L41.588 37.786A22.926 22.926 0 0046.984 23c0-12.682-10.318-23-23-23s-23 10.318-23 23 10.318 23 23 23c4.761 0 9.298-1.436 13.177-4.162l13.661 14.208c.571.593 1.339.92 2.162.92.779 0 1.518-.297 2.079-.837a3.004 3.004 0 00.083-4.242zM23.984 6c9.374 0 17 7.626 17 17s-7.626 17-17 17-17-7.626-17-17 7.626-17 17-17z'/%3e%3c/svg%3e");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 16px;
|
|
||||||
background-position: 0 48%;
|
|
||||||
font-family: @__app_secondaryFont;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 15px;
|
|
||||||
&::placeholder {
|
|
||||||
color: @__app_backgroundAccent;
|
|
||||||
}
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus{
|
|
||||||
background-position: 20px 48%;
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Feather } from 'components'
|
|
||||||
import styles from './__suggestions.less'
|
|
||||||
|
|
||||||
import {Card_Component} from '../index.js'
|
|
||||||
|
|
||||||
export default class __suggestions extends React.Component{
|
|
||||||
state = {
|
|
||||||
trendings: [],
|
|
||||||
loading: true
|
|
||||||
}
|
|
||||||
componentDidMount(){
|
|
||||||
const { data } = this.props
|
|
||||||
if(data){
|
|
||||||
this.setState({ trendings: data, loading: false })
|
|
||||||
}
|
|
||||||
if(!data){
|
|
||||||
this.setState({ loading: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(){
|
|
||||||
if (this.state.loading) return <Card_Component type="skeleton" />
|
|
||||||
return <Card_Component>
|
|
||||||
<div className={styles.suggestions_wrapper}>
|
|
||||||
<h2><Feather.Target /> Suggestions</h2>
|
|
||||||
</div>
|
|
||||||
</Card_Component>
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
.suggestions_wrapper{
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import { Feather } from 'components'
|
|
||||||
import styles from './__trendings.less'
|
|
||||||
|
|
||||||
import {Card_Component} from '../index.js'
|
|
||||||
|
|
||||||
export default class __trendings extends React.Component {
|
|
||||||
state = {
|
|
||||||
trendings: [],
|
|
||||||
loading: true
|
|
||||||
}
|
|
||||||
componentDidMount(){
|
|
||||||
const { data } = this.props
|
|
||||||
if(data){
|
|
||||||
this.setState({ trendings: data, loading: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render(){
|
|
||||||
if (this.state.loading) return <Card_Component type="skeleton" />
|
|
||||||
return <Card_Component><h2><Feather.Award /> Trending now</h2>
|
|
||||||
|
|
||||||
<div className={styles.trendings}>
|
|
||||||
<antd.List
|
|
||||||
dataSource={this.state.trendings}
|
|
||||||
renderItem={item=>(
|
|
||||||
<div className={styles.hash}>
|
|
||||||
<span>#{item.tag}</span>
|
|
||||||
<p> {item.trend_use_num} Posts</p>
|
|
||||||
</div>)}
|
|
||||||
/>
|
|
||||||
</div></Card_Component>
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.trendings{
|
|
||||||
word-break: break-all;
|
|
||||||
padding: 0 5px 0 10px;
|
|
||||||
height: auto;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
.hash{
|
|
||||||
margin: 5px 0 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
span{
|
|
||||||
margin: 0;
|
|
||||||
color: #2196F3;
|
|
||||||
font-size: 12px;
|
|
||||||
max-height: 35px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p{
|
|
||||||
color: #333;
|
|
||||||
font-size: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
import Primary from './layout/Primary.tsx'
|
|
||||||
|
|
||||||
export {
|
|
||||||
Primary
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import { LeftOutlined } from 'components/Icons'
|
|
||||||
|
|
||||||
export interface overlay_primary_props {
|
|
||||||
y?: number;
|
|
||||||
getRef: React.Ref<HTMLDivElement>;
|
|
||||||
isMobile: boolean;
|
|
||||||
fragment: any;
|
|
||||||
mode: string;
|
|
||||||
closable: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderExit = <antd.Button
|
|
||||||
className={window.classToStyle("overlay_backButton")}
|
|
||||||
type="ghost"
|
|
||||||
icon={<LeftOutlined />}
|
|
||||||
onClick={() => window.overlaySwap.close()}
|
|
||||||
> Back </antd.Button>
|
|
||||||
|
|
||||||
const overlay_primary = (props: overlay_primary_props) => {
|
|
||||||
const { element, mode, isMobile } = props
|
|
||||||
return (
|
|
||||||
<div focus="no_loose" className={window.classToStyle("overlay_content_body")}>
|
|
||||||
{props.mode === 'full' || props.mode === 'half' ? renderExit : null}
|
|
||||||
<React.Fragment>{element}</React.Fragment>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay_primary.defaultProps = {
|
|
||||||
mode: false,
|
|
||||||
element: null,
|
|
||||||
isMobile: false,
|
|
||||||
closable: true,
|
|
||||||
y: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default overlay_primary
|
|
@ -1,119 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { verbosity } from '@nodecorejs/utils'
|
|
||||||
import { connect } from 'umi'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
|
|
||||||
import { Primary } from './components'
|
|
||||||
import { objectToArrayMap } from '@nodecorejs/utils'
|
|
||||||
|
|
||||||
const includeAllowedProps = [ "size" ]
|
|
||||||
|
|
||||||
@connect(({ app }) => ({ app }))
|
|
||||||
export default class Overlay extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
this.state = {
|
|
||||||
loading: true,
|
|
||||||
}
|
|
||||||
this.setWrapperRef = this.setWrapperRef.bind(this)
|
|
||||||
this.handleClickOutside = this.handleClickOutside.bind(this)
|
|
||||||
this.keydownFilter = this.keydownFilter.bind(this)
|
|
||||||
|
|
||||||
window.overlaySwap = this.swap
|
|
||||||
}
|
|
||||||
|
|
||||||
swap = {
|
|
||||||
isOpen: () => {
|
|
||||||
return this.props.app.overlayActive
|
|
||||||
},
|
|
||||||
close: () => {
|
|
||||||
this.props.dispatch({
|
|
||||||
type: 'app/updateState',
|
|
||||||
payload: {
|
|
||||||
overlayActive: false,
|
|
||||||
overlayElement: null
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
open: (payload) => {
|
|
||||||
if (!payload) return false;
|
|
||||||
verbosity.log('Dispatching fragment =>', payload)
|
|
||||||
this.props.dispatch({
|
|
||||||
type: 'app/updateState',
|
|
||||||
payload: {
|
|
||||||
overlayActive: true,
|
|
||||||
overlayElement: payload
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keydownFilter(event) {
|
|
||||||
if (event.keyCode === 27) {
|
|
||||||
this.swap.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClickOutside(event) {
|
|
||||||
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
|
|
||||||
this.swap.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
if (this.props.app.overlayElement) {
|
|
||||||
document.addEventListener('keydown', this.keydownFilter, false)
|
|
||||||
document.addEventListener('mousedown', this.handleClickOutside)
|
|
||||||
} else {
|
|
||||||
document.removeEventListener('mousedown', this.handleClickOutside)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setWrapperRef(node) {
|
|
||||||
this.wrapperRef = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let props = {}
|
|
||||||
const { overlayElement, overlayActive } = this.props.app
|
|
||||||
|
|
||||||
const isOnMode = (mode) => {
|
|
||||||
if (!overlayActive || typeof (overlayElement.mode) == "undefined") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return overlayElement.mode === mode ? true : false
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderElement = () => {
|
|
||||||
if (overlayElement && overlayActive) {
|
|
||||||
return <Primary {...overlayElement} />
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
objectToArrayMap(overlayElement).forEach((e) => {
|
|
||||||
if (includeAllowedProps.includes(e.key)) {
|
|
||||||
props[e.key] = e.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
// terrible (⓿_⓿)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={props.size? { width: props.size } : null}
|
|
||||||
id="overlay"
|
|
||||||
ref={this.setWrapperRef}
|
|
||||||
focus="no_loose"
|
|
||||||
className={classnames(window.classToStyle("overlay_wrapper"), {
|
|
||||||
["full"]: isOnMode("full"),
|
|
||||||
["half"]: isOnMode("half"),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{renderElement()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import * as app from 'app'
|
|
||||||
|
|
||||||
export async function post(id,callback){
|
|
||||||
if(!id) return false
|
|
||||||
const payload = { post_id: id }
|
|
||||||
app.comty_post.get((err, response) => {
|
|
||||||
try {
|
|
||||||
return callback(JSON.parse(response)['post_data'])
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function comments(id,callback){
|
|
||||||
if(!id) return false
|
|
||||||
const payload = { post_id: id }
|
|
||||||
app.comty_post.get((err, response) => {
|
|
||||||
try {
|
|
||||||
return callback(JSON.parse(response)['post_comments'])
|
|
||||||
}catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function search(key,callback){
|
|
||||||
if (!key) return false
|
|
||||||
|
|
||||||
const payload = { key: key }
|
|
||||||
app.comty_search.keywords((err, response) => {
|
|
||||||
return callback(response)
|
|
||||||
}, payload)
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import { CardComponent } from 'components'
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<CardComponent>
|
|
||||||
Invalid Component
|
|
||||||
</CardComponent>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import router from 'core/libs/router'
|
|
||||||
import withConnector from 'core/libs/withConnector'
|
|
||||||
import { CardComponent } from 'components'
|
|
||||||
|
|
||||||
@withConnector
|
|
||||||
export default class ProfileCard extends React.Component {
|
|
||||||
render() {
|
|
||||||
const { session_data, session_valid, session_uuid } = this.props.app
|
|
||||||
|
|
||||||
if (session_valid) {
|
|
||||||
return(
|
|
||||||
<div className={window.classToStyle("right_sidebar_component")}>
|
|
||||||
<CardComponent onClick={() => router.goProfile(session_data["username"])} style={{ display: 'flex', lineHeight: '30px', wordBreak: 'break-all' }} >
|
|
||||||
<antd.Avatar src={session_data.avatar} shape="square" />
|
|
||||||
<div style={{ marginLeft: '10px' }}>
|
|
||||||
@{session_data.username}
|
|
||||||
<span style={{ fontSize: "11px" }}>#{session_uuid}</span>
|
|
||||||
</div>
|
|
||||||
</CardComponent>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './index.less'
|
|
||||||
import { newSearch } from "core/models/overlay"
|
|
||||||
|
|
||||||
export default class __searchBar extends React.Component {
|
|
||||||
state = {
|
|
||||||
value: '',
|
|
||||||
}
|
|
||||||
openSearcher = () => {
|
|
||||||
const { value } = this.state
|
|
||||||
if (value.length < 1) return false
|
|
||||||
if (value == /\s/) return false
|
|
||||||
newSearch({ keyword: value });
|
|
||||||
}
|
|
||||||
onChange = e => {
|
|
||||||
const { value } = e.target
|
|
||||||
this.setState({ value: value })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKey = (e) => {
|
|
||||||
if (e.key == 'Enter') {
|
|
||||||
this.openSearcher()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.search_bar}>
|
|
||||||
<input
|
|
||||||
placeholder="Search on Comty..."
|
|
||||||
onChange={this.onChange}
|
|
||||||
onKeyPress={this.handleKey}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.search_bar {
|
|
||||||
height: 24px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
input {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0!important;
|
|
||||||
outline: 0!important;
|
|
||||||
color: @__app_backgroundAccent;
|
|
||||||
padding: 0 0 0 48px;
|
|
||||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 56.966 56.966' fill='%23c1c7cd'%3e%3cpath d='M55.146 51.887L41.588 37.786A22.926 22.926 0 0046.984 23c0-12.682-10.318-23-23-23s-23 10.318-23 23 10.318 23 23 23c4.761 0 9.298-1.436 13.177-4.162l13.661 14.208c.571.593 1.339.92 2.162.92.779 0 1.518-.297 2.079-.837a3.004 3.004 0 00.083-4.242zM23.984 6c9.374 0 17 7.626 17 17s-7.626 17-17 17-17-7.626-17-17 7.626-17 17-17z'/%3e%3c/svg%3e");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 16px;
|
|
||||||
background-position: 0 48%;
|
|
||||||
font-family: @__app_secondaryFont;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 15px;
|
|
||||||
&::placeholder {
|
|
||||||
color: @__app_backgroundAccent;
|
|
||||||
}
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus{
|
|
||||||
background-position: 20px 48%;
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import withConnector from 'core/libs/withConnector'
|
|
||||||
import { objectToArrayMap } from '@nodecorejs/utils'
|
|
||||||
|
|
||||||
import InvalidComponent from './components/invalid'
|
|
||||||
import ProfileCard from './components/profileCard'
|
|
||||||
import SearchBar from './components/searchBar'
|
|
||||||
|
|
||||||
const MapToComponent = {
|
|
||||||
profileCard: <ProfileCard />,
|
|
||||||
searchBar: <SearchBar />
|
|
||||||
}
|
|
||||||
|
|
||||||
// to do: add order by numeric range
|
|
||||||
let DefaultElements = [
|
|
||||||
"searchBar",
|
|
||||||
"profileCard"
|
|
||||||
]
|
|
||||||
|
|
||||||
@withConnector
|
|
||||||
export default class RightSider extends React.Component {
|
|
||||||
|
|
||||||
state = {
|
|
||||||
fragments: []
|
|
||||||
}
|
|
||||||
|
|
||||||
renderElements() {
|
|
||||||
try {
|
|
||||||
return this.state.fragments.map((element) => {
|
|
||||||
return <div key={element.id}>
|
|
||||||
{element.fragment ?? null}
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
return <InvalidComponent />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (typeof (window.rightSidebar) == "undefined") {
|
|
||||||
window.RightSider = {}
|
|
||||||
}
|
|
||||||
window.RightSider.addFragment = (fragment) => {
|
|
||||||
let updated = this.state.fragments
|
|
||||||
updated.push(fragment)
|
|
||||||
this.setState({ fragments: updated })
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultElements.forEach((e) => {
|
|
||||||
window.RightSider.addFragment({ id: e, fragment: MapToComponent[e] })
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="right_sidebar"
|
|
||||||
className={classnames(window.classToStyle("right_sidebar_wrapper"), {["swapped"]: this.props.app.overlayActive ?? false})}
|
|
||||||
>
|
|
||||||
<div className={window.classToStyle("right_sidebar_content")}>
|
|
||||||
{this.renderElements()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import * as Icons from 'components/Icons'
|
|
||||||
import styles from './index.less'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import { connect } from 'umi'
|
|
||||||
import { objectToArrayMap, queryObjectToString } from 'core'
|
|
||||||
|
|
||||||
@connect(({ app }) => ({ app }))
|
|
||||||
export default class Sider_Default extends React.Component {
|
|
||||||
state = {
|
|
||||||
type: "desktop",
|
|
||||||
loading: true,
|
|
||||||
menus: null
|
|
||||||
}
|
|
||||||
|
|
||||||
toogleCollapse() {
|
|
||||||
window.toogleSidebarCollapse()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.setState({ menus: this.props.menus, loading: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMenus(data, position) {
|
|
||||||
if (!position) return null
|
|
||||||
return data.map(e => {
|
|
||||||
if (!e.attributes) e.attributes = {}
|
|
||||||
let componentPosition = e.attributes.position || "top"
|
|
||||||
|
|
||||||
return componentPosition == position
|
|
||||||
? (
|
|
||||||
<antd.Menu.Item icon={React.createElement(Icons[e.icon])} key={e.id}>
|
|
||||||
<span>{e.title}</span>
|
|
||||||
</antd.Menu.Item>
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { handleClickMenu } = this.props
|
|
||||||
if (this.state.loading) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={styles.left_sider_wrapper}>
|
|
||||||
<antd.Layout.Sider
|
|
||||||
onDoubleClick={() => { window.toogleSidebarCollapse() }}
|
|
||||||
collapsed={this.props.app.sidebar_collapsed || false}
|
|
||||||
trigger={null}
|
|
||||||
className={styles.left_sider_container}
|
|
||||||
width="175px"
|
|
||||||
style={{ flex: 'unset' }}
|
|
||||||
>
|
|
||||||
<div onClick={() => { handleClickMenu({ key: '' }) }} className={classnames(styles.left_sider_header, { [styles.emb]: this.props.app.embedded })}>
|
|
||||||
<img className={styles.logotype} src={this.props.logo} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.left_sider_menuContainer}>
|
|
||||||
<antd.Menu
|
|
||||||
selectable={false}
|
|
||||||
className={styles.left_sider_menuItems}
|
|
||||||
onClick={handleClickMenu}
|
|
||||||
>
|
|
||||||
{this.renderMenus(this.state.menus, "top")}
|
|
||||||
</antd.Menu>
|
|
||||||
|
|
||||||
<div className={styles.left_sider_footer}>
|
|
||||||
<antd.Menu
|
|
||||||
selectable={false}
|
|
||||||
className={styles.left_sider_menuItems}
|
|
||||||
onClick={handleClickMenu}
|
|
||||||
>
|
|
||||||
{this.renderMenus(this.state.menus, "bottom")}
|
|
||||||
</antd.Menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</antd.Layout.Sider>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
|
|
||||||
.left_sider_wrapper {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
user-select: none;
|
|
||||||
border-color: transparent;
|
|
||||||
font-size: 15px;
|
|
||||||
font-family: @__app_secondaryFont;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
width: 65%;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
z-index: 40;
|
|
||||||
float: left;
|
|
||||||
position: relative;
|
|
||||||
background-color: transparent;
|
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-layout-sider {
|
|
||||||
background-color: transparent;
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
.ant-menu {
|
|
||||||
font-weight: 700;
|
|
||||||
color: unset;
|
|
||||||
vertical-align: middle;
|
|
||||||
// margin: 0 0 0 5px;
|
|
||||||
// border-right: 0!important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_sider_header {
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 15px 0 0 22px;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
.logotype{
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
max-height: 70px;
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.emb{
|
|
||||||
margin: 5px 0 0 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.logged{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_sider_footer {
|
|
||||||
margin: 0 0 12px;
|
|
||||||
:global {
|
|
||||||
text-align: center;
|
|
||||||
bottom: 0;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
.anticon{
|
|
||||||
font-size: 15px!important;
|
|
||||||
}
|
|
||||||
.ant-menu-item{
|
|
||||||
height: 35px!important;
|
|
||||||
margin-bottom: 0!important;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_sider_container {
|
|
||||||
border-right: transparent;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_sider_menuContainer {
|
|
||||||
height: 100%;
|
|
||||||
margin: 18px 0 8px;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-layout-sider-children {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-menu-item {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
transition: @transition-ease-inout;
|
|
||||||
|
|
||||||
border-radius: 4px 8px 8px 4px;
|
|
||||||
padding: 2px 0 2px 24px;
|
|
||||||
border-right: 0!important;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-menu-item:hover {
|
|
||||||
border-radius: 8px;
|
|
||||||
transform: translate(10px,0);
|
|
||||||
|
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
box-shadow: -2px 2px 1px 0 rgba(51, 51, 51, 0.13);
|
|
||||||
color: rgb(102, 102, 102);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-menu-item-selected {
|
|
||||||
background-color: unset;
|
|
||||||
// background: linear-gradient(90deg, rgb(255, 230, 0) 2%, rgba(255,255,255,0.5) 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.anticon {
|
|
||||||
font-size: @left_sider_sizeIcons;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-layout-sider-collapsed, .ant-menu-inline-collapsed {
|
|
||||||
.ant-menu-item {
|
|
||||||
> span{
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-menu-item:hover {
|
|
||||||
box-shadow: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-menu-item a {
|
|
||||||
color: @__app_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_sider_menuItems {
|
|
||||||
background-color: transparent;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
//width: 100%;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
animation: fadein 0.5s;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import config from 'config'
|
|
||||||
import { router } from 'core/libs'
|
|
||||||
import { connect } from 'umi'
|
|
||||||
import MenuList from 'schemas/sidebar_menu.json'
|
|
||||||
|
|
||||||
import Sider_Mobile from './mobile'
|
|
||||||
import Sider_Default from './default'
|
|
||||||
|
|
||||||
@connect(({ app, extended }) => ({ app, extended }))
|
|
||||||
class Sider extends React.Component {
|
|
||||||
state = {
|
|
||||||
loading: true,
|
|
||||||
menuAtrributes: [],
|
|
||||||
menus: []
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClickMenu = e => {
|
|
||||||
const elementAtrributes = this.state.menuAtrributes[e.key]
|
|
||||||
|
|
||||||
if (typeof (this.state.menuAtrributes[e.key]) !== "undefined") {
|
|
||||||
if (typeof (elementAtrributes.onClick) == "function") {
|
|
||||||
elementAtrributes.onClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
router.go(`/${e.key}`) // by default push to router
|
|
||||||
}
|
|
||||||
|
|
||||||
async menuQuery(data) {
|
|
||||||
if (!data) return false
|
|
||||||
this.setState({ loading: true })
|
|
||||||
|
|
||||||
const filterArray = (data) => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
let menuMap = []
|
|
||||||
let menuAtrributes = []
|
|
||||||
data.forEach(async (element) => {
|
|
||||||
if (!element.attributes) {
|
|
||||||
element.attributes = {}
|
|
||||||
}
|
|
||||||
let validRequire = typeof (element.attributes.require) !== 'undefined' ? await window.requireQuery(element.attributes.require) : true
|
|
||||||
|
|
||||||
if (validRequire) {
|
|
||||||
menuAtrributes[element.id] = element.attributes
|
|
||||||
menuMap.push(element)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.setState({ menuAtrributes })
|
|
||||||
resolve(menuMap)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ menus: await filterArray(data), loading: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.menuQuery(MenuList)
|
|
||||||
}
|
|
||||||
|
|
||||||
filterMenusByType(type) {
|
|
||||||
let arrayResults = []
|
|
||||||
this.state.menus.forEach((e) => {
|
|
||||||
if (typeof (e.attributes) !== "undefined") {
|
|
||||||
const isType = typeof (e.attributes[type]) !== "undefined" ? e.attributes[type] : true // Returns as valid by default if is not set
|
|
||||||
if (isType) {
|
|
||||||
arrayResults.push(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return arrayResults
|
|
||||||
}
|
|
||||||
|
|
||||||
renderByType(type) {
|
|
||||||
const sider_props = { handleClickMenu: this.handleClickMenu, logo: config.app.LogoPath }
|
|
||||||
const filteredMenus = this.filterMenusByType(type)
|
|
||||||
switch (type) {
|
|
||||||
case "desktop": {
|
|
||||||
return <Sider_Default menus={filteredMenus} {...sider_props} />
|
|
||||||
}
|
|
||||||
case "mobile": {
|
|
||||||
return <Sider_Mobile menus={filteredMenus} {...sider_props} />
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return null // include invalid default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { isMobile } = this.props
|
|
||||||
if (this.state.loading) return null
|
|
||||||
return this.renderByType(isMobile ? "mobile" : "desktop")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Sider
|
|
@ -1,31 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import styles from './index.less'
|
|
||||||
|
|
||||||
export default class Sider_Mobile extends React.Component {
|
|
||||||
|
|
||||||
renderMenus(data){
|
|
||||||
return data.map(e => {
|
|
||||||
return <antd.Menu.Item key={e.id} style={{ color: '#ffffff', fontSize: '18px' }} >{e.icon}</antd.Menu.Item>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { handleClickMenu, menus } = this.props
|
|
||||||
return (
|
|
||||||
<div className={styles.left_sider_wrapper}>
|
|
||||||
<antd.Layout.Sider
|
|
||||||
trigger={null}
|
|
||||||
width='100%'
|
|
||||||
>
|
|
||||||
<antd.Menu
|
|
||||||
mode="horizontal"
|
|
||||||
onClick={handleClickMenu}
|
|
||||||
>
|
|
||||||
{this.renderMenus(menus)}
|
|
||||||
</antd.Menu>
|
|
||||||
</antd.Layout.Sider>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.left_sider_wrapper {
|
|
||||||
overflow: hidden!important;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 500;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
height: 50px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
border-color: transparent;
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: @__app_generalFont;
|
|
||||||
padding: 0 27px;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-layout-sider {
|
|
||||||
background-color: transparent;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.ant-menu-item {color: @left_sider_color;}
|
|
||||||
.anticon {font-size: @left_sider_sizeIcons;}
|
|
||||||
.ant-menu-item{margin: auto; padding: 0;}
|
|
||||||
|
|
||||||
.ant-menu {
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 27px 27px 0 0;
|
|
||||||
padding: 0 27px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-menu-horizontal {
|
|
||||||
line-height: 46px;
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 0;
|
|
||||||
border-bottom: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './index.less'
|
|
||||||
import { Minus, X } from 'components/Icons'
|
|
||||||
import { connect } from 'umi';
|
|
||||||
|
|
||||||
@connect(({ app }) => ({ app }))
|
|
||||||
export default class WindowNavbar extends React.Component{
|
|
||||||
handleMinimize(){
|
|
||||||
this.props.dispatch({
|
|
||||||
type: "app/ipcInvoke",
|
|
||||||
payload: {
|
|
||||||
key: "minimize-window"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
handleClose(){
|
|
||||||
this.props.dispatch({
|
|
||||||
type: "app/ipcInvoke",
|
|
||||||
payload: {
|
|
||||||
key: "hide-window"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
render(){
|
|
||||||
if (!this.props.dispatch) return null
|
|
||||||
return(
|
|
||||||
<div className={styles.navbar} >
|
|
||||||
<div className={styles.controls}>
|
|
||||||
<div><Minus onClick={() => this.handleMinimize()} /></div>
|
|
||||||
<div><X onClick={() => this.handleClose()}/></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
@aumentSize: 10px;
|
|
||||||
|
|
||||||
.navbar{
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
height: @__app_winavar_height;
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 5000;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: rgba(54, 54, 54, 0.1);
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar:hover{
|
|
||||||
background-color: rgba(54, 54, 54, 0.65);
|
|
||||||
height: calc(@__app_winavar_height + @aumentSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls{
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
display: flex;
|
|
||||||
text-align: right;
|
|
||||||
float: right;
|
|
||||||
width: auto;
|
|
||||||
margin: auto;
|
|
||||||
height: 100%;
|
|
||||||
> div{
|
|
||||||
padding: 0 14px;
|
|
||||||
svg{
|
|
||||||
margin: 0!important;
|
|
||||||
padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> div:hover{
|
|
||||||
background-color: rgba(54, 54, 54, 0.8);
|
|
||||||
color: #fdfdfd;
|
|
||||||
}
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls:hover{
|
|
||||||
background-color: rgba(54, 54, 54, 0.705);
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import Sider from './Sider'
|
|
||||||
import RightSider from './RightSider'
|
|
||||||
import Overlay from './Overlay'
|
|
||||||
import WindowNavbar from './WindowNavbar'
|
|
||||||
import ContextMenu from './ContextMenu/index.tsx'
|
|
||||||
|
|
||||||
export { RightSider, Sider, Overlay, WindowNavbar, ContextMenu }
|
|
@ -1,4 +1,4 @@
|
|||||||
@import '~theme/index.less';
|
@import "~theme/index.less";
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
font-family: "Nunito", sans-serif;
|
font-family: "Nunito", sans-serif;
|
||||||
@ -7,8 +7,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
color: @__Global_layout_color;
|
color: @__global_color;
|
||||||
background-color: #ffffff;
|
background-color: @__global_background;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
@ -33,7 +33,7 @@
|
|||||||
width: 224px;
|
width: 224px;
|
||||||
:global {
|
:global {
|
||||||
.ant-menu-inline {
|
.ant-menu-inline {
|
||||||
color: @__Global_layout_color;
|
color: @__global_color;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@ -52,17 +52,13 @@
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
color: @__Global_layout_color;
|
color: @__global_color;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.inline{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
&.horizontal {
|
&.horizontal {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.menuList {
|
.menuList {
|
||||||
@ -70,14 +66,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.vertical{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media screen and (max-width: @screen-md) {
|
@media screen and (max-width: @screen-md) {
|
||||||
.main {
|
.main {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Menu, Result } from 'antd'
|
import { Menu, Result } from 'antd'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import { Icons } from 'components/Icons'
|
||||||
|
|
||||||
import styles from './index.less'
|
import styles from './index.less'
|
||||||
import { __proto__filterSchematizedArray } from 'core'
|
import { objectToArrayMap } from '@corenode/utils'
|
||||||
|
|
||||||
export default class ListedMenu extends React.Component {
|
export default class ListedMenu extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
@ -16,7 +17,7 @@ export default class ListedMenu extends React.Component {
|
|||||||
|
|
||||||
async queryMenu() {
|
async queryMenu() {
|
||||||
this.setState({ loading: true })
|
this.setState({ loading: true })
|
||||||
this.setState({ menus: await __proto__filterSchematizedArray(this.props.menuArray), loading: false })
|
this.setState({ menus: objectToArrayMap(this.props.menuArray), loading: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenu() {
|
getMenu() {
|
||||||
@ -27,20 +28,20 @@ export default class ListedMenu extends React.Component {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
selectKey = (key: any) => {
|
selectKey = (key) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectKey: key,
|
selectKey: key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderChildren = () => {
|
renderChildren = () => {
|
||||||
let titlesArray: never[] = []
|
let titlesArray = []
|
||||||
this.state.menus.forEach(e => { titlesArray[e.key] = e })
|
this.state.menus.forEach(e => { titlesArray[e.key] = e })
|
||||||
|
|
||||||
const OptionTitle = () => {
|
const OptionTitle = () => {
|
||||||
if (this.state.renderOptionTitle) {
|
if (this.state.renderOptionTitle) {
|
||||||
return <div>
|
return <div>
|
||||||
<h3>{titlesArray[this.state.selectKey].icon || null}{titlesArray[this.state.selectKey].title || null}</h3>
|
<h3>{React.createElement(Icons[titlesArray[this.state.selectKey].icon]) || null}{titlesArray[this.state.selectKey].title || null}</h3>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -79,7 +80,7 @@ export default class ListedMenu extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selectKey, loading } = this.state
|
const { selectKey, loading } = this.state
|
||||||
const isMode = (e: string) => {
|
const isMode = (e) => {
|
||||||
return this.state.mode === `${e}`
|
return this.state.mode === `${e}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ export default class ListedMenu extends React.Component {
|
|||||||
<div style={this.props.wrapperStyle ?? null} className={classnames(styles.main, { [styles.horizontal]: isMode("horizontal") })}>
|
<div style={this.props.wrapperStyle ?? null} className={classnames(styles.main, { [styles.horizontal]: isMode("horizontal") })}>
|
||||||
<div className={styles.menuList}>
|
<div className={styles.menuList}>
|
||||||
<h3>
|
<h3>
|
||||||
{this.props.icon ?? null} {this.props.title ?? "Menu"}
|
{React.createElement(Icons[this.props.icon]) ?? null} {this.props.title ?? "Menu"}
|
||||||
</h3>
|
</h3>
|
||||||
<Menu
|
<Menu
|
||||||
mode={this.state.mode}
|
mode={this.state.mode}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import styles from './index.less'
|
|
||||||
|
|
||||||
const Loader = (loading) => {
|
|
||||||
return (
|
|
||||||
<div className={classNames(styles.wrapper, {[styles.end]: !loading })}>
|
|
||||||
<div
|
|
||||||
className={styles.newloader}
|
|
||||||
>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Loader
|
|
@ -1,107 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
user-select: none;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1000;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: auto;
|
|
||||||
height: 29px;
|
|
||||||
|
|
||||||
margin: 30px;
|
|
||||||
background-color: #2d2d2dc2;
|
|
||||||
border-radius: 28px;
|
|
||||||
|
|
||||||
&.end {
|
|
||||||
animation: unshow 0.8s linear;
|
|
||||||
.newloader>div {
|
|
||||||
animation: loader 0.8s linear;
|
|
||||||
}
|
|
||||||
opacity: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader {
|
|
||||||
transform: scale(0.28);
|
|
||||||
width: 54px;
|
|
||||||
height: 23px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div {
|
|
||||||
width: 6px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
left: -10px;
|
|
||||||
bottom: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
transform-origin: 10px 35px;
|
|
||||||
transform: rotate(0deg);
|
|
||||||
animation: loader 0.8s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div:nth-child(2) {
|
|
||||||
transform: rotate(45deg);
|
|
||||||
animation-delay: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div:nth-child(3) {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div:nth-child(4) {
|
|
||||||
transform: rotate(135deg);
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div:nth-child(5) {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div:nth-child(6) {
|
|
||||||
transform: rotate(225deg);
|
|
||||||
animation-delay: 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div:nth-child(7) {
|
|
||||||
transform: rotate(270deg);
|
|
||||||
animation-delay: 0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.newloader>div:nth-child(8) {
|
|
||||||
transform: rotate(315deg);
|
|
||||||
animation-delay: 0.7s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader {
|
|
||||||
0% {
|
|
||||||
background: transparent;
|
|
||||||
left: -10px;
|
|
||||||
transform-origin: 10px 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
30% {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background: transparent;
|
|
||||||
left: 10px;
|
|
||||||
transform-origin: -10px 35px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes unshow {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
7
packages/app/src/components/LoadingSpinner/index.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { LoadingOutlined } from "@ant-design/icons"
|
||||||
|
import { Result } from "antd"
|
||||||
|
|
||||||
|
export default (props = {}) => {
|
||||||
|
return <Result title={props.title ?? "Loading"} icon={<LoadingOutlined spin />} />
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import styles from './index.less'
|
|
||||||
|
|
||||||
export default class MediaPlayer extends React.PureComponent {
|
|
||||||
player() {
|
|
||||||
const { file } = this.props
|
|
||||||
let type
|
|
||||||
|
|
||||||
const ImageExtensions = ['.png', '.jpg', '.jpeg', '.gif']
|
|
||||||
const VideoExtensions = ['.mp4', '.mov', '.avi']
|
|
||||||
const AudioExtensions = ['.mp3', '.ogg', '.wav']
|
|
||||||
|
|
||||||
const FilesAllowed = ImageExtensions.concat(
|
|
||||||
VideoExtensions,
|
|
||||||
AudioExtensions
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const prop in FilesAllowed) {
|
|
||||||
if (file.includes(`${ImageExtensions[prop]}`)) {
|
|
||||||
type = 'image'
|
|
||||||
}
|
|
||||||
if (file.includes(`${VideoExtensions[prop]}`)) {
|
|
||||||
type = 'video'
|
|
||||||
}
|
|
||||||
if (file.includes(`${AudioExtensions[prop]}`)) {
|
|
||||||
type = 'audio'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == 'video') {
|
|
||||||
// const payload = {type: 'video', sources: [{src: file,}]}
|
|
||||||
// return (
|
|
||||||
// <PlyrComponent styles={{ width: '100%' }} sources={payload} />
|
|
||||||
// )
|
|
||||||
return (
|
|
||||||
<video id="player" playsInline controls>
|
|
||||||
<source src={file} />
|
|
||||||
</video>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (type == 'audio') {
|
|
||||||
return (
|
|
||||||
<audio id="player" controls>
|
|
||||||
<source src={file} />
|
|
||||||
</audio>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (type == 'image') {
|
|
||||||
return <img src={file} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames(styles.PlayerContainer, {
|
|
||||||
[styles.mobile]: this.props.isMobile,
|
|
||||||
[styles.entire]: this.props.entire,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{this.player()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
.PlayerContainer {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
max-height: 600px;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
audio {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
&.entire {
|
|
||||||
max-width: 52%;
|
|
||||||
max-height: 80%;
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-70%, -50%);
|
|
||||||
|
|
||||||
img {
|
|
||||||
object-fit: contain;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 52vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
audio {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
object-fit: contain;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 52vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mobile {
|
|
||||||
max-width: unset;
|
|
||||||
max-height: unset;
|
|
||||||
top: unset;
|
|
||||||
left: unset;
|
|
||||||
transform: unset;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
height: 60vh;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
img {
|
|
||||||
object-fit: contain;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
max-width: unset;
|
|
||||||
max-height: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
audio {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
object-fit: contain;
|
|
||||||
height: auto;
|
|
||||||
width: 100%;
|
|
||||||
max-width: unset;
|
|
||||||
max-height: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
8
packages/app/src/components/Notifications/index.jsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { notification } from "antd"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
error: (...context) => {
|
||||||
|
notification.error(context)
|
||||||
|
},
|
||||||
|
}
|
91
packages/app/src/components/ObjectInspector/index.jsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.contentInner {
|
|
||||||
background: #fff;
|
|
||||||
padding: 24px;
|
|
||||||
box-shadow: @shadow-1;
|
|
||||||
min-height: ~'calc(100vh - 230px)';
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.contentInner {
|
|
||||||
padding: 12px;
|
|
||||||
min-height: ~'calc(100vh - 160px)';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import styles from './Page.less';
|
|
||||||
|
|
||||||
export default class Page extends Component {
|
|
||||||
render() {
|
|
||||||
const { className, children, loading = false, inner = false } = this.props;
|
|
||||||
const loadingStyle = {
|
|
||||||
height: 'calc(100vh - 184px)',
|
|
||||||
overflow: 'hidden',
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames(className, {
|
|
||||||
[styles.contentInner]: inner,
|
|
||||||
})}
|
|
||||||
style={loading ? loadingStyle : null}
|
|
||||||
>
|
|
||||||
{loading ? 'Loading' : ''}
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Page.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
children: PropTypes.node,
|
|
||||||
loading: PropTypes.bool,
|
|
||||||
inner: PropTypes.bool,
|
|
||||||
};
|
|
@ -1,133 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import { RefreshCw } from 'components/Icons'
|
|
||||||
import { objectToArrayMap } from '@nodecorejs/utils'
|
|
||||||
import { getCircularReplacer, decycle } from 'core'
|
|
||||||
|
|
||||||
const serializeFlags = {
|
|
||||||
__cycle_flag: true // with id 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFlagId(e, id) {
|
|
||||||
return serializeFlags[Object.keys(e)[id ?? 0]]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getErrorRender = (e, error) => {
|
|
||||||
return (
|
|
||||||
<div key={e.key} >
|
|
||||||
<div style={{ display: "flex", alignItems: "center", padding: "12px 16px", height: "47px", backgroundColor: "#d9d9d9" }} key={e.key} >
|
|
||||||
This could not be rendered > ({e.key}) [{typeof (e.value)}]
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<antd.Collapse>
|
|
||||||
<antd.Collapse.Panel header="See error" >
|
|
||||||
<div style={{ margin: '0 5px 15px 5px', wordBreak: "break-all" }} >
|
|
||||||
<span>{error.toString()}</span>
|
|
||||||
</div>
|
|
||||||
</antd.Collapse.Panel>
|
|
||||||
</antd.Collapse>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDecoratorStr = (e, json) => {
|
|
||||||
try {
|
|
||||||
switch (typeof (e.value)) {
|
|
||||||
case "string": {
|
|
||||||
return `(${json.length}) characters`
|
|
||||||
}
|
|
||||||
case "object": {
|
|
||||||
if (e.value == null) {
|
|
||||||
return `Empty (null/undefined)`
|
|
||||||
}
|
|
||||||
if (isFlagId(e.value, 0)) {
|
|
||||||
return <span><RefreshCw /> Cylic </span>
|
|
||||||
}
|
|
||||||
if (typeof (e.value.length) !== "undefined") {
|
|
||||||
return `Lenght (${e.value.length})`
|
|
||||||
}
|
|
||||||
if (typeof (Object.keys(e.value).length) !== "undefined") {
|
|
||||||
return `Lenght (${Object.keys(e.value).length})`
|
|
||||||
}
|
|
||||||
return `Immeasurable (by error) (not valid object)`
|
|
||||||
}
|
|
||||||
case "array": {
|
|
||||||
return `Lenght (${e.value.length})`
|
|
||||||
}
|
|
||||||
case "boolean": {
|
|
||||||
return <antd.Tag color={e.value ? "blue" : "volcano"} > {e.value ? "true" : "false"} </antd.Tag>
|
|
||||||
}
|
|
||||||
case "number": {
|
|
||||||
return <antd.Tag > {e.value} </antd.Tag>
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return `Immeasurable / Invalid`
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return <strong>Immeasurable (by error)</strong>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getContent = (e) => {
|
|
||||||
try {
|
|
||||||
switch (typeof (e.value)) {
|
|
||||||
case "string": {
|
|
||||||
return e.value
|
|
||||||
}
|
|
||||||
case "object": {
|
|
||||||
if (e.value == null) {
|
|
||||||
return `${e.value}`
|
|
||||||
}
|
|
||||||
if (isFlagId(e.value, 0)) {
|
|
||||||
return <div key={e.key} style={{ display: "flex", alignItems: "center", padding: "12px 16px", height: "47px", backgroundColor: "#d9d9d9" }} >
|
|
||||||
<RefreshCw /> This cannot be rendered because a cylic has been detected
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
if (Object.keys(e.value).length > 0) { // trying create nested
|
|
||||||
return <div>
|
|
||||||
{DebugPanel(e.value)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
return JSON.stringify(e.value, getCircularReplacer())
|
|
||||||
}
|
|
||||||
case "array": {
|
|
||||||
return JSON.stringify(e.value, getCircularReplacer())
|
|
||||||
}
|
|
||||||
case "boolean": {
|
|
||||||
return `${e.value}`
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return `${e.value}`
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return getErrorRender(e, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getType = (e) => {
|
|
||||||
if (e !== null && isFlagId(e, 0)) {
|
|
||||||
return `[loop]`
|
|
||||||
}
|
|
||||||
return `[${typeof (e)}]`
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DebugPanel(data) {
|
|
||||||
if (!data) return false
|
|
||||||
return objectToArrayMap(decycle(data)).map(e => {
|
|
||||||
try {
|
|
||||||
const content = getContent(e)
|
|
||||||
return (
|
|
||||||
<antd.Collapse style={{ border: '0px' }} key={e.key}>
|
|
||||||
<antd.Collapse.Panel key={e.key} header={<div>{getType(e.value)} <strong>{e.key}</strong> | {getDecoratorStr(e, content)} </div>} >
|
|
||||||
<div style={{ margin: '0 5px 15px 5px', wordBreak: "break-all" }} >
|
|
||||||
<span>{content}</span>
|
|
||||||
</div>
|
|
||||||
</antd.Collapse.Panel>
|
|
||||||
</antd.Collapse>
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
return getErrorRender(e, error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './index.less'
|
|
||||||
import * as core from 'core'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import { verbosity } from '@nodecorejs/utils'
|
|
||||||
|
|
||||||
export default class LikeBtn extends React.Component {
|
|
||||||
state = {
|
|
||||||
hoveringCounter: false,
|
|
||||||
liked: this.props.liked,
|
|
||||||
count: this.props.count,
|
|
||||||
clicked: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick() {
|
|
||||||
let done = false
|
|
||||||
if (typeof (this.props.handleClick) !== "undefined") {
|
|
||||||
this.setState({ clicked: true })
|
|
||||||
|
|
||||||
this.props.handleClick((callback) => {
|
|
||||||
if (typeof (callback) !== "object") {
|
|
||||||
this.setState({ count: callback })
|
|
||||||
} else {
|
|
||||||
verbosity.log(`Invalid response`)
|
|
||||||
this.setState({ clicked: false, liked: false })
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({ clicked: false, liked: !this.state.liked })
|
|
||||||
}, 150)
|
|
||||||
done = true
|
|
||||||
}, setTimeout(() => {
|
|
||||||
if (!done) {
|
|
||||||
verbosity.log(`like click timeout!`)
|
|
||||||
this.setState({ clicked: false })
|
|
||||||
}
|
|
||||||
}, 3000))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLeave() {
|
|
||||||
if (this.state.hoveringCounter) {
|
|
||||||
this.setState({hoveringCounter: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOver() {
|
|
||||||
if (!this.state.hoveringCounter) {
|
|
||||||
this.setState({hoveringCounter: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getDecoratorCount(count) {
|
|
||||||
return <span>{this.state.hoveringCounter? `${count}` : core.abbreviateCount(new Number(count).toString())}</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { liked, clicked, count } = this.state
|
|
||||||
return (
|
|
||||||
<div onMouseLeave={() => this.handleLeave()} onMouseOver={() => this.handleOver()}>
|
|
||||||
<button onClick={() => { this.handleClick() }} className={classnames(styles.like_button, { [styles.clickanim]: clicked })} >
|
|
||||||
<div className={styles.like_wrapper}>
|
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
styles.ripple,
|
|
||||||
liked ? null : { [styles.clickanim]: clicked }
|
|
||||||
)}
|
|
||||||
></div>
|
|
||||||
<svg
|
|
||||||
className={classnames(
|
|
||||||
styles.heart,
|
|
||||||
{ [styles.empty]: !liked },
|
|
||||||
liked ? null : { [styles.clickanim]: clicked }
|
|
||||||
)}
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<div className={classnames(styles.likesIndicator, {[styles.hover]: this.state.hoveringCounter})} >
|
|
||||||
<span className={classnames(styles.likeCounter, {
|
|
||||||
[styles.active]: !clicked,
|
|
||||||
[styles.past]: clicked,
|
|
||||||
})} >
|
|
||||||
{this.getDecoratorCount(count)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,225 +0,0 @@
|
|||||||
@blur-transition-duration: 150ms;
|
|
||||||
@blurFilter-transition-duration: 150ms;
|
|
||||||
|
|
||||||
.like_button,
|
|
||||||
.like_button:before,
|
|
||||||
.like_button:after {
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ripple,
|
|
||||||
.ripple:before,
|
|
||||||
.ripple:after {
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.likesIndicator{
|
|
||||||
cursor: default;
|
|
||||||
margin: auto;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 12;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 0 12px 12px 0;
|
|
||||||
color: #333;
|
|
||||||
width: 52px;
|
|
||||||
height: fit-content;
|
|
||||||
transition: all 150ms ease-in-out;
|
|
||||||
transform: translate(30px, -4px);
|
|
||||||
padding: 5px 14px;
|
|
||||||
min-width: fit-content;
|
|
||||||
|
|
||||||
&.hover{
|
|
||||||
width: 90px;
|
|
||||||
}
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.likeCounter {
|
|
||||||
font-family: "Poppins", sans-serif;
|
|
||||||
|
|
||||||
opacity: 0;
|
|
||||||
transform: perspective(100px) translateZ(10px);
|
|
||||||
filter: blur(10px);
|
|
||||||
letter-spacing: 0;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
opacity: 1;
|
|
||||||
//transform: perspective(100px) translateZ(0px);
|
|
||||||
filter: blur(0px);
|
|
||||||
|
|
||||||
transition: opacity @blur-transition-duration linear, transform @blur-transition-duration linear, filter @blurFilter-transition-duration linear, letter-spacing @blur-transition-duration linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.past {
|
|
||||||
opacity: 0;
|
|
||||||
//transform: perspective(100px) translateZ(-10px);
|
|
||||||
filter: blur(1px);
|
|
||||||
letter-spacing: 0.15em;
|
|
||||||
transition: opacity @blur-transition-duration linear, transform @blur-transition-duration linear, filter @blurFilter-transition-duration linear, letter-spacing @blur-transition-duration linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.like_button {
|
|
||||||
--color-heart: #EA442B;
|
|
||||||
--easing: cubic-bezier(.7, 0, .3, 1);
|
|
||||||
--duration: .5s;
|
|
||||||
|
|
||||||
font-size: 40px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: white;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
outline: none;
|
|
||||||
z-index: 13;
|
|
||||||
transition: transform var(--duration) var(--easing);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
z-index: -1;
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: inherit;
|
|
||||||
transition: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: inherit;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@keyframes depress {
|
|
||||||
|
|
||||||
from,
|
|
||||||
to {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: translateY(5%) scale(0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes depress-shadow {
|
|
||||||
|
|
||||||
from,
|
|
||||||
to {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: scale(0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.like_wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 5;
|
|
||||||
|
|
||||||
>* {
|
|
||||||
margin: auto;
|
|
||||||
grid-area: 1 / 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.heart {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
>path {
|
|
||||||
stroke: var(--color-heart);
|
|
||||||
stroke-width: 2;
|
|
||||||
transition: fill var(--duration) var(--easing);
|
|
||||||
fill: var(--color-heart);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.empty {
|
|
||||||
>path {
|
|
||||||
stroke: var(--color-heart);
|
|
||||||
stroke-width: 2;
|
|
||||||
transition: fill var(--duration) var(--easing);
|
|
||||||
fill: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.clickanim {
|
|
||||||
animation: heart-bounce var(--duration) var(--easing);
|
|
||||||
|
|
||||||
@keyframes heart-bounce {
|
|
||||||
40% {
|
|
||||||
transform: scale(0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ripple {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: .4em solid var(--color-heart);
|
|
||||||
border-radius: inherit;
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.clickanim {
|
|
||||||
&:before {
|
|
||||||
animation: ripple-out var(--duration) var(--easing);
|
|
||||||
|
|
||||||
@keyframes ripple-out {
|
|
||||||
from {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: scale(5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,265 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import styles from './index.less'
|
|
||||||
import { MediaPlayer } from 'components'
|
|
||||||
import * as Icons from 'components/Icons'
|
|
||||||
|
|
||||||
import { Clipboard, Aperture, FlagOutlined, MessageSquare, MoreOutlined, PushpinFilled, EllipsisOutlined, verifiedBadge } from 'components/Icons'
|
|
||||||
import * as core from 'core'
|
|
||||||
import Icon from '@ant-design/icons'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import { verbosity } from '@nodecorejs/utils'
|
|
||||||
|
|
||||||
import settings from 'core/libs/settings'
|
|
||||||
import { router } from 'core/libs'
|
|
||||||
import LikeBtn from './components/like/index.js'
|
|
||||||
import { connect } from 'umi'
|
|
||||||
import { clipboard } from 'core/libs/browser'
|
|
||||||
|
|
||||||
const { Meta } = antd.Card
|
|
||||||
|
|
||||||
const defaultPayload = {
|
|
||||||
id: null,
|
|
||||||
post_time: null,
|
|
||||||
postText: null,
|
|
||||||
postFile: null,
|
|
||||||
publisher: null,
|
|
||||||
post_likes: null,
|
|
||||||
is_post_pinned: null,
|
|
||||||
is_liked: null,
|
|
||||||
post_comments: null,
|
|
||||||
get_post_comments: null,
|
|
||||||
postPinned: false,
|
|
||||||
postReported: false,
|
|
||||||
postBoosted: false,
|
|
||||||
ReportIgnore: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
const moreMenuList = [
|
|
||||||
{
|
|
||||||
key: "save_post",
|
|
||||||
icon: "Save",
|
|
||||||
textEnable: "Save post",
|
|
||||||
textDisable: "Unsave post"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "report_post",
|
|
||||||
icon: "AlertCircle",
|
|
||||||
text: "Report"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const contextMenuList = [
|
|
||||||
{
|
|
||||||
key: "inspect_element",
|
|
||||||
title: "Copy URL",
|
|
||||||
icon: <Clipboard />,
|
|
||||||
params: {
|
|
||||||
onClick: (e) => {
|
|
||||||
clipboard.copyText(core.generatePostURI(e.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "screenshot",
|
|
||||||
title: "Save screenshot",
|
|
||||||
icon: <Aperture />,
|
|
||||||
params: {
|
|
||||||
itemProps: {
|
|
||||||
style: { color: "#40a9ff" }
|
|
||||||
},
|
|
||||||
onClick: (e) => {
|
|
||||||
core.createScreenshotFromElement(document.getElementById(e.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
@connect(({ app }) => ({ app }))
|
|
||||||
export default class PostCard extends React.PureComponent {
|
|
||||||
state = {
|
|
||||||
visibleMoreMenu: false,
|
|
||||||
payload: this.props.payload,
|
|
||||||
}
|
|
||||||
|
|
||||||
elementRef = React.createRef()
|
|
||||||
|
|
||||||
handleDispatchInvoke(key, payload) {
|
|
||||||
this.props.dispatch({
|
|
||||||
type: "app/ipcInvoke",
|
|
||||||
payload: { key: key, payload: payload }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
goElementById(id) {
|
|
||||||
if (settings("post_autoposition")) {
|
|
||||||
document.getElementById(id).scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
block: "center",
|
|
||||||
inline: "center"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toogleMoreMenu() {
|
|
||||||
this.setState({ visibleMoreMenu: !this.state.visibleMoreMenu })
|
|
||||||
}
|
|
||||||
|
|
||||||
renderReportedPost() {
|
|
||||||
if (this.state.ReportIgnore) return null
|
|
||||||
return (
|
|
||||||
<div className={styles.post_card_flaggedWarning}>
|
|
||||||
<FlagOutlined />
|
|
||||||
<h3>It seems that this post has been reported</h3>
|
|
||||||
<p>The content may be inappropriate or compromising</p>
|
|
||||||
<antd.Button
|
|
||||||
onClick={() => {
|
|
||||||
this.setState({ ReportIgnore: true })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Ignore
|
|
||||||
</antd.Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent(payload) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{payload.postText ? (
|
|
||||||
<div className={styles.post_card_content}>
|
|
||||||
<h3 dangerouslySetInnerHTML={{ __html: payload.postText }} />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{payload.postFile_full ? (
|
|
||||||
<div className={styles.post_card_file}>
|
|
||||||
<MediaPlayer file={payload.postFile_full} />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.contextMenu.addEventListener(
|
|
||||||
{
|
|
||||||
priority: 100,
|
|
||||||
onEventRender: contextMenuList,
|
|
||||||
ref: this.elementRef.current,
|
|
||||||
props: { id: this.state.payload.id }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLikeClick = (id, callback) => {
|
|
||||||
if (typeof (this.props.handleActions)) {
|
|
||||||
this.props.handleActions("like", id, (callbackResponse) => {
|
|
||||||
let updated = this.state.payload
|
|
||||||
if (callbackResponse.code == 200) {
|
|
||||||
|
|
||||||
updated.is_liked = !this.state.payload.is_liked
|
|
||||||
updated.post_likes = callbackResponse.response.count ?? 0
|
|
||||||
this.setState({ payload: updated })
|
|
||||||
|
|
||||||
if (typeof (callback) !== "undefined") {
|
|
||||||
callback(callbackResponse.response.count)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
verbosity.log(`Api error response ${callbackResponse.code}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
verbosity.log(`socket connection not available`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getMenuValue(id) {
|
|
||||||
return true // fetch from local state
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMenuClick(id) {
|
|
||||||
return true // mapToFunction
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMoreMenu() {
|
|
||||||
return moreMenuList.map((e) => {
|
|
||||||
return (<antd.Menu.Item onClick={() => this.handleMenuClick(e.id)} key={e.id ?? ""}>
|
|
||||||
{React.createElement(Icons[e.icon])}{e.textDisable && e.textEnable? (this.getMenuValue(e.id) ? e.textEnable : e.textDisable) : e.title?? e.text ?? "Who knows"}
|
|
||||||
</antd.Menu.Item>)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
post_time,
|
|
||||||
postText,
|
|
||||||
postFile,
|
|
||||||
publisher,
|
|
||||||
post_likes,
|
|
||||||
is_post_pinned,
|
|
||||||
is_liked,
|
|
||||||
post_comments,
|
|
||||||
get_post_comments
|
|
||||||
} = this.state.payload || defaultPayload
|
|
||||||
|
|
||||||
const menuMore = (
|
|
||||||
<antd.Menu>
|
|
||||||
{this.renderMoreMenu()}
|
|
||||||
</antd.Menu>
|
|
||||||
)
|
|
||||||
|
|
||||||
const actions = [
|
|
||||||
<LikeBtn handleClick={(callback) => { this.handleLikeClick(id, (response) => { callback(response) }) }} count={post_likes} liked={core.booleanFix(is_liked)} />,
|
|
||||||
<antd.Badge dot={this.state.payload.post_comments > 0 ? true : false}>
|
|
||||||
<MessageSquare key="comments" />
|
|
||||||
</antd.Badge>,
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={this.elementRef} key={this.state.payload.id} id={this.state.payload.id} className={styles.post_card_wrapper}>
|
|
||||||
<antd.Card
|
|
||||||
className={settings("post_hidebar") ? null : styles.showMode}
|
|
||||||
onClick={() => { this.goElementById(this.state.payload.id) }}
|
|
||||||
actions={actions}
|
|
||||||
hoverable
|
|
||||||
>
|
|
||||||
{this.state.postReported ? this.renderReportedPost() : null}
|
|
||||||
<div className={classnames(styles.post_include, { [styles.blur]: this.state.ReportIgnore ? false : this.state.postReported })}>
|
|
||||||
<Meta
|
|
||||||
avatar={
|
|
||||||
<div className={styles.postAvatar}>
|
|
||||||
<antd.Avatar shape="square" size={50} src={publisher.avatar} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
title={
|
|
||||||
<div className={styles.post_card_title}>
|
|
||||||
<h4 onClick={() => router.goProfile(publisher.username)} className={styles.titleUser}>
|
|
||||||
@{publisher.username}
|
|
||||||
{core.booleanFix(publisher.verified) ? (<Icon style={{ color: 'blue' }} component={verifiedBadge} />) : null}
|
|
||||||
{core.booleanFix(publisher.nsfw_flag) ? (<antd.Tag style={{ margin: '0 0 0 13px' }} color="volcano" > NSFW </antd.Tag>) : null}
|
|
||||||
</h4>
|
|
||||||
<div className={styles.PostTags}>
|
|
||||||
<div className={styles.MoreMenu}>
|
|
||||||
<antd.Dropdown overlay={menuMore} trigger={['click']}>
|
|
||||||
<MoreOutlined key="actionMenu" />
|
|
||||||
</antd.Dropdown>
|
|
||||||
</div>
|
|
||||||
{core.booleanFix(is_post_pinned) ? (<PushpinFilled />) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
description={<span className={styles.textAgo}>{post_time}</span>}
|
|
||||||
bordered="false"
|
|
||||||
/>
|
|
||||||
{this.renderContent(this.state.payload)}
|
|
||||||
<div className={styles.ellipsisIcon}>
|
|
||||||
<EllipsisOutlined />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</antd.Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,234 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.post_card_flaggedWarning {
|
|
||||||
border-radius: @post_card_general_border-rd;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 20;
|
|
||||||
background: @post_card_flaggedWarning_backgroud;
|
|
||||||
font-family: @__app_generalFont;
|
|
||||||
padding: @post_card_flaggedWarning_padding;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.anticon {
|
|
||||||
font-size: @post_card_flaggedWarning_iconSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post_card_wrapper {
|
|
||||||
user-select: none;
|
|
||||||
box-shadow: @post_card_wrapper_shadow;
|
|
||||||
border-radius: @post_card_general_border-rd;
|
|
||||||
max-width: 510px;
|
|
||||||
min-width: 265px;
|
|
||||||
width: auto;
|
|
||||||
margin: 23px auto 50px;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-card-meta-detail>div:not(:last-child) {
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card {
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
border-radius: @post_card_general_border-rd;
|
|
||||||
border: 0;
|
|
||||||
border-top: 1px solid #4646460c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-actions {
|
|
||||||
border-top: 0;
|
|
||||||
background: @post_card_wrapper_actions_backgroud;
|
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
|
||||||
transition: opacity @__Global_Components_transitions_dur linear, position @__Global_Components_transitions_dur linear, transform @__Global_Components_transitions_dur linear;
|
|
||||||
border-radius: 0 0 10px 10px;
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
&.showMode {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(0, 15px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-actions:hover {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(0, 15px);
|
|
||||||
transition: opacity @__Global_Components_transitions_dur linear, position @__Global_Components_transitions_dur linear, transform @__Global_Components_transitions_dur linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-actions>li>.anticon {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 22px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background: @post_card_wrapper_actions_icon_backgroud;
|
|
||||||
border-radius: 23px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-actions>li {
|
|
||||||
margin: -20px 0 0;
|
|
||||||
border-right: 0;
|
|
||||||
|
|
||||||
.ant-badge-count {
|
|
||||||
width: 20px;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-scroll-number-only>p.ant-scroll-number-only-unit {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
margin: 0;
|
|
||||||
line-height: 20px;
|
|
||||||
padding: 0 0 0 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 22px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background: @post_card_wrapper_actions_icon_backgroud;
|
|
||||||
border-radius: 23px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
#feather_icon{ margin-right: unset!important; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post_include {
|
|
||||||
padding: 13px 0 5px;
|
|
||||||
transition: all 150ms linear;
|
|
||||||
|
|
||||||
&.blur {
|
|
||||||
filter: blur(10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.showMode {
|
|
||||||
:global {
|
|
||||||
ul {
|
|
||||||
opacity: 1 !important;
|
|
||||||
transform: translate(0, 15px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post_card_title {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.postAvatar {
|
|
||||||
position: absolute;
|
|
||||||
left: -8px;
|
|
||||||
top: -8px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleUser {
|
|
||||||
display: flex;
|
|
||||||
font-family: @__app_generalFont;
|
|
||||||
margin: 0 0 0 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textAgo {
|
|
||||||
display: flex;
|
|
||||||
font-size: 10px;
|
|
||||||
margin: 0 0 0 53px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.PostTags {
|
|
||||||
float: right;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.anticon {
|
|
||||||
color: @post_card_wrapper_tags_color_default;
|
|
||||||
float: right;
|
|
||||||
margin: -0 6px 0 0;
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post_card_content {
|
|
||||||
word-break: break-all;
|
|
||||||
display: flex;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin: 23px 24px;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
user-select: all;
|
|
||||||
font-family: @__app_generalFont;
|
|
||||||
color: @post_card_wrapper_post_content_color;
|
|
||||||
font-weight: @post_card_wrapper_post_content_weight;
|
|
||||||
font-size: @post_card_wrapper_post_content_fontSize;
|
|
||||||
letter-spacing: @post_card_wrapper_post_content_letterSpacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.post_card_file {
|
|
||||||
display: flex;
|
|
||||||
margin: 23px 0 5px;
|
|
||||||
max-height: 600px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
color: rgb(85, 85, 85);
|
|
||||||
font-weight: 470;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.likebtn {
|
|
||||||
:global {
|
|
||||||
svg {
|
|
||||||
color: rgba(0, 0, 0, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg:hover {
|
|
||||||
color: rgb(233, 35, 68);
|
|
||||||
transition: all 0.2s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ellipsisIcon {
|
|
||||||
color: rgba(0, 0, 0, 0.45);
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
margin: auto;
|
|
||||||
font-size: 30px;
|
|
||||||
transition: opacity 150ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ellipsisIcon:hover {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 150ms linear;
|
|
||||||
}
|
|
@ -1,304 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import { imageToBase64 } from 'core'
|
|
||||||
import * as Icons from 'components/Icons'
|
|
||||||
import styles from './index.less'
|
|
||||||
import { connect } from 'umi'
|
|
||||||
import config from 'config'
|
|
||||||
import { settings, newSetting } from 'core/libs/settings'
|
|
||||||
|
|
||||||
const PrivacyList = [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
type: "any",
|
|
||||||
icon: "Globe",
|
|
||||||
decoratorText: "Share with everyone"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
type: "only_followers",
|
|
||||||
icon: "UserCheck",
|
|
||||||
decoratorText: "Share with people I follow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
type: "only_follow",
|
|
||||||
icon: "Users",
|
|
||||||
decoratorText: "Share with people follow me"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
type: "private",
|
|
||||||
icon: "Shield",
|
|
||||||
decoratorText: "Dont share, only me"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
type: "anon",
|
|
||||||
icon: "EyeOff",
|
|
||||||
decoratorText: "Anonymous"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
@connect(({ app }) => ({ app }))
|
|
||||||
class PostCreator extends React.PureComponent {
|
|
||||||
state = {
|
|
||||||
maxFileSize: config.stricts.api_maxpayload,
|
|
||||||
maxTextLenght: config.stricts.post_maxlenght,
|
|
||||||
|
|
||||||
renderValid: false,
|
|
||||||
loading: false,
|
|
||||||
|
|
||||||
textLenght: config.stricts.post_maxlenght,
|
|
||||||
rawText: '',
|
|
||||||
posting: false,
|
|
||||||
postingResult: false,
|
|
||||||
privacity: 0,
|
|
||||||
|
|
||||||
uploader: false,
|
|
||||||
uploaderFile: null,
|
|
||||||
uploaderFileOrigin: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
dropRef = React.createRef()
|
|
||||||
|
|
||||||
ToogleUploader() {
|
|
||||||
this.setState({ uploader: !this.state.uploader })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteFile = () => {
|
|
||||||
this.setState({ uploaderFile: null })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFileUpload = info => {
|
|
||||||
if (info.file.status === 'uploading') {
|
|
||||||
this.setState({ loading: true })
|
|
||||||
}
|
|
||||||
if (info.file.status === 'done') {
|
|
||||||
this.setState({ uploaderFileOrigin: info.file.originFileObj, uploader: false })
|
|
||||||
|
|
||||||
imageToBase64(info.file.originFileObj, fileURL => {
|
|
||||||
this.setState({ uploaderFile: fileURL, loading: false })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeUpload = file => {
|
|
||||||
const filter =
|
|
||||||
file.type === 'image/jpeg' ||
|
|
||||||
file.type === 'audio/mp3' ||
|
|
||||||
file.type === 'audio/wav' ||
|
|
||||||
file.type === 'audio/ogg' ||
|
|
||||||
file.type === 'image/png' ||
|
|
||||||
file.type === 'image/jpg' ||
|
|
||||||
file.type === 'image/gif' ||
|
|
||||||
file.type === 'video/mp4'
|
|
||||||
if (!filter) {
|
|
||||||
antd.message.error(`${file.type} This file is not valid!`)
|
|
||||||
}
|
|
||||||
const maxsize = file.size / 1024 / 1024 < stricts.api_maxpayload
|
|
||||||
if (!maxsize) {
|
|
||||||
antd.message.error(
|
|
||||||
`Image must smaller than ${stricts.api_maxpayload} KB!`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return filter && maxsize
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChanges = ({ target: { value } }) => {
|
|
||||||
this.setState({
|
|
||||||
rawText: value,
|
|
||||||
textLenght: this.state.maxTextLenght - value.length,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeysProgressBar() {
|
|
||||||
return this.state.textLenght <= (this.state.maxTextLenght / 100) * 30? 'exception' : 'active'
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDragIn = e => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
this.state.uploader? this.setState({ uploader: true }) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDragOut = e => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
this.state.uploader? null : this.setState({ uploader: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.props.app.session_data) {
|
|
||||||
this.setState({renderValid: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
// const _this = this
|
|
||||||
// $('body').bind('paste', function(je) {
|
|
||||||
// var e = je.originalEvent
|
|
||||||
// for (var i = 0; i < e.clipboardData.items.length; i++) {
|
|
||||||
// var item = e.clipboardData.items[i]
|
|
||||||
// if (item.type.indexOf('image') != -1) {
|
|
||||||
// //item.
|
|
||||||
// let a;
|
|
||||||
// a = item.getAsFile()
|
|
||||||
// _this.setState({ uploaderFileOrigin: a })
|
|
||||||
// ReadFileAsB64(a, res => {
|
|
||||||
// _this.setState({ uploaderFile: res })
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// // ignore not images
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// let div = this.dropRef.current
|
|
||||||
// div.addEventListener('dragenter', this.handleDragIn)
|
|
||||||
// div.addEventListener('dragleave', this.handleDragOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
// let div = this.dropRef.current
|
|
||||||
// div.removeEventListener('dragenter', this.handleDragIn)
|
|
||||||
// div.removeEventListener('dragleave', this.handleDragOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
canPost() {
|
|
||||||
const isTypedSomething = this.state.textLenght < this.state.maxTextLenght
|
|
||||||
const isUploadedFile = this.state.uploaderFile ? true : false
|
|
||||||
|
|
||||||
return isUploadedFile || isTypedSomething
|
|
||||||
}
|
|
||||||
|
|
||||||
renderShareOptions = () => {
|
|
||||||
return PrivacyList.map(e => {
|
|
||||||
if (!e) return null
|
|
||||||
return(
|
|
||||||
<antd.Menu.Item key={e.id}>
|
|
||||||
{e.icon? React.createElement(Icons[e.icon]) : null} {e.decoratorText? e.decoratorText : "Bruh"}
|
|
||||||
</antd.Menu.Item>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const userData = this.props.app.session_data
|
|
||||||
const { textLenght, uploaderFile } = this.state
|
|
||||||
|
|
||||||
const ShareOptionsMenu = () => {
|
|
||||||
return(
|
|
||||||
<antd.Menu onClick={e => this.setState({ privacity: e.key })}>
|
|
||||||
{this.renderShareOptions()}
|
|
||||||
</antd.Menu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PostCreator_Uploader = () => {
|
|
||||||
return(
|
|
||||||
<div className={styles.uploader}>
|
|
||||||
<antd.Upload.Dragger
|
|
||||||
multiple={false}
|
|
||||||
listType="picture"
|
|
||||||
showUploadList={false}
|
|
||||||
beforeUpload={this.beforeUpload}
|
|
||||||
onChange={this.handleFileUpload}
|
|
||||||
>
|
|
||||||
<Icons.CloudUploadOutlined />
|
|
||||||
<span>Drop your file here o click for upload</span>
|
|
||||||
</antd.Upload.Dragger>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PostCreator_InputText = () => {
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
<div className={styles.titleAvatar}>
|
|
||||||
<img src={userData.avatar} />
|
|
||||||
</div>
|
|
||||||
<antd.Input.TextArea
|
|
||||||
disabled={this.state.posting ? true : false}
|
|
||||||
onPressEnter={this.handlePublishPost}
|
|
||||||
value={this.state.rawText}
|
|
||||||
autoSize={{ minRows: 3, maxRows: 5 }}
|
|
||||||
dragable="false"
|
|
||||||
placeholder="What are you thinking?"
|
|
||||||
onChange={this.handleChanges}
|
|
||||||
allowClear
|
|
||||||
maxLength={this.state.maxTextLenght}
|
|
||||||
rows={8}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<antd.Button
|
|
||||||
disabled={this.state.posting ? true : !this.canPost()}
|
|
||||||
onClick={this.handlePublishPost}
|
|
||||||
type="primary"
|
|
||||||
icon={
|
|
||||||
this.state.postingResult ? (
|
|
||||||
<Icons.CheckCircleOutlined />
|
|
||||||
) : this.state.posting ? (
|
|
||||||
<Icons.LoadingOutlined />
|
|
||||||
) : (
|
|
||||||
<Icons.ExportOutlined />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(!this.state.renderValid) return null
|
|
||||||
return (
|
|
||||||
<div className={styles.cardWrapper}>
|
|
||||||
<antd.Card bordered="false">
|
|
||||||
<div ref={this.dropRef} className={styles.inputWrapper}>
|
|
||||||
{this.state.uploader ? <PostCreator_Uploader /> : <PostCreator_InputText /> }
|
|
||||||
</div>
|
|
||||||
<div className={styles.progressHandler}>
|
|
||||||
<antd.Progress
|
|
||||||
className={
|
|
||||||
this.state.posting
|
|
||||||
? styles.proccessUnset
|
|
||||||
: textLenght < 512
|
|
||||||
? styles.proccessSet
|
|
||||||
: styles.proccessUnset
|
|
||||||
}
|
|
||||||
percent={((textLenght / this.state.maxTextLenght) * 100).toFixed(2)}
|
|
||||||
status={this.handleKeysProgressBar()}
|
|
||||||
strokeWidth="4px"
|
|
||||||
showInfo={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{uploaderFile ? this.renderPostPlayer(uploaderFile) : null}
|
|
||||||
<div className={styles.postExtra}>
|
|
||||||
<antd.Button
|
|
||||||
styles={this.state.uploader ? { fontSize: '20px' } : null}
|
|
||||||
type="ghost"
|
|
||||||
onClick={() => this.ToogleUpload()}
|
|
||||||
>
|
|
||||||
|
|
||||||
{this.state.uploader ? (
|
|
||||||
<Icons.XCircle style={{ margin: 0 }} />
|
|
||||||
) : (
|
|
||||||
<Icons.Plus style={{ margin: 0, fontSize: '14px' }} />
|
|
||||||
)}
|
|
||||||
</antd.Button>
|
|
||||||
<antd.Button type="ghost" onClick={() => null}>
|
|
||||||
<Icons.Sliders style={{ margin: 0 }} />
|
|
||||||
</antd.Button>
|
|
||||||
<antd.Dropdown overlay={ShareOptionsMenu}>
|
|
||||||
<a className={styles.shareWith} onClick={e => e.preventDefault()}>
|
|
||||||
{PrivacyList[this.state.privacity].icon? React.createElement(Icons[PrivacyList[this.state.privacity].icon]) : null}
|
|
||||||
{PrivacyList[this.state.privacity].decoratorText? PrivacyList[this.state.privacity].decoratorText : "Bruh"}
|
|
||||||
</a>
|
|
||||||
</antd.Dropdown>
|
|
||||||
</div>
|
|
||||||
</antd.Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default PostCreator
|
|
@ -1,317 +0,0 @@
|
|||||||
@import '~theme/index.less';
|
|
||||||
|
|
||||||
.cardWrapper {
|
|
||||||
// box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
|
||||||
border-radius: 7px;
|
|
||||||
max-width: 510px;
|
|
||||||
min-width: 265px;
|
|
||||||
width: auto;
|
|
||||||
margin: 7px auto 50px;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
textarea {
|
|
||||||
font-weight: 500;
|
|
||||||
resize: none;
|
|
||||||
outline: none !important;
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea:focus {
|
|
||||||
outline: none !important;
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea:hover {
|
|
||||||
outline: none !important;
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-meta-detail>div:not(:last-child) {
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card {
|
|
||||||
border-radius: 7px;
|
|
||||||
border: 0;
|
|
||||||
border-top: 1px solid #4646460c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 5px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-actions {
|
|
||||||
border-top: 0;
|
|
||||||
background: #EBEBEB;
|
|
||||||
opacity: 0;
|
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
|
||||||
transition: opacity 150ms linear, position 150ms linear, transform 150ms linear;
|
|
||||||
border-radius: 0 0 10px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-actions:hover {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translate(0, 15px);
|
|
||||||
transition: opacity 150ms linear, position 150ms linear, transform 150ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-actions>li {
|
|
||||||
margin: -20px 0 0;
|
|
||||||
border-right: 0;
|
|
||||||
|
|
||||||
i {
|
|
||||||
|
|
||||||
vertical-align: middle;
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleAvatar {
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
img {
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputWrapper {
|
|
||||||
display: flex;
|
|
||||||
z-index: 10;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
padding: 18px 7px 0;
|
|
||||||
transition: height 150ms linear;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-btn-primary {
|
|
||||||
z-index: 10;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 0 10px 10px 0;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: bottom;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input {
|
|
||||||
z-index: 10;
|
|
||||||
position: relative;
|
|
||||||
border-color: transparent !important;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 3px 0 0;
|
|
||||||
height: 100%;
|
|
||||||
padding: 5px 10px;
|
|
||||||
transition: height 150ms linear;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input:hover {
|
|
||||||
border-color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input-affix-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progressHandler {
|
|
||||||
z-index: 10;
|
|
||||||
position: relative;
|
|
||||||
margin: 0 7px;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-progress-bg {
|
|
||||||
border-radius: 0 0 10px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-progress-inner {
|
|
||||||
border-radius: 0 0 14px 14px;
|
|
||||||
width: calc(100% - 32px);
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.postExtra {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
margin: 0 0 40px;
|
|
||||||
svg {
|
|
||||||
vertical-align: -0.125em;
|
|
||||||
}
|
|
||||||
.shareWith {
|
|
||||||
color: rgb(53, 53, 53);
|
|
||||||
float: right;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.MuiSvgIcon-root {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 18px;
|
|
||||||
transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 8px;
|
|
||||||
line-height: 1px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn .anticon {
|
|
||||||
transition: margin-left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn {
|
|
||||||
width: 35px;
|
|
||||||
height: 35px;
|
|
||||||
float: left;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 11px;
|
|
||||||
margin: 0 10px;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn:hover {
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploader {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 10px;
|
|
||||||
z-index: 30;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
span {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-upload.ant-upload-drag {
|
|
||||||
background: #fafafa;
|
|
||||||
border: 1px dashed #d9d9d9;
|
|
||||||
border-radius: 12px;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.anticon svg {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagePreviewWrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
// top: -100px;
|
|
||||||
margin: 0 0 15px;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
|
|
||||||
.imagePreview {
|
|
||||||
z-index: 5;
|
|
||||||
position: relative;
|
|
||||||
width: 50%;
|
|
||||||
margin: auto;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 150ms linear;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
border: 0.5px rgba(56, 56, 56, 0.459) solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
width: 100%;
|
|
||||||
border: 0.5px rgba(56, 56, 56, 0.459) solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
transition: all 150ms linear;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.imageOverlay {
|
|
||||||
z-index: 10;
|
|
||||||
position: relative;
|
|
||||||
opacity: 0;
|
|
||||||
transition: all 150ms linear;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagePreviewWrapper:hover .imagePreview {
|
|
||||||
opacity: 0.5;
|
|
||||||
transition: all 150ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagePreviewWrapper:hover .imageOverlay {
|
|
||||||
opacity: 1;
|
|
||||||
transition: all 150ms linear;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.proccessUnset {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 250ms linear;
|
|
||||||
animation: proccessUnset 250ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proccessSet {
|
|
||||||
transition: opacity 250ms linear;
|
|
||||||
animation: proccessSet 250ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes proccessSet {
|
|
||||||
0% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes proccessUnset {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fontct {
|
|
||||||
font-family: "Poppins", sans-serif;
|
|
||||||
}
|
|
@ -1,143 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { List } from 'antd'
|
|
||||||
import { connect } from 'umi'
|
|
||||||
import settings from 'core/libs/settings'
|
|
||||||
import { verbosity } from '@nodecorejs/utils'
|
|
||||||
import { PostCard, PostCreator, Invalid } from 'components'
|
|
||||||
import * as antd from 'antd'
|
|
||||||
import styles from './index.less'
|
|
||||||
|
|
||||||
@connect(({ app, socket }) => ({ app, socket }))
|
|
||||||
export default class PostsFeed extends React.Component {
|
|
||||||
state = {
|
|
||||||
socket: null,
|
|
||||||
feed: null,
|
|
||||||
renderError: false
|
|
||||||
}
|
|
||||||
|
|
||||||
addPostToRender(payload) {
|
|
||||||
let postSchema = {
|
|
||||||
id: this.state.feed[0].id + 1,
|
|
||||||
post_time: "who knows",
|
|
||||||
postText: "empty",
|
|
||||||
publisher: "me",
|
|
||||||
post_likes: 2500
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof(payload) !== "undefined") {
|
|
||||||
postSchema = { ...postSchema, ...payload }
|
|
||||||
}
|
|
||||||
|
|
||||||
let updated = this.state.feed
|
|
||||||
updated.push(postSchema)
|
|
||||||
this.setState({ feed: updated })
|
|
||||||
this.goPostById(postSchema.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
goPostById(id) {
|
|
||||||
document.getElementById(id).scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
block: "center",
|
|
||||||
inline: "center"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserIdByProps(id) {
|
|
||||||
if(typeof(id) == "string") {
|
|
||||||
return new Number(id)
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchFeed() {
|
|
||||||
const { socket } = this.state
|
|
||||||
if (socket) {
|
|
||||||
const requestPayload = {
|
|
||||||
from: this.props.from ?? "feed",
|
|
||||||
post_id: this.props.fromID ?? 0,
|
|
||||||
userToken: this.props.app.session_token,
|
|
||||||
id: this.getUserIdByProps(this.props.fromID) ?? this.props.app.session_uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestCallback = (data) => {
|
|
||||||
console.log(data)
|
|
||||||
if (Array.isArray(data.response)) {
|
|
||||||
this.setState({ feed: data.response })
|
|
||||||
} else {
|
|
||||||
verbosity.log(`error gathering posts >`, data)
|
|
||||||
this.setState({ renderError: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socket._emit("get", requestPayload, requestCallback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePostActions(action, post_id, callback) {
|
|
||||||
const { socket } = this.state
|
|
||||||
if (socket) {
|
|
||||||
const requestPayload = {
|
|
||||||
userToken: this.props.app.session_token,
|
|
||||||
post_id,
|
|
||||||
action
|
|
||||||
}
|
|
||||||
|
|
||||||
socket._emit("actions", requestPayload, (res) => callback(res))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.addPostToRender = (...context) => this.addPostToRender(...context)
|
|
||||||
|
|
||||||
if (this.props.app.session_valid) {
|
|
||||||
this.props.dispatch({
|
|
||||||
type: "socket/use",
|
|
||||||
persistent: true,
|
|
||||||
scope: "posts",
|
|
||||||
then: (data) => {
|
|
||||||
this.setState({ socket: data })
|
|
||||||
this.fetchFeed()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.state.socket) {
|
|
||||||
this.state.socket.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this.props.app.session_valid) {
|
|
||||||
return <Invalid type="SESSION_INVALID" />
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.state.feed) {
|
|
||||||
return (
|
|
||||||
<antd.Card bordered="false" >
|
|
||||||
<antd.Skeleton active />
|
|
||||||
</antd.Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.renderError) {
|
|
||||||
return (
|
|
||||||
<Invalid type="SESSION_INVALID" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.exploreWrapper}>
|
|
||||||
<List
|
|
||||||
//loadMore={loadMore}
|
|
||||||
dataSource={this.state.feed}
|
|
||||||
renderItem={item => (
|
|
||||||
<PostCard handleActions={(...context) => this.handlePostActions(...context)} payload={item} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
.exploreWrapper{
|
|
||||||
|
|
||||||
}
|
|
52
packages/app/src/components/QRReader/index.jsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React from "react"
|
||||||
|
import QrReader from "react-qr-reader"
|
||||||
|
import { Window } from "components"
|
||||||
|
|
||||||
|
export class Reader extends React.Component {
|
||||||
|
state = {
|
||||||
|
delay: 100,
|
||||||
|
result: "No result",
|
||||||
|
}
|
||||||
|
qrReaderRef = React.createRef()
|
||||||
|
|
||||||
|
handleScan = (data) => {
|
||||||
|
this.setState({
|
||||||
|
result: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleError = (err) => {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
openImageDialog = () => {
|
||||||
|
this.qrReaderRef.current.openImageDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const previewStyle = {
|
||||||
|
height: 240,
|
||||||
|
width: 320,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input type="button" value="Submit QR Code" onClick={this.openImageDialog} />
|
||||||
|
<p>{this.state.result}</p>
|
||||||
|
|
||||||
|
<QrReader
|
||||||
|
ref={this.qrReaderRef}
|
||||||
|
delay={this.state.delay}
|
||||||
|
style={previewStyle}
|
||||||
|
onError={this.handleError}
|
||||||
|
onScan={this.handleScan}
|
||||||
|
legacyMode
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openModal() {
|
||||||
|
new Window.DOMWindow({ id: "QRScanner", children: Reader }).create()
|
||||||
|
}
|
55
packages/app/src/components/RenderError/index.jsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Result, Button, Typography } from "antd"
|
||||||
|
import { CloseCircleOutlined } from "@ant-design/icons"
|
||||||
|
|
||||||
|
const { Paragraph, Text } = Typography
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
let errors = []
|
||||||
|
const getErrors = () => {
|
||||||
|
return errors.map((err) => {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return (
|
||||||
|
<Paragraph>
|
||||||
|
<CloseCircleOutlined style={{
|
||||||
|
color: "red",
|
||||||
|
marginRight: "10px",
|
||||||
|
}} />
|
||||||
|
{err.toString()}
|
||||||
|
</Paragraph>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return <div></div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(props.error)) {
|
||||||
|
errors = props.error
|
||||||
|
} else {
|
||||||
|
errors.push(props.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<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 will be automatically reported to the developers to find a solution as soon as possible)"
|
||||||
|
>
|
||||||
|
<div className="desc">
|
||||||
|
<Paragraph>
|
||||||
|
<Text
|
||||||
|
strong
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
We have detected the following errors:
|
||||||
|
</Text>
|
||||||
|
</Paragraph>
|
||||||
|
{getErrors()}
|
||||||
|
</div>
|
||||||
|
</Result>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
196
packages/app/src/components/RenderWindow/index.jsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
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.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 }
|
89
packages/app/src/components/RenderWindow/index.less
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
@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);
|
||||||
|
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;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
user-select: text !important;
|
||||||
|
}
|
||||||
|
}
|
22
packages/app/src/components/Roles/index.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
export default class Roles extends React.Component {
|
||||||
|
render() {
|
||||||
|
const roles = []
|
||||||
|
|
||||||
|
if (Array.isArray(this.props.roles)) {
|
||||||
|
this.props.roles.forEach((role) => {
|
||||||
|
roles.push(
|
||||||
|
<div key={role}>
|
||||||
|
<antd.Tag>{role}</antd.Tag>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="roles_wrapper">{roles}</div>
|
||||||
|
}
|
||||||
|
}
|
4
packages/app/src/components/Roles/index.less
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.roles_wrapper{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
194
packages/app/src/components/SelectableList/index.jsx
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
import { ActionsBar } from "components"
|
||||||
|
import { List, Button } from "antd"
|
||||||
|
import classnames from "classnames"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
export default class SelectableList extends React.Component {
|
||||||
|
state = {
|
||||||
|
selectedKeys: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (typeof this.props.defaultSelected !== "undefined" && Array.isArray(this.props.defaultSelected)) {
|
||||||
|
this.setState({
|
||||||
|
selectedKeys: [...this.props.defaultSelected],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickKey = (key) => {
|
||||||
|
if (typeof this.props.selectionEnabled !== "undefined") {
|
||||||
|
if (!Boolean(this.props.selectionEnabled)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = this.state.selectedKeys ?? []
|
||||||
|
|
||||||
|
if (!list.includes(key)) {
|
||||||
|
list.push(key)
|
||||||
|
} else {
|
||||||
|
list = list.filter((_key) => key !== _key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.setState({ selectedKeys: list })
|
||||||
|
}
|
||||||
|
|
||||||
|
onDone = () => {
|
||||||
|
if (typeof this.props.onDone === "function") {
|
||||||
|
this.props.onDone(this.state.selectedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedKeys: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onDiscard = () => {
|
||||||
|
if (typeof this.props.onDiscard === "function") {
|
||||||
|
this.props.onDiscard(this.state.selectedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedKeys: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActions = () => {
|
||||||
|
if (typeof this.props.renderActions !== "undefined" && !this.props.renderActions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this.state.selectedKeys.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderExtraActions = () => {
|
||||||
|
if (Array.isArray(this.props.actions)) {
|
||||||
|
return this.props.actions.map((action) => {
|
||||||
|
return (
|
||||||
|
<div key={action.key}>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
...action.props.style,
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (typeof action.onClick === "function") {
|
||||||
|
action.onClick(this.state.selectedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.props[action.props.call] !== "undefined") {
|
||||||
|
if (typeof this.props[action.props.call] === "function") {
|
||||||
|
let data = this.state.selectedKeys // by default send selectedKeys
|
||||||
|
|
||||||
|
if (typeof action.props.sendData === "string") {
|
||||||
|
switch (action.props.sendData) {
|
||||||
|
case "keys": {
|
||||||
|
data = this.state.selectedKeys
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
data = this.state.selectedKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props[action.props.call](data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{action}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bottomActions_wrapper">
|
||||||
|
<ActionsBar style={{ borderRadius: "8px 8px 0 0", width: "fit-content" }}>
|
||||||
|
<div key="discard">
|
||||||
|
<Button
|
||||||
|
style={{ display: "flex", alignItems: "center", justifyContent: "center" }}
|
||||||
|
shape="circle"
|
||||||
|
onClick={this.onDiscard}
|
||||||
|
{...this.props.onDiscardProps}
|
||||||
|
>
|
||||||
|
{this.props.onDiscardRender ?? <Icons.X style={{ margin: 0, padding: 0 }} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div key="done">
|
||||||
|
<Button type="primary" onClick={this.onDone} {...this.props.onDoneProps}>
|
||||||
|
{this.props.onDoneRender ?? (
|
||||||
|
<>
|
||||||
|
<Icons.Check /> Done
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{renderExtraActions()}
|
||||||
|
</ActionsBar>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const renderMethod = (item) => {
|
||||||
|
if (typeof this.props.renderItem === "function") {
|
||||||
|
const _key = item.key ?? item.id ?? item._id
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={_key}
|
||||||
|
id={_key}
|
||||||
|
onClick={() => this.onClickKey(_key)}
|
||||||
|
className={classnames("selectableList_item", this.props.itemClassName, {
|
||||||
|
selection: this.state.selectionEnabled,
|
||||||
|
selected: this.state.selectedKeys.includes(_key),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{this.props.renderItem(item)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("renderItem method is not defined!")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const { borderer, grid, header, loadMore, locale, pagination, rowKey, size, split, itemLayout, loading } =
|
||||||
|
this.props
|
||||||
|
const listProps = {
|
||||||
|
borderer,
|
||||||
|
grid,
|
||||||
|
header,
|
||||||
|
loadMore,
|
||||||
|
locale,
|
||||||
|
pagination,
|
||||||
|
rowKey,
|
||||||
|
size,
|
||||||
|
split,
|
||||||
|
itemLayout,
|
||||||
|
loading,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderActions()}
|
||||||
|
<List
|
||||||
|
{...listProps}
|
||||||
|
dataSource={[
|
||||||
|
...(Array.isArray(this.props.items) ? this.props.items : []),
|
||||||
|
...(Array.isArray(this.props.children) ? this.props.children : []),
|
||||||
|
]}
|
||||||
|
renderItem={renderMethod}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
59
packages/app/src/components/SelectableList/index.less
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
@selectableList_item_borderColor_active: rgba(51,51,51,1);
|
||||||
|
@selectableList_item_borderColor_normal: rgba(51,51,51,0.3);
|
||||||
|
|
||||||
|
.selectableList_item {
|
||||||
|
user-select: none;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
border: @selectableList_item_borderColor_normal 1px solid;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selection{
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: #dadada;
|
||||||
|
transform: translate(10px, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectableList_item:hover {
|
||||||
|
// transform: translate(2px, 2px);
|
||||||
|
box-shadow: 2px 2px 8px 0px rgba(51,51,51,0.5);
|
||||||
|
border: @selectableList_item_borderColor_active 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomActions_wrapper{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
74
packages/app/src/components/Sessions/index.jsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
export default class Sessions extends React.Component {
|
||||||
|
renderSessions = () => {
|
||||||
|
const data = this.props.sessions
|
||||||
|
|
||||||
|
return data.map((session) => {
|
||||||
|
const header = (
|
||||||
|
<div className="session_header">
|
||||||
|
<div>
|
||||||
|
<Icons.Key />
|
||||||
|
</div>
|
||||||
|
<div>{session._id}</div>
|
||||||
|
<div>{this.props.current === session.uuid ? <antd.Tag>Current</antd.Tag> : ""}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderDate = () => {
|
||||||
|
const dateNumber = Number(session.date)
|
||||||
|
|
||||||
|
if (dateNumber) {
|
||||||
|
return new Date(dateNumber).toString()
|
||||||
|
}
|
||||||
|
return session.date
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<antd.Collapse.Panel header={header} key={session._id} className="session_entry">
|
||||||
|
<div className="session_entry_info">
|
||||||
|
{session.allowRegenerate && (
|
||||||
|
<div style={{ color: "orange" }}>
|
||||||
|
<Icons.AlertTriangle />
|
||||||
|
This token can be regenerated
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<Icons.Clock />
|
||||||
|
{renderDate()}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Icons.Navigation />
|
||||||
|
{session.location}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Icons.Map />
|
||||||
|
{session.geo}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</antd.Collapse.Panel>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (Array.isArray(this.props.sessions)) {
|
||||||
|
return (
|
||||||
|
<div className="sessions_wrapper">
|
||||||
|
<h1>
|
||||||
|
All Sessions
|
||||||
|
</h1>
|
||||||
|
<antd.Collapse bordered={false} accordion>
|
||||||
|
{this.renderSessions()}
|
||||||
|
</antd.Collapse>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div></div>
|
||||||
|
}
|
||||||
|
}
|
35
packages/app/src/components/Sessions/index.less
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
.sessions_wrapper {
|
||||||
|
.ant-collapse-borderless{
|
||||||
|
background-color: transparent!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.session_entry {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
border: 1px solid #ccc!important;
|
||||||
|
border-radius: 12px!important;
|
||||||
|
|
||||||
|
.session_entry_info{
|
||||||
|
> div {
|
||||||
|
padding: 4px 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-collapse-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.session_header{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
> div {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
235
packages/app/src/components/Settings/index.jsx
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import { SketchPicker } from "react-color"
|
||||||
|
import { AboutApp } from ".."
|
||||||
|
import config from "config"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const ItemTypes = {
|
||||||
|
Button: antd.Button,
|
||||||
|
Switch: antd.Switch,
|
||||||
|
Slider: antd.Slider,
|
||||||
|
Checkbox: antd.Checkbox,
|
||||||
|
Input: antd.Input,
|
||||||
|
InputNumber: antd.InputNumber,
|
||||||
|
Select: antd.Select,
|
||||||
|
ColorPicker: SketchPicker,
|
||||||
|
}
|
||||||
|
|
||||||
|
import settingList from "schemas/settings.json"
|
||||||
|
import groupsDecorator from "schemas/settingsGroups.json"
|
||||||
|
import { Session } from "models"
|
||||||
|
|
||||||
|
export class SettingsMenu extends React.Component {
|
||||||
|
state = {
|
||||||
|
settings: window.app.configuration.settings.get() ?? {},
|
||||||
|
}
|
||||||
|
|
||||||
|
_set(key, value) {
|
||||||
|
this.setState({ settings: window.app.configuration.settings.change(key, value) })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(event, id, type) {
|
||||||
|
if (typeof id === "undefined") {
|
||||||
|
console.error(`No setting id provided!`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (typeof type !== "string") {
|
||||||
|
console.error(`Invalid eventType data-type, expecting string!`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = window.app.configuration.settings.get(id) ?? false
|
||||||
|
let to = !value
|
||||||
|
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case "button": {
|
||||||
|
window.app.configuration.settings.events.emit("changeSetting", { event, id, value, to })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
this._set(id, to)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateMenu(data) {
|
||||||
|
let items = {}
|
||||||
|
|
||||||
|
const renderGroupItems = (group) => {
|
||||||
|
return items[group].map((item) => {
|
||||||
|
if (!item.type) {
|
||||||
|
console.error(`Item [${item.id}] has no an type!`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (typeof ItemTypes[item.type] === "undefined") {
|
||||||
|
console.error(`Item [${item.id}] has an invalid type: ${item.type}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof item.props === "undefined") {
|
||||||
|
item.props = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix handlers
|
||||||
|
switch (item.type.toLowerCase()) {
|
||||||
|
case "colorpicker": {
|
||||||
|
item.props.onChange = (value) => {
|
||||||
|
item.props.color = value.hex
|
||||||
|
}
|
||||||
|
item.props.onChangeComplete = (color, event) => {
|
||||||
|
window.app.configuration.settings.events.emit("changeSetting", { id: item.id, event, value: color })
|
||||||
|
this._set(item.id, color.hex)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "switch": {
|
||||||
|
item.props.children = item.title ?? item.id
|
||||||
|
item.props.checked = this.state.settings[item.id]
|
||||||
|
item.props.onClick = (e) => this.handleEvent(e, item.id ?? "anon", item.type)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
item.props.children = item.title ?? item.id
|
||||||
|
item.props.value = this.state.settings[item.id]
|
||||||
|
item.props.onClick = (e) => this.handleEvent(e, item.id ?? "anon", item.type)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={item.id}>
|
||||||
|
<h5>
|
||||||
|
{item.icon ? React.createElement(Icons[item.icon]) : null}
|
||||||
|
{item.title ?? item.id}
|
||||||
|
</h5>
|
||||||
|
{item.render ??
|
||||||
|
React.createElement(ItemTypes[item.type], {
|
||||||
|
...item.props,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderGroupDecorator = (group) => {
|
||||||
|
if (group === "none") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const fromDecoratorIcon = groupsDecorator[group]?.icon
|
||||||
|
const fromDecoratorTitle = groupsDecorator[group]?.title
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
{fromDecoratorIcon ? React.createElement(Icons[fromDecoratorIcon]) : null}{" "}
|
||||||
|
{fromDecoratorTitle ?? group}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
data.forEach((item) => {
|
||||||
|
if (typeof item.group == "undefined") {
|
||||||
|
item.group = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!items[item.group]) {
|
||||||
|
items[item.group] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
items[item.group].push(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(items).map((group) => {
|
||||||
|
return (
|
||||||
|
<div key={group} style={{ marginBottom: "30px" }}>
|
||||||
|
{renderGroupDecorator(group)}
|
||||||
|
<div key={group} className="settings_groupItems">
|
||||||
|
{renderGroupItems(group)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAboutApp() {
|
||||||
|
const appConfig = config.app
|
||||||
|
const eviteNamespace = window.__evite
|
||||||
|
const isDevMode = eviteNamespace.env.NODE_ENV !== "production"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="settings_about_app">
|
||||||
|
<div>{appConfig.siteName}</div>
|
||||||
|
<div>
|
||||||
|
<antd.Tag>
|
||||||
|
<Icons.Tag />v{eviteNamespace.projectVersion}
|
||||||
|
</antd.Tag>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<antd.Tag color={isDevMode ? "magenta" : "green"}>
|
||||||
|
{isDevMode ? <Icons.Triangle /> : <Icons.Box />}
|
||||||
|
{isDevMode ? "development" : "stable"}
|
||||||
|
</antd.Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLogout() {
|
||||||
|
if (window.app.isValidSession()) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<antd.Button
|
||||||
|
onClick={() => {
|
||||||
|
Session.logout()
|
||||||
|
}}
|
||||||
|
type="danger"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.generateMenu(settingList)}
|
||||||
|
<div className="settings_bottom_items">
|
||||||
|
{this.renderLogout()}
|
||||||
|
{this.renderAboutApp()}
|
||||||
|
<antd.Button type="link" onClick={() => AboutApp.openModal()}>
|
||||||
|
About
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = {
|
||||||
|
open: (key) => {
|
||||||
|
// TODO: Scroll to content
|
||||||
|
window.app.DrawerController.open("settings", SettingsMenu, {
|
||||||
|
props: {
|
||||||
|
width: "45%",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
close: () => {
|
||||||
|
window.app.DrawerController.close("settings")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default controller
|
44
packages/app/src/components/Settings/index.less
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
.settings_groupItems{
|
||||||
|
> div {
|
||||||
|
padding: 12px 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings_about_app{
|
||||||
|
font-family: 'Space Mono', monospace;
|
||||||
|
font-size: 10px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.ant-tag{
|
||||||
|
height: 18px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
padding: 0 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings_bottom_items {
|
||||||
|
position: relative;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import "./index.less"
|
|
||||||
|
|
||||||
export default class Splash extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="splash-wrapper">
|
|
||||||
<div className="bouncy-logo">
|
|
||||||
<div className="ball">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
||||||
viewBox="0 0 100 120"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<linearGradient
|
|
||||||
id="glowingLinearGradient"
|
|
||||||
x1="0"
|
|
||||||
y1="0"
|
|
||||||
x2="100%"
|
|
||||||
y2="100%"
|
|
||||||
gradientUnits="userSpaceOnUse"
|
|
||||||
>
|
|
||||||
<stop stopColor="#d03c4a" offset="10%" />
|
|
||||||
<stop stopColor="#ac4ada" offset="20%" />
|
|
||||||
<stop stopColor="#0087ff" offset="30%" />
|
|
||||||
<stop stopColor="#2400ff" offset="40%" />
|
|
||||||
<stop stopColor="#ff1d7a" offset="70%" />
|
|
||||||
<stop stopColor="#f5381b" offset="80%" />
|
|
||||||
<stop stopColor="#ff5335" offset="90%" />
|
|
||||||
<stop stopColor="#691eff" offset="100%" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path
|
|
||||||
style={{ stroke: "url(#glowingLinearGradient)" }}
|
|
||||||
d="M77.55,29.69,92,18.78a1.42,1.42,0,0,0,.25-2,39.2,39.2,0,0,0-56.31-4.21A38.05,38.05,0,0,0,23.23,42a38.09,38.09,0,0,0,3.62,15.1A38.65,38.65,0,0,0,37.8,70.84,39.46,39.46,0,0,0,83.37,73a38.26,38.26,0,0,0,8.41-7.4,1.41,1.41,0,0,0-.23-2L77.65,53a1.43,1.43,0,0,0-1.9.15A17,17,0,0,1,72.55,56a17.75,17.75,0,0,1-9,2.88c-8.32.31-13.62-5.69-14-6.13a17.68,17.68,0,0,1-4.13-10.13,17.93,17.93,0,0,1,4.56-13A17.46,17.46,0,0,1,71.7,26.34a17.3,17.3,0,0,1,4,3.2A1.41,1.41,0,0,0,77.55,29.69Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
style={{ stroke: "url(#glowingLinearGradient)" }}
|
|
||||||
d="M13,63.17a2.77,2.77,0,0,1,3.75,1.43A48.38,48.38,0,0,0,32.07,84.53,48.83,48.83,0,0,0,52.34,93.3,47.37,47.37,0,0,0,92.57,81.8a2.77,2.77,0,0,1,4,.3l6.23,7.4a2.79,2.79,0,0,1-.21,3.83,63.83,63.83,0,0,1-6,5,62.21,62.21,0,0,1-7.44,4.7A60.84,60.84,0,0,1,77,108a62.3,62.3,0,0,1-27,1.51A62.51,62.51,0,0,1,40.18,107,61.5,61.5,0,0,1,20.1,95.69,61.73,61.73,0,0,1,2.41,71a2.79,2.79,0,0,1,1.42-3.55Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="glow"></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|