use evite as engine

This commit is contained in:
srgooglo 2021-11-17 17:58:04 +01:00
parent 02e9d6985a
commit f6ce583c52
169 changed files with 5412 additions and 6204 deletions

36
packages/app/.config.js Normal file
View 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
}

View File

@ -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

View File

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

View File

@ -0,0 +1,3 @@
{
"collapseOnLooseFocus": true
}

View File

@ -0,0 +1,4 @@
[
"main",
"posts"
]

View File

@ -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"
}

View 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"
}
}

View File

@ -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"
}
]
]

View File

@ -0,0 +1,14 @@
{
"general": {
"title": "General",
"icon": "Settings"
},
"sidebar": {
"title": "Sidebar",
"icon": "Layout"
},
"aspect": {
"title": "Aspect",
"icon": "Eye"
}
}

View File

@ -0,0 +1,14 @@
[
{
"id": "main",
"title": "Dashboard",
"icon": "Home",
"locked": true
},
{
"id": "posts",
"title": "Posts",
"icon": "Home",
"locked": true
}
]

View File

@ -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"
}
}
]

View File

@ -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],
})

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

View File

@ -0,0 +1,5 @@
import crash from './crash.wav'
export default {
"crash": crash
}

View File

@ -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>
)
}
}

View File

@ -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;
}

View 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)
}

View 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 {
}

View 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>
}

View 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;
}
}

View 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>
}

View 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>
)
}
}

View 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;
}
}

View File

@ -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

View 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>
)
})
}

View File

@ -0,0 +1 @@
export { default as Operations } from './operations'

View File

@ -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>
}
}

View 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>
}
}

View 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;
}
}
}

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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"
/>
}
}

View File

@ -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%;
}

View File

@ -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%);
}
}

View File

@ -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

View File

@ -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>
)
}
}

View File

@ -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;
}
}

View File

@ -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>
}
}

View File

@ -1,4 +0,0 @@
.suggestions_wrapper{
}

View File

@ -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>
}
}

View File

@ -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;
}
}

View File

@ -1,5 +0,0 @@
import Primary from './layout/Primary.tsx'
export {
Primary
}

View File

@ -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

View File

@ -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>
)
}
}

View File

@ -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)
}

View File

@ -1,11 +0,0 @@
import React from 'react'
import * as antd from 'antd'
import { CardComponent } from 'components'
export default () => {
return (
<CardComponent>
Invalid Component
</CardComponent>
)
}

View File

@ -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
}
}

View File

@ -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>
)
}
}

View File

@ -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;
}
}

View File

@ -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>
)
}
}

View File

@ -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>
)
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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>
)
}
}

View File

@ -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;
}
}
}
}

View File

@ -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>
)
}
}

View File

@ -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);
}

View File

@ -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 }

View File

@ -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;

View File

@ -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}

View File

@ -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

View File

@ -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;
}
}

View 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 />} />
}

View File

@ -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>
)
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
import React from "react"
import { notification } from "antd"
export default {
error: (...context) => {
notification.error(context)
},
}

View File

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

View File

@ -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)';
}
}

View File

@ -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,
};

View File

@ -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)
}
})
}

View File

@ -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>
)
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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>
)
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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>
)
}
}

View File

@ -1,3 +0,0 @@
.exploreWrapper{
}

View 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()
}

View 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>
)
}

View 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 }

View 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;
}
}

View 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>
}
}

View File

@ -0,0 +1,4 @@
.roles_wrapper{
display: flex;
flex-direction: row;
}

View 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>
)
}
}

View 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;
}

View 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>
}
}

View 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;
}
}

View 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

View 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;
}
}

View File

@ -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>
)
}
}

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