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: {
|
||||
guid: "7d6b74b5-1b3b-432f-97df-2c5fc2c2b6ae",
|
||||
siteName: 'Comty™',
|
||||
copyright: 'RageStudio©',
|
||||
MainPath: '/',
|
||||
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',
|
||||
mainPath: '/main',
|
||||
|
||||
appTheme_desiredContrast: 7,
|
||||
// Contrast level AA = 4.5, Level AAA = 7
|
||||
// Reference: https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0&showtechniques=143#qr-visual-audio-contrast-contrast
|
||||
},
|
||||
|
||||
i18n: {
|
||||
languages: [
|
||||
{
|
||||
@ -39,40 +26,6 @@ module.exports = {
|
||||
],
|
||||
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: {
|
||||
post_maxlenght: '512',
|
||||
// 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",
|
||||
"icon": "Watch",
|
||||
"id": "expire_session",
|
||||
"icon": "Key",
|
||||
"title": "Expire Session",
|
||||
"group": "general",
|
||||
"type": "Switch"
|
||||
},
|
||||
{
|
||||
"id": "post_autoposition",
|
||||
"icon": "AlignCenter",
|
||||
"type": "Switch",
|
||||
"title": "No expire session",
|
||||
"description": "Force the app to not expire any session"
|
||||
"title": "Center on click",
|
||||
"description": "Center posts element when then is clicked"
|
||||
},
|
||||
{
|
||||
"id": "search_ontype",
|
||||
@ -21,17 +28,22 @@
|
||||
"description": "Hide post actions bar when loose focus"
|
||||
},
|
||||
{
|
||||
"id": "post_autoposition",
|
||||
"icon": "AlignCenter",
|
||||
"type": "Switch",
|
||||
"title": "Center on click",
|
||||
"description": "Center posts element when then is clicked"
|
||||
"id": "edit_sidebar",
|
||||
"group": "sidebar",
|
||||
"icon": "Edit",
|
||||
"title": "Edit Sidebar",
|
||||
"type": "Button"
|
||||
},
|
||||
{
|
||||
"id": "verbosity",
|
||||
"icon": "Terminal",
|
||||
"type": "Switch",
|
||||
"title": "Verbosity",
|
||||
"description": "Show all development logs of the application"
|
||||
"id": "collapseOnLooseFocus",
|
||||
"group": "sidebar",
|
||||
"title": "Collapse when loose focus",
|
||||
"type": "Switch"
|
||||
},
|
||||
{
|
||||
"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 classnames from "classnames"
|
||||
|
||||
import { Sidebar, Header, Drawer, Sidedrawer } from "./layout"
|
||||
import { NotFound, RenderError } from "components"
|
||||
import { Icons } from "components/Icons"
|
||||
import { CreateEviteApp, BindPropsProvider } from "evite"
|
||||
|
||||
import config from "config"
|
||||
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 SettingsController from "core/models/settings"
|
||||
|
||||
import { CreateEviteApp, BindPropsProvider } from "evite"
|
||||
import { API, Render, Splash, Debug, theme, Sound } from "extensions"
|
||||
|
||||
import { Sidebar, Header, Drawer, Sidedrawer } from "./layout"
|
||||
import "theme/index.less"
|
||||
|
||||
// append method to array prototype
|
||||
@ -25,20 +22,6 @@ Array.prototype.move = function (from, to) {
|
||||
return this
|
||||
}
|
||||
|
||||
const SplashExtension = Splash.extension({
|
||||
logo: config.logo.alt,
|
||||
preset: "fadeOut",
|
||||
velocity: 1000,
|
||||
props: {
|
||||
logo: {
|
||||
style: {
|
||||
marginBottom: "10%",
|
||||
stroke: "black",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
class ThrowCrash {
|
||||
constructor(message, description) {
|
||||
this.message = message
|
||||
@ -156,14 +139,14 @@ class App {
|
||||
}
|
||||
|
||||
static staticRenders = {
|
||||
on404: (props) => {
|
||||
NotFound: (props) => {
|
||||
return <NotFound />
|
||||
},
|
||||
onRenderError: (props) => {
|
||||
RenderError: (props) => {
|
||||
return <RenderError {...props} />
|
||||
},
|
||||
initialization: () => {
|
||||
return <Splash.SplashComponent logo={config.logo.alt} />
|
||||
return <h1>Initializing...</h1>
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,7 +247,7 @@ class App {
|
||||
user={this.state.user}
|
||||
session={this.state.session}
|
||||
>
|
||||
<Render.RenderController staticRenders={App.staticRenders} />
|
||||
<Render.RenderRouter staticRenders={App.staticRenders} />
|
||||
</BindPropsProvider>
|
||||
</div>
|
||||
</antd.Layout.Content>
|
||||
@ -277,5 +260,5 @@ class 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'
|
||||
export * from '@ant-design/icons'
|
||||
export * from './custom'
|
||||
export * from '@icons-pack/react-simple-icons'
|
||||
import React from "react"
|
||||
|
||||
// import icons lib
|
||||
import * as lib1 from "feather-reactjs"
|
||||
import * as lib2 from "react-icons/md"
|
||||
import * as lib3 from "@ant-design/icons"
|
||||
|
||||
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 {
|
||||
font-family: "Nunito", sans-serif;
|
||||
@ -7,21 +7,21 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
color: @__Global_layout_color;
|
||||
background-color: #ffffff;
|
||||
color: @__global_color;
|
||||
background-color: @__global_background;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
|
||||
|
||||
:global {
|
||||
.ant-list-item {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
|
||||
.ant-list-split .ant-list-item {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
.ant-list-item-meta-title {
|
||||
color: rgba(0, 0, 0, 0.733);
|
||||
font-size: 14px;
|
||||
@ -33,7 +33,7 @@
|
||||
width: 224px;
|
||||
:global {
|
||||
.ant-menu-inline {
|
||||
color: @__Global_layout_color;
|
||||
color: @__global_color;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
@ -52,32 +52,22 @@
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
color: @__Global_layout_color;
|
||||
color: @__global_color;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline{
|
||||
|
||||
}
|
||||
|
||||
&.horizontal{
|
||||
&.horizontal {
|
||||
flex-direction: column;
|
||||
.menuList{
|
||||
.menuList {
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.vertical{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: @screen-md) {
|
||||
.main {
|
||||
flex-direction: column;
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Menu, Result } from 'antd'
|
||||
import classnames from 'classnames'
|
||||
import { Icons } from 'components/Icons'
|
||||
|
||||
import styles from './index.less'
|
||||
import { __proto__filterSchematizedArray } from 'core'
|
||||
import { objectToArrayMap } from '@corenode/utils'
|
||||
|
||||
export default class ListedMenu extends React.Component {
|
||||
state = {
|
||||
@ -16,7 +17,7 @@ export default class ListedMenu extends React.Component {
|
||||
|
||||
async queryMenu() {
|
||||
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() {
|
||||
@ -27,20 +28,20 @@ export default class ListedMenu extends React.Component {
|
||||
))
|
||||
}
|
||||
|
||||
selectKey = (key: any) => {
|
||||
selectKey = (key) => {
|
||||
this.setState({
|
||||
selectKey: key,
|
||||
})
|
||||
}
|
||||
|
||||
renderChildren = () => {
|
||||
let titlesArray: never[] = []
|
||||
let titlesArray = []
|
||||
this.state.menus.forEach(e => { titlesArray[e.key] = e })
|
||||
|
||||
const OptionTitle = () => {
|
||||
if (this.state.renderOptionTitle) {
|
||||
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>
|
||||
}
|
||||
return null
|
||||
@ -79,7 +80,7 @@ export default class ListedMenu extends React.Component {
|
||||
|
||||
render() {
|
||||
const { selectKey, loading } = this.state
|
||||
const isMode = (e: string) => {
|
||||
const isMode = (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 className={styles.menuList}>
|
||||
<h3>
|
||||
{this.props.icon ?? null} {this.props.title ?? "Menu"}
|
||||
{React.createElement(Icons[this.props.icon]) ?? null} {this.props.title ?? "Menu"}
|
||||
</h3>
|
||||
<Menu
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|