2
comty.js
@ -1 +1 @@
|
||||
Subproject commit 6c7a218716a8de758ad34543301f958f0826fe09
|
||||
Subproject commit bce0abd21b00e0bcc163e612cb603e88cf8c02f3
|
@ -1,41 +1,43 @@
|
||||
import copyToClipboard from "@utils/copyToClipboard"
|
||||
import pasteFromClipboard from "@utils/pasteFromClipboard"
|
||||
|
||||
export default {
|
||||
"default-context": (items) => {
|
||||
const text = window.getSelection().toString()
|
||||
"default-context": (items) => {
|
||||
const text = window.getSelection().toString()
|
||||
|
||||
if (text) {
|
||||
items.push({
|
||||
label: "Copy",
|
||||
icon: "FiCopy",
|
||||
action: (clickedItem, ctx) => {
|
||||
copyToClipboard(text)
|
||||
if (text) {
|
||||
items.push({
|
||||
label: "Copy",
|
||||
icon: "FiCopy",
|
||||
action: (clickedItem, ctx) => {
|
||||
copyToClipboard(text)
|
||||
|
||||
ctx.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
ctx.close()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: "Paste",
|
||||
icon: "FiClipboard",
|
||||
action: (clickedItem, ctx) => {
|
||||
app.message.error("This action is not supported by your browser")
|
||||
items.push({
|
||||
label: "Paste",
|
||||
icon: "FiClipboard",
|
||||
action: (clickedItem, ctx) => {
|
||||
pasteFromClipboard(clickedItem)
|
||||
ctx.close()
|
||||
},
|
||||
})
|
||||
|
||||
ctx.close()
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
label: "Report a bug",
|
||||
icon: "FiAlertTriangle",
|
||||
action: (clickedItem, ctx) => {
|
||||
app.eventBus.emit("app.reportBug", {
|
||||
clickedItem,
|
||||
})
|
||||
|
||||
items.push({
|
||||
label: "Report a bug",
|
||||
icon: "FiAlertTriangle",
|
||||
action: (clickedItem, ctx) => {
|
||||
app.eventBus.emit("app.reportBug", {
|
||||
clickedItem,
|
||||
})
|
||||
ctx.close()
|
||||
},
|
||||
})
|
||||
|
||||
ctx.close()
|
||||
}
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
return items
|
||||
},
|
||||
}
|
||||
|
@ -2,69 +2,73 @@ import copyToClipboard from "@utils/copyToClipboard"
|
||||
import download from "@utils/download"
|
||||
|
||||
export default {
|
||||
"post-card": (items, parent, element, control) => {
|
||||
items.push({
|
||||
label: "Copy ID",
|
||||
icon: "FiCopy",
|
||||
action: () => {
|
||||
copyToClipboard(parent.id)
|
||||
control.close()
|
||||
}
|
||||
})
|
||||
"post-card": (items, parent, element, control) => {
|
||||
if (!parent.id) {
|
||||
parent = parent.parentNode
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: "Copy Link",
|
||||
icon: "FiLink",
|
||||
action: () => {
|
||||
copyToClipboard(`${window.location.origin}/post/${parent.id}`)
|
||||
control.close()
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
label: "Copy ID",
|
||||
icon: "FiCopy",
|
||||
action: () => {
|
||||
copyToClipboard(parent.id)
|
||||
control.close()
|
||||
},
|
||||
})
|
||||
|
||||
let media = null
|
||||
items.push({
|
||||
label: "Copy Link",
|
||||
icon: "FiLink",
|
||||
action: () => {
|
||||
copyToClipboard(`${window.location.origin}/post/${parent.id}`)
|
||||
control.close()
|
||||
},
|
||||
})
|
||||
|
||||
if (parent.querySelector(".attachment")) {
|
||||
media = parent.querySelector(".attachment")
|
||||
media = media.querySelector("video, img")
|
||||
let media = null
|
||||
|
||||
if (media.querySelector("source")) {
|
||||
media = media.querySelector("source")
|
||||
}
|
||||
}
|
||||
if (parent.querySelector(".attachment")) {
|
||||
media = parent.querySelector(".attachment")
|
||||
media = media.querySelector("video, img")
|
||||
|
||||
if (media) {
|
||||
items.push({
|
||||
type: "separator",
|
||||
})
|
||||
if (media.querySelector("source")) {
|
||||
media = media.querySelector("source")
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: "Copy media URL",
|
||||
icon: "FiCopy",
|
||||
action: () => {
|
||||
copyToClipboard(media.src)
|
||||
control.close()
|
||||
}
|
||||
})
|
||||
if (media) {
|
||||
items.push({
|
||||
type: "separator",
|
||||
})
|
||||
|
||||
items.push({
|
||||
label: "Open media in new tab",
|
||||
icon: "FiExternalLink",
|
||||
action: () => {
|
||||
window.open(media.src, "_blank")
|
||||
control.close()
|
||||
}
|
||||
})
|
||||
items.push({
|
||||
label: "Copy media URL",
|
||||
icon: "FiCopy",
|
||||
action: () => {
|
||||
copyToClipboard(media.src)
|
||||
control.close()
|
||||
},
|
||||
})
|
||||
|
||||
items.push({
|
||||
label: "Download media",
|
||||
icon: "FiDownload",
|
||||
action: () => {
|
||||
download(media.src)
|
||||
control.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
label: "Open media in new tab",
|
||||
icon: "FiExternalLink",
|
||||
action: () => {
|
||||
window.open(media.src, "_blank")
|
||||
control.close()
|
||||
},
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
items.push({
|
||||
label: "Download media",
|
||||
icon: "FiDownload",
|
||||
action: () => {
|
||||
download(media.src)
|
||||
control.close()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
},
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
{
|
||||
"name": "@comty/app",
|
||||
"version": "1.36.0@alpha",
|
||||
"version": "1.37.0@alpha",
|
||||
"license": "ComtyLicense",
|
||||
"main": "electron/main",
|
||||
"type": "module",
|
||||
"author": "RageStudio",
|
||||
"description": "A prototype of a social network.",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite",
|
||||
"docker-compose:update_run": "docker-compose down && git pull && yarn build && docker-compose up -d --build",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"release": "node ./scripts/release.js"
|
||||
},
|
||||
@ -33,7 +32,7 @@
|
||||
"axios": "^1.7.7",
|
||||
"bear-react-carousel": "^4.0.10-alpha.0",
|
||||
"classnames": "2.3.1",
|
||||
"comty.js": "^0.61.0",
|
||||
"comty.js": "^0.63.0",
|
||||
"dashjs": "^4.7.4",
|
||||
"dompurify": "^3.0.0",
|
||||
"fast-average-color": "^9.2.0",
|
||||
|
3
packages/app/src-tauri/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
5277
packages/app/src-tauri/Cargo.lock
generated
@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "comty"
|
||||
version = "0.1.0"
|
||||
description = "Comty Desktop APP"
|
||||
authors = ["RageStudio"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "comty"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.6.7", features = [ "api-all"] }
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
|
||||
# DO NOT REMOVE!!
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 27 KiB |
@ -1,14 +0,0 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![my_custom_command])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn my_custom_command() {
|
||||
println!("I was invoked from JS!");
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"devPath": "https://fr01.ragestudio.net:8000",
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "Comty",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": true,
|
||||
"window": {
|
||||
"all": true,
|
||||
"close": true,
|
||||
"hide": true,
|
||||
"show": true,
|
||||
"maximize": true,
|
||||
"minimize": true,
|
||||
"unmaximize": true,
|
||||
"unminimize": true,
|
||||
"startDragging": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"identifier": "com.ragestudio.comty",
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": ""
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "Comty",
|
||||
"titleBarStyle": "Overlay",
|
||||
"hiddenTitle": true,
|
||||
"decorations": true,
|
||||
"fullscreen": false,
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"resizable": true,
|
||||
"center": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -335,10 +335,17 @@ export default class PostCreator extends React.Component {
|
||||
}
|
||||
|
||||
handleOnMentionSearch = lodash.debounce(async (value) => {
|
||||
const results = await SearchModel.userSearch(`username:${value}`)
|
||||
if (value === "") {
|
||||
return false
|
||||
}
|
||||
|
||||
const results = await SearchModel.search(`${value}`, {
|
||||
fields: "users",
|
||||
limit: 5,
|
||||
})
|
||||
|
||||
this.setState({
|
||||
mentionsLoadedData: results,
|
||||
mentionsLoadedData: results.users.items,
|
||||
})
|
||||
}, 300)
|
||||
|
||||
@ -674,13 +681,20 @@ export default class PostCreator extends React.Component {
|
||||
draggable={false}
|
||||
prefix="@"
|
||||
allowClear
|
||||
onBlur={() => {
|
||||
this.setState({ mentionsLoadedData: [] })
|
||||
}}
|
||||
options={this.state.mentionsLoadedData.map((item) => {
|
||||
return {
|
||||
key: item.id,
|
||||
value: item.username,
|
||||
label: (
|
||||
<>
|
||||
<antd.Avatar src={item.avatar} />
|
||||
<antd.Avatar
|
||||
size={24}
|
||||
src={item.avatar}
|
||||
shape="square"
|
||||
/>
|
||||
<span>{item.username}</span>
|
||||
</>
|
||||
),
|
||||
|
@ -9,13 +9,12 @@ const ContextMenu = (props) => {
|
||||
const [visible, setVisible] = React.useState(true)
|
||||
const { items = [], cords, clickedComponent, ctx } = props
|
||||
|
||||
async function onClose() {
|
||||
setVisible(false)
|
||||
props.unregisterOnClose(onClose)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
props.registerOnClose(onClose)
|
||||
if (props.fireWhenClosing) {
|
||||
props.fireWhenClosing(() => {
|
||||
setVisible(false)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleItemClick = async (item) => {
|
||||
|
@ -1,158 +1,211 @@
|
||||
import React from "react"
|
||||
|
||||
import { Core, EventBus } from "@ragestudio/vessel"
|
||||
|
||||
import ContextMenu from "./components/contextMenu"
|
||||
|
||||
import DefaultContenxt from "@config/context-menu/default"
|
||||
import DefaultContext from "@config/context-menu/default"
|
||||
import PostCardContext from "@config/context-menu/post"
|
||||
|
||||
export default class ContextMenuCore extends Core {
|
||||
static namespace = "contextMenu"
|
||||
static namespace = "contextMenu"
|
||||
|
||||
contexts = {
|
||||
...DefaultContenxt,
|
||||
...PostCardContext,
|
||||
}
|
||||
contexts = {
|
||||
...DefaultContext,
|
||||
...PostCardContext,
|
||||
}
|
||||
|
||||
eventBus = new EventBus()
|
||||
eventBus = new EventBus()
|
||||
isMenuOpen = false
|
||||
fireWhenClosing = null
|
||||
|
||||
async onInitialize() {
|
||||
if (app.isMobile) {
|
||||
this.console.warn("Context menu is not available on mobile")
|
||||
return false
|
||||
}
|
||||
async onInitialize() {
|
||||
if (app.isMobile) {
|
||||
this.console.warn("Context menu is not available on mobile")
|
||||
return false
|
||||
}
|
||||
|
||||
document.addEventListener("contextmenu", this.handleEvent.bind(this))
|
||||
}
|
||||
document.addEventListener("contextmenu", this.handleEvent)
|
||||
}
|
||||
|
||||
async handleEvent(event) {
|
||||
event.preventDefault()
|
||||
handleEvent = async (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
// get the cords of the mouse
|
||||
const x = event.clientX
|
||||
const y = event.clientY
|
||||
// obtain cord of mouse
|
||||
const x = event.clientX
|
||||
const y = event.clientY
|
||||
|
||||
// get the component that was clicked
|
||||
const component = document.elementFromPoint(x, y)
|
||||
// get clicked component
|
||||
const component = document.elementFromPoint(x, y)
|
||||
|
||||
// check if is clicking inside a context menu or a children inside a context menu
|
||||
if (component.classList.contains("contextMenu") || component.closest(".contextMenu")) {
|
||||
return
|
||||
}
|
||||
// check if right-clicked inside a context menu
|
||||
if (
|
||||
component.classList.contains("contextMenu") ||
|
||||
component.closest(".contextMenu")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const items = await this.generateItems(component)
|
||||
// gen items
|
||||
const items = await this.generateItems(component)
|
||||
|
||||
if (!items) {
|
||||
this.console.warn("No context menu items found, aborting")
|
||||
return false
|
||||
}
|
||||
// if no items, abort
|
||||
if (!items || items.length === 0) {
|
||||
this.console.error("No context menu items found, aborting")
|
||||
return false
|
||||
}
|
||||
|
||||
this.show({
|
||||
registerOnClose: (cb) => { this.eventBus.on("close", cb) },
|
||||
unregisterOnClose: (cb) => { this.eventBus.off("close", cb) },
|
||||
cords: {
|
||||
x,
|
||||
y,
|
||||
},
|
||||
clickedComponent: component,
|
||||
items: items,
|
||||
ctx: {
|
||||
close: this.onClose,
|
||||
}
|
||||
})
|
||||
}
|
||||
// render menu
|
||||
this.show({
|
||||
cords: { x, y },
|
||||
clickedComponent: component,
|
||||
items: items,
|
||||
fireWhenClosing: (fn) => {
|
||||
this.fireWhenClosing = fn
|
||||
},
|
||||
ctx: {
|
||||
close: this.close,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
registerContext = async (element, context) => {
|
||||
this.contexts[element] = context
|
||||
}
|
||||
registerContext = (element, context) => {
|
||||
this.contexts[element] = context
|
||||
}
|
||||
|
||||
generateItems = async (element) => {
|
||||
let items = []
|
||||
generateItems = async (element) => {
|
||||
let contextNames = []
|
||||
let finalItems = []
|
||||
|
||||
// find the closest context with attribute (context-menu)
|
||||
// if not found, use default context
|
||||
const parentElement = element.closest("[context-menu]")
|
||||
// search parent element with context-menu attribute
|
||||
const parentElement = element.closest("[context-menu]")
|
||||
|
||||
let contexts = []
|
||||
// if parent element exists, get context names from attribute
|
||||
if (parentElement) {
|
||||
const contextAttr = parentElement.getAttribute("context-menu") || ""
|
||||
contextNames = contextAttr
|
||||
.split(",")
|
||||
.map((context) => context.trim())
|
||||
|
||||
if (parentElement) {
|
||||
contexts = parentElement.getAttribute("context-menu") ?? []
|
||||
// if context includes "ignore", no show context menu
|
||||
if (contextNames.includes("ignore")) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof contexts === "string") {
|
||||
contexts = contexts.split(",").map((context) => context.trim())
|
||||
}
|
||||
}
|
||||
// if context includes "no-default", no add default context
|
||||
if (!contextNames.includes("no-default")) {
|
||||
contextNames.push("default-context")
|
||||
} else {
|
||||
// remove "no-default" from context names
|
||||
contextNames = contextNames.filter(
|
||||
(context) => context !== "no-default",
|
||||
)
|
||||
}
|
||||
|
||||
// if context includes ignore, return null
|
||||
if (contexts.includes("ignore")) {
|
||||
return null
|
||||
}
|
||||
// process each context sequentially
|
||||
for (let i = 0; i < contextNames.length; i++) {
|
||||
const contextName = contextNames[i]
|
||||
|
||||
// check if context includes no-default, if not, push default context and remove no-default
|
||||
if (contexts.includes("no-default")) {
|
||||
contexts = contexts.filter((context) => context !== "no-default")
|
||||
} else {
|
||||
contexts.push("default-context")
|
||||
}
|
||||
// obtain contexted items
|
||||
const contextItems = await this.getContextItems(
|
||||
contextName,
|
||||
parentElement,
|
||||
element,
|
||||
)
|
||||
|
||||
for await (const [index, context] of contexts.entries()) {
|
||||
let contextObject = this.contexts[context]
|
||||
// if any contexted items exist, add them to the final items
|
||||
if (contextItems && contextItems.length > 0) {
|
||||
finalItems = finalItems.concat(contextItems)
|
||||
|
||||
if (!contextObject) {
|
||||
this.console.warn(`Context ${context} not found`)
|
||||
continue
|
||||
}
|
||||
// if is not the last context, add a separator
|
||||
if (i < contextNames.length - 1) {
|
||||
finalItems.push({
|
||||
type: "separator",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof contextObject === "function") {
|
||||
contextObject = await contextObject(items, parentElement, element, {
|
||||
close: this.onClose,
|
||||
})
|
||||
}
|
||||
// assign indices
|
||||
finalItems = finalItems.map((item, index) => {
|
||||
if (!item.index) {
|
||||
item.index = index
|
||||
}
|
||||
return item
|
||||
})
|
||||
|
||||
// push divider
|
||||
if (contexts.length > 0 && index !== contexts.length - 1) {
|
||||
items.push({
|
||||
type: "separator"
|
||||
})
|
||||
}
|
||||
}
|
||||
// sort items by index
|
||||
finalItems.sort((a, b) => a.index - b.index)
|
||||
|
||||
// fullfill each item with a correspondent index if missing declared
|
||||
items = items.map((item, index) => {
|
||||
if (!item.index) {
|
||||
item.index = index
|
||||
}
|
||||
// remove undefined items
|
||||
finalItems = finalItems.filter((item) => item !== undefined)
|
||||
|
||||
return item
|
||||
})
|
||||
return finalItems
|
||||
}
|
||||
|
||||
// short items (if has declared index)
|
||||
items = items.sort((a, b) => a.index - b.index)
|
||||
getContextItems = async (contextName, parentElement, element) => {
|
||||
const contextObject = this.contexts[contextName]
|
||||
|
||||
// remove undefined items
|
||||
items = items.filter((item) => item !== undefined)
|
||||
if (!contextObject) {
|
||||
this.console.warn(`Context ${contextName} not found`)
|
||||
return []
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
// if is a function, execute it to get the elements
|
||||
if (typeof contextObject === "function") {
|
||||
try {
|
||||
const newItems = []
|
||||
|
||||
show = async (payload) => {
|
||||
app.cores.window_mng.render(
|
||||
"context-menu-portal",
|
||||
React.createElement(ContextMenu, payload),
|
||||
{
|
||||
onClose: this.onClose,
|
||||
createOrUpdate: true,
|
||||
closeOnClickOutside: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
// call the function
|
||||
const result = await contextObject(
|
||||
newItems,
|
||||
parentElement,
|
||||
element,
|
||||
{ close: this.close },
|
||||
)
|
||||
|
||||
onClose = async (delay = 200) => {
|
||||
this.eventBus.emit("close", delay)
|
||||
return result || newItems
|
||||
} catch (error) {
|
||||
this.console.error(
|
||||
`Error processing context [${contextName}] >`,
|
||||
error,
|
||||
)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, delay)
|
||||
})
|
||||
}
|
||||
}
|
||||
// if it is an object (array), return it directly
|
||||
return Array.isArray(contextObject) ? contextObject : []
|
||||
}
|
||||
|
||||
show = async (props) => {
|
||||
app.cores.window_mng.render(
|
||||
"context-menu-portal",
|
||||
React.createElement(ContextMenu, props),
|
||||
{
|
||||
useFrame: false,
|
||||
createOrUpdate: true,
|
||||
closeOnClickOutside: true, // sets default click outside behavior
|
||||
onClose: this.onClose, // triggered when the menu is closing
|
||||
},
|
||||
)
|
||||
|
||||
this.isMenuOpen = true
|
||||
}
|
||||
|
||||
// triggered when the menu is closing
|
||||
onClose = async (delay = 200) => {
|
||||
if (typeof this.fireWhenClosing === "function") {
|
||||
await this.fireWhenClosing()
|
||||
}
|
||||
|
||||
if (delay > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, delay))
|
||||
}
|
||||
|
||||
this.isMenuOpen = false
|
||||
}
|
||||
|
||||
// close the menu
|
||||
close = async () => {
|
||||
app.cores.window_mng.close("context-menu-portal")
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ const variantToAlgorithm = {
|
||||
dark: theme.darkAlgorithm,
|
||||
}
|
||||
|
||||
const ClientPrefersDark = () => window.matchMedia("(prefers-color-scheme: dark)")
|
||||
const ClientPrefersDark = () =>
|
||||
window.matchMedia("(prefers-color-scheme: dark)")
|
||||
|
||||
function variantKeyToColor(key) {
|
||||
if (key == "auto") {
|
||||
@ -25,7 +26,6 @@ function variantKeyToColor(key) {
|
||||
return key
|
||||
}
|
||||
|
||||
|
||||
export class ThemeProvider extends React.Component {
|
||||
state = {
|
||||
useAlgorigthm: variantKeyToColor(app.cores.style.currentVariantKey),
|
||||
@ -37,7 +37,7 @@ export class ThemeProvider extends React.Component {
|
||||
|
||||
this.setState({
|
||||
useAlgorigthm: variantKeyToColor(app.cores.style.currentVariantKey),
|
||||
useCompactMode: update["compact-mode"]
|
||||
useCompactMode: update["compact-mode"],
|
||||
})
|
||||
}
|
||||
|
||||
@ -58,19 +58,19 @@ export class ThemeProvider extends React.Component {
|
||||
themeAlgorithms.push(theme.compactAlgorithm)
|
||||
}
|
||||
|
||||
return <ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
...app.cores.style.getVar(),
|
||||
},
|
||||
algorithm: themeAlgorithms,
|
||||
}}
|
||||
componentSize={
|
||||
app.isMobile ? "large" : "middle"
|
||||
}
|
||||
>
|
||||
{this.props.children}
|
||||
</ConfigProvider>
|
||||
return (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
...app.cores.style.getVar(),
|
||||
},
|
||||
algorithm: themeAlgorithms,
|
||||
}}
|
||||
componentSize={app.isMobile ? "large" : "middle"}
|
||||
>
|
||||
{this.props.children}
|
||||
</ConfigProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,9 +83,12 @@ export default class StyleCore extends Core {
|
||||
static defaultVariantKey = "auto"
|
||||
|
||||
static get rootVariables() {
|
||||
let attributes = document.documentElement.getAttribute("style").trim().split(";")
|
||||
let attributes = document.documentElement
|
||||
.getAttribute("style")
|
||||
.trim()
|
||||
.split(";")
|
||||
|
||||
attributes = attributes.slice(0, (attributes.length - 1))
|
||||
attributes = attributes.slice(0, attributes.length - 1)
|
||||
|
||||
attributes = attributes.map((variable) => {
|
||||
let [key, value] = variable.split(":")
|
||||
@ -107,7 +110,7 @@ export default class StyleCore extends Core {
|
||||
|
||||
isOnTemporalVariant = false
|
||||
|
||||
// modifications
|
||||
// modifications
|
||||
static get storagedModifications() {
|
||||
return store.get(StyleCore.modificationStorageKey) ?? {}
|
||||
}
|
||||
@ -149,12 +152,14 @@ export default class StyleCore extends Core {
|
||||
}
|
||||
|
||||
// apply variation
|
||||
this.applyVariant(StyleCore.storagedVariantKey ?? StyleCore.defaultVariantKey)
|
||||
this.applyVariant(
|
||||
StyleCore.storagedVariantKey ?? StyleCore.defaultVariantKey,
|
||||
)
|
||||
|
||||
// if mobile set fontScale to 1
|
||||
if (app.isMobile) {
|
||||
this.applyStyles({
|
||||
fontScale: 1
|
||||
fontScale: 1,
|
||||
})
|
||||
}
|
||||
|
||||
@ -181,7 +186,10 @@ export default class StyleCore extends Core {
|
||||
}
|
||||
}
|
||||
|
||||
return StyleCore.storagedModifications[key] || this.public.theme.defaultVars[key]
|
||||
return (
|
||||
StyleCore.storagedModifications[key] ||
|
||||
this.public.theme.defaultVars[key]
|
||||
)
|
||||
}
|
||||
|
||||
getDefaultVar(key) {
|
||||
@ -201,11 +209,14 @@ export default class StyleCore extends Core {
|
||||
this.public.mutation = {
|
||||
...this.public.theme.defaultVars,
|
||||
...this.public.mutation,
|
||||
...update
|
||||
...update,
|
||||
}
|
||||
|
||||
Object.keys(this.public.mutation).forEach(key => {
|
||||
document.documentElement.style.setProperty(`--${key}`, this.public.mutation[key])
|
||||
Object.keys(this.public.mutation).forEach((key) => {
|
||||
document.documentElement.style.setProperty(
|
||||
`--${key}`,
|
||||
this.public.mutation[key],
|
||||
)
|
||||
})
|
||||
|
||||
app.eventBus.emit("style.update", {
|
||||
@ -213,7 +224,10 @@ export default class StyleCore extends Core {
|
||||
})
|
||||
}
|
||||
|
||||
applyVariant = (variantKey = (this.public.theme.defaultVariant ?? "light"), save = true) => {
|
||||
applyVariant = (
|
||||
variantKey = this.public.theme.defaultVariant ?? "light",
|
||||
save = true,
|
||||
) => {
|
||||
if (save) {
|
||||
StyleCore.storagedVariantKey = variantKey
|
||||
this.public.currentVariantKey = variantKey
|
||||
@ -253,8 +267,11 @@ export default class StyleCore extends Core {
|
||||
resetToDefault() {
|
||||
store.remove(StyleCore.modificationStorageKey)
|
||||
|
||||
app.cores.settings.set("colorPrimary", this.public.theme.defaultVars.colorPrimary)
|
||||
app.cores.settings.set(
|
||||
"colorPrimary",
|
||||
this.public.theme.defaultVars.colorPrimary,
|
||||
)
|
||||
|
||||
this.onInitialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,15 +158,15 @@ export default class WindowManager extends Core {
|
||||
})
|
||||
|
||||
if (!win || !element) {
|
||||
this.console.error(`Window [${id}] not found`)
|
||||
this.console.error(`[${id}] Window not found`)
|
||||
return false
|
||||
}
|
||||
|
||||
this.console.debug(`Closing window ${id}`, win, element)
|
||||
this.console.debug(`[${id}] Closing window`, win, element)
|
||||
|
||||
// if onClose callback is defined, call it
|
||||
if (typeof win.onClose === "function") {
|
||||
this.console.debug(`Trigging close callback for window ${id}`)
|
||||
this.console.debug(`[${id}] Trigging on closing callback`)
|
||||
await win.onClose()
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ const ProfileData = (props) => {
|
||||
async function handleChange(key, value) {
|
||||
setLoading(true)
|
||||
|
||||
const result = await Streaming.createOrUpdateStream({
|
||||
const result = await Streaming.createOrUpdateProfile({
|
||||
[key]: value,
|
||||
_id: profile._id,
|
||||
}).catch((error) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
import { FastAverageColor } from "fast-average-color"
|
||||
import { Icons } from "@components/Icons"
|
||||
|
||||
import UserPreview from "@components/UserPreview"
|
||||
|
||||
@ -69,17 +70,26 @@ const LivestreamItem = (props) => {
|
||||
|
||||
<div className="livestream_info">
|
||||
<div className="livestream_titles">
|
||||
<antd.Tag className="livestream_category">
|
||||
{livestream.info?.category}
|
||||
</antd.Tag>
|
||||
|
||||
<div className="livestream_title">
|
||||
<h1>{livestream.info?.title}</h1>
|
||||
</div>
|
||||
|
||||
<div className="livestream_description">
|
||||
<h2>
|
||||
<span className="livestream_description-text">
|
||||
{livestream.info?.description ?? "No description"}
|
||||
</h2>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="livestream_views">
|
||||
<Icons.FiEye />
|
||||
<h4>{livestream.info?.viewers ?? 0}</h4>
|
||||
</div>
|
||||
|
||||
<UserPreview user={livestream.user} small />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,120 +1,170 @@
|
||||
@item_border_radius: 10px;
|
||||
|
||||
.livestream_list {
|
||||
display: grid;
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-column-gap: 15px;
|
||||
grid-row-gap: 15px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-column-gap: 15px;
|
||||
grid-row-gap: 15px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@media not screen and (max-width: 1900px) {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
@media not screen and (max-width: 1900px) {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@media not screen and (max-width: 2300px) {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
@media not screen and (max-width: 2300px) {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@media not screen and (max-width: 2600px) {
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
}
|
||||
@media not screen and (max-width: 2600px) {
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@media not screen and (max-width: 2900px) {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
@media not screen and (max-width: 2900px) {
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
&.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.livestream_item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.livestream_item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
|
||||
padding: 10px;
|
||||
transition: all 150ms ease-in-out;
|
||||
padding: 10px;
|
||||
transition: all 150ms ease-in-out;
|
||||
|
||||
background-color: var(--background-color-primary-2);
|
||||
background-color: var(--background-color-primary-2);
|
||||
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: @item_border_radius;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: @item_border_radius;
|
||||
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-color-accent);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--background-color-accent);
|
||||
}
|
||||
|
||||
&.white_background {
|
||||
&.white_background {
|
||||
h1,
|
||||
h2,
|
||||
span {
|
||||
color: var(--text-color-black) !important;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
span {
|
||||
color: var(--text-color-black) !important;
|
||||
}
|
||||
}
|
||||
.livestream_thumbnail {
|
||||
width: 100%;
|
||||
|
||||
.livestream_thumbnail {
|
||||
width: 100%;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: @item_border_radius;
|
||||
|
||||
border-radius: @item_border_radius;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.livestream_info {
|
||||
position: relative;
|
||||
|
||||
.livestream_info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
|
||||
gap: 10px;
|
||||
h1,
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
.livestream_titles {
|
||||
position: relative;
|
||||
|
||||
.livestream_titles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 7px;
|
||||
padding: 10px 0;
|
||||
|
||||
color: var(--text-color);
|
||||
color: var(--text-color);
|
||||
|
||||
.livestream_title {
|
||||
margin-top: 10px;
|
||||
font-size: 1rem;
|
||||
height: fit-content;
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
}
|
||||
.livestream_title {
|
||||
max-height: 200px;
|
||||
|
||||
.livestream_description {
|
||||
font-size: 0.6rem;
|
||||
font-weight: 400;
|
||||
height: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
overflow: hidden;
|
||||
white-space: wrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.livestream_description {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
max-height: 200px;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: wrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.livestream_description-text {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.livestream_category {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
margin-top: 13px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.livestream_views {
|
||||
position: absolute;
|
||||
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 6px;
|
||||
|
||||
padding: 5px 10px;
|
||||
|
||||
background-color: var(--background-color-primary);
|
||||
border-radius: 12px;
|
||||
font-family: "DM Mono", monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
import React from "react"
|
||||
import config from "@config"
|
||||
|
||||
class Splash extends React.Component {
|
||||
state = {
|
||||
visible: true
|
||||
}
|
||||
|
||||
onUnmount = async () => {
|
||||
this.setState({
|
||||
visible: false
|
||||
})
|
||||
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div
|
||||
className={this.state.visible ? "app_splash_wrapper" : "app_splash_wrapper fade-away"}
|
||||
>
|
||||
<div className="content">
|
||||
<img
|
||||
src={config.logo.alt}
|
||||
/>
|
||||
|
||||
<div className="loader_wrapper">
|
||||
<div
|
||||
className="loader"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default Splash
|
@ -686,3 +686,27 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-empty-image {
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
.ant-empty-description {
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
.ant-mentions-dropdown {
|
||||
background-color: var(--background-color-primary);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.ant-mentions-dropdown-menu-item {
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
gap: 10px;
|
||||
padding: 5px !important;
|
||||
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
@ -1,8 +1,48 @@
|
||||
export default (text) => {
|
||||
if (!navigator.clipboard?.writeText) {
|
||||
return app.message.error("Clipboard API not supported")
|
||||
}
|
||||
/**
|
||||
* Copies content to clipboard, supporting both text and file data
|
||||
* @param {string|File|Blob} content - The content to copy (text string or file/blob data)
|
||||
* @param {Object} options - Optional configuration
|
||||
* @param {string} options.successMessage - Custom success message
|
||||
* @returns {Promise<boolean>} - Promise resolving to success state
|
||||
*/
|
||||
export default async (content, options = {}) => {
|
||||
const { successMessage = "Copied to clipboard" } = options
|
||||
|
||||
navigator.clipboard.writeText(text)
|
||||
app.message.success("Copied to clipboard")
|
||||
}
|
||||
try {
|
||||
if (!navigator.clipboard) {
|
||||
app.message.error("Clipboard API not supported")
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof content === "string") {
|
||||
await navigator.clipboard.writeText(content)
|
||||
|
||||
app.message.success(successMessage)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (content instanceof File || content instanceof Blob) {
|
||||
const clipboardItem = new ClipboardItem({
|
||||
[content.type]: content,
|
||||
})
|
||||
|
||||
if (!navigator.clipboard.write) {
|
||||
app.message.error("File copying not supported in this browser")
|
||||
return false
|
||||
}
|
||||
|
||||
await navigator.clipboard.write([clipboardItem])
|
||||
app.message.success(successMessage)
|
||||
return true
|
||||
}
|
||||
|
||||
app.message.error("Unsupported content type")
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error("Clipboard operation failed:", error)
|
||||
app.message.error("Failed to copy to clipboard")
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
33
packages/app/src/utils/pasteFromClipboard/index.js
Normal file
@ -0,0 +1,33 @@
|
||||
export async function pasteFromClipboard(element) {
|
||||
if (!navigator.clipboard) {
|
||||
throw new Error(
|
||||
"Clipboard API not available in this browser or context",
|
||||
)
|
||||
}
|
||||
|
||||
if (!element || !(element instanceof HTMLElement)) {
|
||||
console.error("Invalid element provided to pasteFromClipboard")
|
||||
return Promise.reject(new Error("Invalid element provided"))
|
||||
}
|
||||
|
||||
let data = await navigator.clipboard.read()
|
||||
|
||||
data = data[0]
|
||||
|
||||
data = await data.getType(data.types[0])
|
||||
|
||||
const event = new ClipboardEvent("paste", {
|
||||
clipboardData: new DataTransfer(),
|
||||
})
|
||||
|
||||
element.focus()
|
||||
element.dispatchEvent(event)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export function isClipboardSupported() {
|
||||
return !!navigator.clipboard
|
||||
}
|
||||
|
||||
export default pasteFromClipboard
|
@ -1,38 +0,0 @@
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
const exec = require("child_process").execSync
|
||||
|
||||
const sharedRootPath = path.resolve(process.cwd(), "shared")
|
||||
|
||||
const rootPath = process.cwd()
|
||||
const packagesPath = path.resolve(rootPath, "packages")
|
||||
|
||||
const getPackages = require("./utils/getPackages")
|
||||
|
||||
async function main() {
|
||||
const packages = await getPackages()
|
||||
|
||||
// copy shared dir to each root package path
|
||||
for await (const packageName of packages) {
|
||||
const packagePath = path.resolve(packagesPath, packageName)
|
||||
const sharedPath = path.resolve(packagePath, "src", "_shared")
|
||||
|
||||
if (fs.existsSync(sharedPath)) {
|
||||
// remove old shared folder
|
||||
fs.rmdirSync(sharedPath, { recursive: true })
|
||||
}
|
||||
|
||||
// copy entire shared folder
|
||||
// shared/* => /_shared/*
|
||||
fs.mkdirSync(sharedPath, { recursive: true })
|
||||
|
||||
await exec(`cp -r ${sharedRootPath}/* ${sharedPath}`)
|
||||
}
|
||||
|
||||
console.log("📦 Shared classes copied to each package.")
|
||||
|
||||
// run docker build
|
||||
await exec("sudo docker compose build --no-cache")
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
@ -1,61 +0,0 @@
|
||||
require("dotenv").config()
|
||||
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
const child_process = require("child_process")
|
||||
|
||||
const sharedRootPath = path.resolve(process.cwd(), "shared")
|
||||
|
||||
const rootPath = process.cwd()
|
||||
const packagesPath = path.resolve(rootPath, "packages")
|
||||
|
||||
const getPackages = require("./utils/getPackages")
|
||||
|
||||
async function main() {
|
||||
const packages = await getPackages({
|
||||
ignore: ["shared", "app", "wrapper", "comty.js"]
|
||||
})
|
||||
|
||||
for await (const packageName of packages) {
|
||||
const packagePath = path.resolve(packagesPath, packageName)
|
||||
|
||||
// copy shared
|
||||
const sharedPath = path.resolve(packagePath, "src", "_shared")
|
||||
|
||||
if (fs.existsSync(sharedPath)) {
|
||||
// remove old shared folder
|
||||
fs.rmdirSync(sharedPath, { recursive: true })
|
||||
}
|
||||
|
||||
// copy entire shared folder
|
||||
fs.mkdirSync(sharedPath, { recursive: true })
|
||||
|
||||
await child_process.execSync(`cp -r ${sharedRootPath}/* ${sharedPath} && echo Shared lib copied`, {
|
||||
cwd: packagePath,
|
||||
stdio: "inherit"
|
||||
})
|
||||
|
||||
console.log(`Building [${packagePath}]`)
|
||||
|
||||
// run yarn build
|
||||
await child_process.execSync("yarn build", {
|
||||
cwd: packagePath,
|
||||
stdio: "inherit"
|
||||
})
|
||||
|
||||
if (process.env.INFISICAL_TOKEN) {
|
||||
// write env
|
||||
const envPath = path.resolve(packagePath, ".env")
|
||||
|
||||
if (fs.existsSync(envPath)) {
|
||||
fs.unlinkSync(envPath)
|
||||
}
|
||||
|
||||
const envData = `INFISICAL_TOKEN="${process.env.INFISICAL_TOKEN}"`
|
||||
|
||||
await fs.writeFileSync(envPath, envData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
@ -1,196 +0,0 @@
|
||||
const fs = require("node:fs")
|
||||
const path = require("node:path")
|
||||
const child_process = require("node:child_process")
|
||||
|
||||
const rootPath = process.cwd()
|
||||
|
||||
const sharedRootPath = path.resolve(rootPath, "shared")
|
||||
const packagesPath = path.resolve(rootPath, "packages")
|
||||
|
||||
const getPackages = require("./utils/getPackages")
|
||||
|
||||
const pkgjson = require("../package.json")
|
||||
|
||||
// vars
|
||||
const appPath = path.resolve(rootPath, pkgjson._web_app_path)
|
||||
const comtyjsPath = path.resolve(rootPath, "comty.js")
|
||||
const vesselPath = path.resolve(rootPath, "vessel")
|
||||
const linebridePath = path.resolve(rootPath, "linebridge")
|
||||
|
||||
async function linkSharedResources(pkgJSON, packagePath) {
|
||||
if (typeof pkgJSON !== "object") {
|
||||
throw new Error("Package must be an object")
|
||||
}
|
||||
|
||||
const { shared } = pkgJSON
|
||||
|
||||
if (!shared) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof shared === "string") {
|
||||
const finalLinkPath = path.resolve(packagePath, shared)
|
||||
if (fs.existsSync(finalLinkPath)) {
|
||||
console.warn(`⚠️ Resource [${shared}] link already exists in [${finalLinkPath}]`)
|
||||
return
|
||||
}
|
||||
|
||||
// link entire folder
|
||||
fs.symlinkSync(sharedRootPath, finalLinkPath, "dir")
|
||||
} else {
|
||||
for (const [resource, linkPath] of Object.entries(shared)) {
|
||||
const originClassPath = path.resolve(sharedRootPath, resource)
|
||||
const finalLinkPath = path.resolve(packagePath, linkPath)
|
||||
|
||||
if (!fs.existsSync(originClassPath)) {
|
||||
throw new Error(`Resource [${resource}] does not exist`)
|
||||
}
|
||||
|
||||
if (fs.existsSync(finalLinkPath)) {
|
||||
console.warn(`⚠️ Resource [${resource}] link already exists in [${finalLinkPath}]`)
|
||||
continue
|
||||
} else {
|
||||
fs.mkdirSync(path.resolve(finalLinkPath, ".."), { recursive: true })
|
||||
}
|
||||
|
||||
try {
|
||||
fs.symlinkSync(originClassPath, finalLinkPath, "dir")
|
||||
console.log(`🔗 Linked resouce [${resource}] to [${finalLinkPath}]`)
|
||||
} catch (error) {
|
||||
if (error.code && error.code == 'EEXIST') {
|
||||
fs.unlinkSync(finalLinkPath)
|
||||
fs.symlinkSync(originClassPath, finalLinkPath, "dir")
|
||||
console.log(`🔗 Linked resouce [${resource}] to [${finalLinkPath}]`)
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function linkInternalSubmodules(packages) {
|
||||
//* APP RUNTIME LINKING
|
||||
console.log(`Linking Vessel to app...`)
|
||||
|
||||
await child_process.execSync("yarn link", {
|
||||
cwd: vesselPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
await child_process.execSync(`yarn link "vessel"`, {
|
||||
cwd: appPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
//* COMTY.JS LINKING
|
||||
console.log(`Linking comty.js to app...`)
|
||||
|
||||
await child_process.execSync(`yarn link`, {
|
||||
cwd: comtyjsPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
await child_process.execSync(`yarn link "comty.js"`, {
|
||||
cwd: appPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
//* LINEBRIDE LINKING
|
||||
console.log(`Linking Linebride to servers...`)
|
||||
|
||||
await child_process.execSync(`yarn link`, {
|
||||
cwd: linebridePath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
for await (const packageName of packages) {
|
||||
const packagePath = path.resolve(packagesPath, packageName)
|
||||
|
||||
const packageJsonPath = path.resolve(packagePath, "package.json")
|
||||
|
||||
if (!fs.existsSync(packageJsonPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
await child_process.execSync(`yarn link "linebridge"`, {
|
||||
cwd: packagePath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
console.log(`Linking Linebride to package [${packageName}]...`)
|
||||
}
|
||||
|
||||
console.log(`✅ All submodules linked!`)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.time("✅ post-install tooks:")
|
||||
|
||||
// read dir with absolute paths
|
||||
let packages = await getPackages()
|
||||
|
||||
// link shared resources
|
||||
for (const packageName of packages) {
|
||||
const packagePath = path.resolve(packagesPath, packageName)
|
||||
|
||||
const packageJsonPath = path.resolve(packagePath, "package.json")
|
||||
|
||||
if (!fs.existsSync(packageJsonPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const packageJson = require(packageJsonPath)
|
||||
|
||||
if (packageJson.shared) {
|
||||
console.log(`📦 Package [${packageName}] has declared shared resources.`)
|
||||
|
||||
await linkSharedResources(packageJson, packagePath)
|
||||
}
|
||||
}
|
||||
|
||||
// install dependencies for modules
|
||||
console.log("Installing app dependencies...")
|
||||
await child_process.execSync("npm install --force", {
|
||||
cwd: appPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
console.log("Installing vessel dependencies...")
|
||||
await child_process.execSync("npm install --force", {
|
||||
cwd: vesselPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
console.log("Installing comty.js dependencies...")
|
||||
await child_process.execSync("npm install --force", {
|
||||
cwd: comtyjsPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
console.log("Installing linebridge dependencies...")
|
||||
await child_process.execSync("npm install --force", {
|
||||
cwd: linebridePath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
|
||||
// link internal submodules
|
||||
await linkInternalSubmodules(packages)
|
||||
|
||||
// fixes for arm architecture
|
||||
if (process.arch == "arm64") {
|
||||
// rebuild tfjs
|
||||
console.log("Rebuilding TFJS...")
|
||||
|
||||
await child_process.execSync("npm rebuild @tensorflow/tfjs-node --build-from-source", {
|
||||
cwd: rootPath,
|
||||
stdio: "inherit",
|
||||
})
|
||||
}
|
||||
|
||||
console.timeEnd("✅ post-install tooks:")
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
import axios from "axios"
|
||||
import sevenzip from "7zip-min"
|
||||
import formdata from "form-data"
|
||||
|
||||
const marketplaceAPIOrigin = "https://indev.comty.app/api/extensions"
|
||||
const token = process.argv[2]
|
||||
|
||||
const excludedFiles = [
|
||||
"/.git",
|
||||
"/.tmp",
|
||||
"/bundle.7z",
|
||||
"/node_modules",
|
||||
"/package-lock.json",
|
||||
]
|
||||
|
||||
const rootPath = process.cwd()
|
||||
const tmpPath = path.join(rootPath, ".tmp")
|
||||
const buildPath = path.join(tmpPath, "build")
|
||||
const bundlePath = path.join(tmpPath, "bundle.7z")
|
||||
|
||||
async function copySources(origin, to) {
|
||||
const files = fs.readdirSync(origin)
|
||||
|
||||
if (!fs.existsSync(to)) {
|
||||
await fs.promises.mkdir(to, { recursive: true })
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(origin, file)
|
||||
|
||||
// run a rexeg to check if the filePath is excluded
|
||||
const isExcluded = excludedFiles.some((excludedPath) => {
|
||||
return filePath.match(excludedPath)
|
||||
})
|
||||
|
||||
if (isExcluded) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (fs.lstatSync(filePath).isDirectory()) {
|
||||
await copySources(filePath, path.join(to, file))
|
||||
} else {
|
||||
await fs.promises.copyFile(filePath, path.join(to, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createBundle(origin, desitinationFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
sevenzip.pack(origin, desitinationFile, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!token) {
|
||||
console.error("🛑 You need to pass a token as argument")
|
||||
return
|
||||
}
|
||||
|
||||
// create a .tmp folder
|
||||
if (fs.existsSync(tmpPath)) {
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
try {
|
||||
// try to read package.json
|
||||
if (!fs.existsSync(path.resolve(rootPath, "package.json"))) {
|
||||
console.error("🛑 package.json not found")
|
||||
return
|
||||
}
|
||||
|
||||
const packageJSON = require(path.resolve(rootPath, "package.json"))
|
||||
|
||||
// check if package.json has a main file
|
||||
if (!packageJSON.main) {
|
||||
console.error("🛑 package.json does not have a main file")
|
||||
return
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.resolve(rootPath, packageJSON.main))) {
|
||||
console.error("🛑 main file not found")
|
||||
return
|
||||
}
|
||||
|
||||
console.log(packageJSON)
|
||||
|
||||
console.log("📦 Creating bundle...")
|
||||
|
||||
await copySources(rootPath, buildPath)
|
||||
await createBundle(`${buildPath}/*`, bundlePath)
|
||||
|
||||
console.log("📦✅ Bundle created successfully")
|
||||
|
||||
console.log("🚚 Publishing bundle...")
|
||||
|
||||
const formData = new formdata()
|
||||
|
||||
formData.append("file", fs.createReadStream(bundlePath))
|
||||
|
||||
const response = await axios({
|
||||
method: "PUT",
|
||||
url: `${marketplaceAPIOrigin}/publish`,
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
pkg: JSON.stringify(packageJSON),
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
data: formData,
|
||||
}).catch((error) => {
|
||||
console.error("🛑 Error while publishing bundle \n\t", error.response?.data ?? error)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (response) {
|
||||
console.log("🚚✅ Bundle published successfully! \n", response.data)
|
||||
}
|
||||
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
} catch (error) {
|
||||
console.error("🛑 Error while publishing bundle \n\t", error)
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
2
vessel
@ -1 +1 @@
|
||||
Subproject commit bdbd92a73cd8305e36fcf0b8d7ea0ceba8a1ffcc
|
||||
Subproject commit 829e1bce7bc7c1b613747eedfbcc21329c9c4442
|