Backing up
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" ar-theme="th-dark-1">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
@@ -8,6 +8,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="root" class="vh-100"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
30377
package-lock.json
generated
30377
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@@ -7,21 +7,12 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"start": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"generate": "plop",
|
||||
"atom": "plop atom",
|
||||
"molecule": "plop molecule",
|
||||
"component": "plop component",
|
||||
"page": "plop page",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint .",
|
||||
"type-check": "tsc"
|
||||
"dev": "vite --config vite-run.config.ts",
|
||||
"start": "serve -s build",
|
||||
"build": "./build-tools/build.sh",
|
||||
"build:sm": "./build-tools/build.sh --dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"peerDependencies": {
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
@@ -29,32 +20,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.1",
|
||||
"react-router-dom": "^6.13.0",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@armco/types": "^0.0.6",
|
||||
"@testing-library/dom": "^9.2.0",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.2.5",
|
||||
"@types/moment": "^2.13.0",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jsdom": "^21.1.0",
|
||||
"plop": "^3.1.2",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-config-nick": "^1.0.2",
|
||||
"sass": "^1.63.4",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.0.0",
|
||||
"vitest": "^0.30.1"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
||||
1
packages/collaboration/src/index.js
Normal file
1
packages/collaboration/src/index.js
Normal file
@@ -0,0 +1 @@
|
||||
"use strict";
|
||||
@@ -7,8 +7,8 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.0"
|
||||
|
||||
1
packages/core/src/index.js
Normal file
1
packages/core/src/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./utils";
|
||||
21
packages/core/src/utils/helper.js
Normal file
21
packages/core/src/utils/helper.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export function debounce(func, wait, immediate) {
|
||||
if (immediate === void 0) { immediate = false; }
|
||||
var timeout;
|
||||
return function () {
|
||||
var args = [];
|
||||
for (var _i = 0; _i < arguments.length; _i++) {
|
||||
args[_i] = arguments[_i];
|
||||
}
|
||||
var later = function () {
|
||||
timeout = undefined;
|
||||
if (!immediate) {
|
||||
func.apply(void 0, args);
|
||||
}
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
if (immediate && !timeout) {
|
||||
func.apply(void 0, args);
|
||||
}
|
||||
timeout = setTimeout(later, wait || 1000);
|
||||
};
|
||||
}
|
||||
1
packages/core/src/utils/index.js
Normal file
1
packages/core/src/utils/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./helper";
|
||||
3
packages/event-manager/.gitignore
vendored
Normal file
3
packages/event-manager/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
build
|
||||
handlers
|
||||
36
packages/event-manager/build-tools/build.sh
Executable file
36
packages/event-manager/build-tools/build.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get the directory of the current script
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Default values
|
||||
DEV_FLAG=""
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"
|
||||
do
|
||||
case $arg in
|
||||
--dev)
|
||||
DEV_FLAG="--dev"
|
||||
shift # Remove --dev from processing
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
|
||||
echo "[BUILD:SH] Removing build if exists"
|
||||
rm -rf build
|
||||
echo "[BUILD:SH] Checking TS Types"
|
||||
npx tsc
|
||||
echo "[BUILD:SH] Initiating build..."
|
||||
# Conditionally use vite-dev.config.ts if --dev flag is present
|
||||
if [ "$DEV_FLAG" == "--dev" ]; then
|
||||
vite build --config vite-dev.config.ts
|
||||
else
|
||||
vite build
|
||||
fi
|
||||
|
||||
echo "[BUILD:SH] Running post processor scripts..."
|
||||
# Run Post processors: Update style imports in .js files, create component modules
|
||||
node "$SCRIPT_DIR/post-processor.js" build/cjs $DEV_FLAG
|
||||
node "$SCRIPT_DIR/post-processor.js" build/es $DEV_FLAG
|
||||
31
packages/event-manager/build-tools/generate-module.js
Normal file
31
packages/event-manager/build-tools/generate-module.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { promises as fs } from "fs"
|
||||
import { dirname, resolve } from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
async function generateModule(fileName, isDev) {
|
||||
if (fileName === "handlers.js") {
|
||||
const dir = fileName.slice(0, -3)
|
||||
const name = `@sampadak/event-manager/${dir}`
|
||||
const packageJsonContent = {
|
||||
name,
|
||||
main: `../${isDev ? "build/" : ""}cjs/${dir}.js`,
|
||||
module: `../${isDev ? "build/" : ""}es/${dir}.js`,
|
||||
types: `../${isDev ? "build/" : ""}types/${dir}.d.ts`,
|
||||
}
|
||||
const dirPath = resolve(__dirname, `../${isDev ? "" : "build/"}${dir}`)
|
||||
try {
|
||||
await fs.mkdir(dirPath, { recursive: true })
|
||||
await fs.writeFile(
|
||||
resolve(dirPath, "package.json"),
|
||||
JSON.stringify(packageJsonContent, null, 2),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Error processing directory ${dirPath}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default generateModule
|
||||
24
packages/event-manager/build-tools/post-processor.js
Normal file
24
packages/event-manager/build-tools/post-processor.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { readdir } from "fs/promises"
|
||||
import generateModule from "./generate-module.js"
|
||||
|
||||
async function postProcessor(dir, isDev) {
|
||||
try {
|
||||
const files = await readdir(dir)
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
await generateModule(file, isDev)
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Error processing directory ${dir}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
const targetDir = process.argv[2]
|
||||
|
||||
if (targetDir) {
|
||||
postProcessor(targetDir, process.argv.includes("--dev"))
|
||||
} else {
|
||||
console.error("Please provide the build directory to run post processor on.")
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"name": "@sampadak/event-manager",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.tsx",
|
||||
"type": "module",
|
||||
"types": "./build/types/index.d.ts",
|
||||
"main": "./build/cjs/index.js",
|
||||
"module": "./build/es/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
"dev": "vite --config vite-run.config.ts",
|
||||
"start": "serve -s build",
|
||||
"build": "./build-tools/build.sh",
|
||||
"build:sm": "./build-tools/build.sh --dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.0"
|
||||
|
||||
@@ -1,30 +1,118 @@
|
||||
import { MutableRefObject } from "react"
|
||||
import { ChunkInfo, FlexContentType, FunctionType, TextInfo } from "@armco/types"
|
||||
import { calculateOffset } from "@sampadak/selection-manager"
|
||||
import {
|
||||
Dispatch,
|
||||
FormEvent,
|
||||
KeyboardEvent,
|
||||
MutableRefObject,
|
||||
RefObject,
|
||||
SetStateAction,
|
||||
} from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import {
|
||||
ArEditorEvents,
|
||||
ChunkInfo,
|
||||
EventHandler,
|
||||
EventHandlerCollection,
|
||||
EventHandlers,
|
||||
FlexContentState,
|
||||
FlexContentType,
|
||||
FunctionType,
|
||||
TextInfo,
|
||||
} from "@armco/types"
|
||||
import {
|
||||
calculateOffset,
|
||||
getNormalizedCursorPosition,
|
||||
} from "@sampadak/selection-manager"
|
||||
import { applyTool } from "@sampadak/formatter"
|
||||
|
||||
const DEFAULT_PREVENTERS = ["Enter", "Backspace", "Delete"]
|
||||
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
const isAndroid = /Android/.test(navigator.userAgent)
|
||||
|
||||
export const shortcuts: { [key: string]: ArEditorEvents } = {
|
||||
b: ArEditorEvents.BOLD, // Bold
|
||||
i: ArEditorEvents.ITALIC, // Italic
|
||||
u: ArEditorEvents.UNDERLINE, // Underline
|
||||
s: ArEditorEvents.STRIKETHROUGH, // Strikethrough
|
||||
z: ArEditorEvents.SUBSCRIPT, // Subscript
|
||||
y: ArEditorEvents.SUPERSCRIPT, // Superscript
|
||||
}
|
||||
|
||||
export const onKeyDown = (
|
||||
e: KeyboardEvent<HTMLDivElement>,
|
||||
state: FlexContentState,
|
||||
activeChunkRef: RefObject<ChunkInfo>,
|
||||
) => {
|
||||
if (DEFAULT_PREVENTERS.includes(e.key)) e.preventDefault()
|
||||
if (e.key === "Enter") {
|
||||
return (
|
||||
eventHandlers[ArEditorEvents.ENTER] as EventHandlerCollection
|
||||
).enterHandler(state, activeChunkRef)
|
||||
} else if (e.key === "Backspace") {
|
||||
return (
|
||||
eventHandlers[ArEditorEvents.BACKSPACE] as EventHandlerCollection
|
||||
).backspaceHandler(state, activeChunkRef)
|
||||
} else if (e.key === "Delete") {
|
||||
return (
|
||||
eventHandlers[ArEditorEvents.DELETE] as EventHandlerCollection
|
||||
).deleteHandler(state, activeChunkRef)
|
||||
} else if ((e.ctrlKey || e.metaKey) && shortcuts[e.key.toUpperCase()]) {
|
||||
return (eventHandlers[ArEditorEvents.FORMAT] as EventHandler)(state)
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
// Input event is handled in before input, since by the time input is called, some of the required pre-processing is lost
|
||||
// hence cursorPosition is outdated
|
||||
// and should be incremented by length of added text
|
||||
export const handleInput = (
|
||||
input: string,
|
||||
state: FlexContentState,
|
||||
activeChunkRef: RefObject<ChunkInfo>,
|
||||
) => {
|
||||
const { registry, cursorPosition } = state
|
||||
const selection = window.getSelection()
|
||||
if (selection) {
|
||||
const range = selection.getRangeAt(0)
|
||||
let anchorText = (selection.anchorNode as HTMLElement).textContent
|
||||
anchorText =
|
||||
anchorText?.slice(0, cursorPosition) +
|
||||
input +
|
||||
anchorText?.slice(cursorPosition)
|
||||
state.cursorPosition = (cursorPosition || 0) + 1
|
||||
const startContainer = range.startContainer
|
||||
|
||||
if (registry && activeChunkRef.current) {
|
||||
const textConfig = registry[activeChunkRef.current.parent]
|
||||
textConfig.text = anchorText // TODO: Fix this value, might only capture chunk's text
|
||||
if (
|
||||
startContainer?.parentNode &&
|
||||
!(startContainer.parentNode as HTMLElement).classList.contains(
|
||||
"ar-FlexContent",
|
||||
)
|
||||
) {
|
||||
textConfig.startContainer = startContainer
|
||||
}
|
||||
const activeText = registry[activeChunkRef.current.parent]
|
||||
const activeChunk = activeText?.chunks[activeChunkRef.current.id]
|
||||
activeChunk.text = anchorText
|
||||
activeChunk.end = activeChunk.start + activeChunk.text.length
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export const splitChunkAtCursor = (
|
||||
selection: Selection,
|
||||
contentState: {
|
||||
entireSelectionFormatted?: boolean
|
||||
contentRegistry: FlexContentType
|
||||
},
|
||||
contentRegistry: FlexContentType,
|
||||
activeChunkRef: MutableRefObject<ChunkInfo | undefined>,
|
||||
updateContentState: FunctionType,
|
||||
) => {
|
||||
if (!activeChunkRef.current || !contentState?.contentRegistry) return
|
||||
const selection = window.getSelection()
|
||||
if (!activeChunkRef.current || !contentRegistry || !selection) return
|
||||
const cursorPosition = selection.anchorOffset
|
||||
const normalizedCursorPosition = getNormalizedCursorPosition()
|
||||
const activeChunk = activeChunkRef.current
|
||||
const activeTextConfig = contentState.contentRegistry[
|
||||
activeChunk.parent
|
||||
] as TextInfo
|
||||
const normalizedCursorPosition =
|
||||
selection.anchorNode &&
|
||||
calculateOffset(
|
||||
selection.getRangeAt(0),
|
||||
selection.anchorNode,
|
||||
cursorPosition,
|
||||
)
|
||||
const activeTextConfig = contentRegistry[activeChunk.parent] as TextInfo
|
||||
const newTextConfigId = uuid()
|
||||
const order = activeTextConfig.order + 1
|
||||
const newTextConfig: TextInfo = {
|
||||
@@ -63,12 +151,123 @@ export const splitChunkAtCursor = (
|
||||
newTextConfig.chunks[chunkId] = activeTextConfig.chunks[chunkId]
|
||||
delete activeTextConfig.chunks[chunkId]
|
||||
}
|
||||
const textConfigs = Object.values(contentState.contentRegistry)
|
||||
const textConfigs = Object.values(contentRegistry)
|
||||
textConfigs.forEach((textConfig) => {
|
||||
textConfig.order >= order && textConfig.order++
|
||||
})
|
||||
|
||||
// Update the content state
|
||||
contentState.contentRegistry[newTextConfigId] = newTextConfig
|
||||
updateContentState({ ...contentState })
|
||||
}
|
||||
contentRegistry[newTextConfigId] = newTextConfig
|
||||
return contentRegistry
|
||||
}
|
||||
|
||||
// Define event handlers
|
||||
export const eventHandlers: EventHandlers = {
|
||||
[ArEditorEvents.CLICK]: {
|
||||
handler1: () => console.log("Handler 1 for Click event triggered!"),
|
||||
},
|
||||
[ArEditorEvents.INPUT]: {
|
||||
handleInput,
|
||||
},
|
||||
[ArEditorEvents.KEYDOWN]: {
|
||||
onKeyDown,
|
||||
},
|
||||
[ArEditorEvents.ENTER]: {
|
||||
enterHandler: (state, activeChunkRef) => {
|
||||
return splitChunkAtCursor(state.registry, activeChunkRef) || state
|
||||
},
|
||||
},
|
||||
[ArEditorEvents.BACKSPACE]: {
|
||||
backspaceHandler: (state, activeChunkRef) => {
|
||||
const { registry, cursorPosition } = state
|
||||
const selection = window.getSelection()
|
||||
if (selection) {
|
||||
const range = selection.getRangeAt(0)
|
||||
let anchorText = (selection.anchorNode as HTMLElement).textContent
|
||||
anchorText =
|
||||
anchorText &&
|
||||
anchorText?.slice(0, cursorPosition - 1) +
|
||||
anchorText?.slice(cursorPosition)
|
||||
state.cursorPosition -= 1
|
||||
const startContainer = range.startContainer
|
||||
|
||||
if (registry && activeChunkRef.current) {
|
||||
const textConfig = registry[activeChunkRef.current.parent]
|
||||
textConfig.text = anchorText // TODO: Fix this value, might only capture chunk's text
|
||||
if (
|
||||
startContainer?.parentNode &&
|
||||
!(startContainer.parentNode as HTMLElement).classList.contains(
|
||||
"ar-FlexContent",
|
||||
)
|
||||
) {
|
||||
textConfig.startContainer = startContainer
|
||||
}
|
||||
const activeText = registry[activeChunkRef.current.parent]
|
||||
const activeChunk = activeText?.chunks[activeChunkRef.current.id]
|
||||
activeChunk.text = anchorText
|
||||
activeChunk.end = activeChunk.start + activeChunk.text.length
|
||||
}
|
||||
return state
|
||||
}
|
||||
},
|
||||
},
|
||||
[ArEditorEvents.DELETE]: {
|
||||
deleteHandler: (state) => {
|
||||
const range = window.getSelection()?.getRangeAt(0)
|
||||
if (range) {
|
||||
if (isMac || isIOS || isAndroid) {
|
||||
if (range.startOffset > 0) {
|
||||
range.setStart(range.startContainer, range.startOffset - 1)
|
||||
range.deleteContents()
|
||||
}
|
||||
} else {
|
||||
// On Windows, "Delete" key deletes character after the cursor
|
||||
if (
|
||||
range.startContainer.textContent &&
|
||||
range.startOffset < range.startContainer.textContent.length
|
||||
) {
|
||||
range.setEnd(range.startContainer, range.startOffset + 1)
|
||||
range.deleteContents()
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
},
|
||||
},
|
||||
[ArEditorEvents.FORMAT]: (contentState) => {
|
||||
console.log("Formatting event triggered!")
|
||||
return contentState
|
||||
},
|
||||
[ArEditorEvents.BOLD]: {
|
||||
handler: ({ selectionInfoRef, contentState }) => {
|
||||
selectionInfoRef.current &&
|
||||
contentState &&
|
||||
applyTool(
|
||||
"bold",
|
||||
true,
|
||||
selectionInfoRef.current,
|
||||
contentState,
|
||||
(state) => {
|
||||
selectionInfoRef.current = null
|
||||
return state
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
[ArEditorEvents.ITALIC]: {
|
||||
handler: () => console.log("Italic handler triggered!"),
|
||||
},
|
||||
[ArEditorEvents.UNDERLINE]: {
|
||||
handler: () => console.log("Underline handler triggered!"),
|
||||
},
|
||||
[ArEditorEvents.STRIKETHROUGH]: {
|
||||
handler: () => console.log("Strikethrough handler triggered!"),
|
||||
},
|
||||
[ArEditorEvents.SUBSCRIPT]: {
|
||||
handler: () => console.log("Subscript handler triggered!"),
|
||||
},
|
||||
[ArEditorEvents.SUPERSCRIPT]: {
|
||||
handler: () => console.log("Superscript handler triggered!"),
|
||||
},
|
||||
// Add more handlers as needed
|
||||
}
|
||||
|
||||
@@ -1,66 +1,62 @@
|
||||
import { EventHandlerOptions, EventHandlers } from "./types"
|
||||
import {
|
||||
ArEditorEvents,
|
||||
EventHandlerCollection,
|
||||
EventHandlerOptions,
|
||||
} from "@armco/types"
|
||||
import { eventHandlers, shortcuts } from "./handlers"
|
||||
|
||||
// Define event types
|
||||
enum ArEventTypes {
|
||||
CLICK = "click",
|
||||
INPUT = "input",
|
||||
SELECTIONCHANGE = "selectionchange",
|
||||
KEYDOWN = "keydown",
|
||||
BOLD = "bold",
|
||||
ITALIC = "italic",
|
||||
UNDERLINE = "underline",
|
||||
STRIKETHROUGH = "strikethrough",
|
||||
SUBSCRIPT = "subscript",
|
||||
SUPERSCRIPT = "superscript",
|
||||
}
|
||||
|
||||
// Define event handlers
|
||||
const eventHandlers: EventHandlers = {
|
||||
[ArEventTypes.CLICK]: {
|
||||
handler1: () => console.log("Handler 1 for Click event triggered!"),
|
||||
handler2: () => console.log("Handler 2 for Click event triggered!"),
|
||||
},
|
||||
[ArEventTypes.INPUT]: {
|
||||
handler1: () => console.log("Handler 1 for Input event triggered!"),
|
||||
handler2: () => console.log("Handler 2 for Input event triggered!"),
|
||||
},
|
||||
[ArEventTypes.KEYDOWN]: {
|
||||
handler1: () => console.log("Handler 1 for Keydown event triggered!"),
|
||||
handler2: () => console.log("Handler 2 for Keydown event triggered!"),
|
||||
},
|
||||
[ArEventTypes.BOLD]: {
|
||||
handler: () => console.log("Bold handler triggered!"),
|
||||
},
|
||||
[ArEventTypes.ITALIC]: {
|
||||
handler: () => console.log("Italic handler triggered!"),
|
||||
},
|
||||
[ArEventTypes.UNDERLINE]: {
|
||||
handler: () => console.log("Underline handler triggered!"),
|
||||
},
|
||||
// Add more handlers as needed
|
||||
}
|
||||
|
||||
// Define keyboard shortcut handlers
|
||||
const macShortcuts: { [key: string]: ArEventTypes } = {
|
||||
"cmd+b": ArEventTypes.BOLD, // Bold
|
||||
"cmd+i": ArEventTypes.ITALIC, // Italic
|
||||
"cmd+u": ArEventTypes.UNDERLINE, // Underline
|
||||
"cmd+s": ArEventTypes.STRIKETHROUGH, // Strikethrough
|
||||
"cmd+z": ArEventTypes.SUBSCRIPT, // Subscript
|
||||
"cmd+y": ArEventTypes.SUPERSCRIPT, // Superscript
|
||||
}
|
||||
|
||||
const windowsShortcuts: { [key: string]: ArEventTypes } = {
|
||||
"ctrl+b": ArEventTypes.BOLD, // Bold
|
||||
"ctrl+i": ArEventTypes.ITALIC, // Italic
|
||||
"ctrl+u": ArEventTypes.UNDERLINE, // Underline
|
||||
"ctrl+s": ArEventTypes.STRIKETHROUGH, // Strikethrough
|
||||
"ctrl+z": ArEventTypes.SUBSCRIPT, // Subscript
|
||||
"ctrl+y": ArEventTypes.SUPERSCRIPT, // Superscript
|
||||
}
|
||||
const isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform)
|
||||
|
||||
/**
|
||||
* Utility designed to manage and hook various event handlers and keyboard shortcuts in a web application.
|
||||
* It provides a structured way to define, include, and exclude event handlers based on specific criteria.
|
||||
*
|
||||
* **Event Hooking**: The `hook` method allows for the inclusion and exclusion of specific event handlers based on provided options (`EventHandlerOptions`).
|
||||
* It processes the include and exclude options to determine which handlers should be hooked.
|
||||
*
|
||||
* **Include and Exclude Options**:
|
||||
*
|
||||
* - **Include Options**: Specifies which event handlers to include by providing an array of event types and handler names.
|
||||
* - **Exclude Options**: Specifies which event handlers to exclude by providing an array of event types and handler names.
|
||||
* Handlers present in both include and exclude lists are excluded, with a warning logged to the console.
|
||||
*
|
||||
* **Event Handlers**: Event handlers are hooked to the document based on the processed include and exclude lists.
|
||||
* Handlers are defined in a collection (`eventHandlers`) and are hooked to their respective event types.
|
||||
*
|
||||
* **Platform-Specific Shortcuts**:
|
||||
*
|
||||
* The utility detects the user's platform (Mac or Windows) and hooks the appropriate keyboard shortcuts.
|
||||
* Keyboard shortcuts are mapped to specific event types and are hooked to the document (DOCUMENT ONLY).
|
||||
* When a keyboard shortcut is detected, the corresponding event handler is triggered, and the default action for the shortcut is prevented.
|
||||
*
|
||||
* **Including Event Handlers**: Specify which event handlers to include by providing an `include` option with the event type and handler names.
|
||||
*
|
||||
* **Excluding Event Handlers**: Specify which event handlers to exclude by providing an `exclude` option with the event type and handler names.
|
||||
*
|
||||
* **Hooking Event Handlers**: Call the `hook` method with the desired options to hook the event handlers and keyboard shortcuts.
|
||||
*
|
||||
* To apply default formatting handlers like bold, italic, underline etc., don't pass options
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import EventsManager, { ArEventTypes } from './index';
|
||||
*
|
||||
* EventsManager.hook({
|
||||
* include: [
|
||||
* { eventType: ArEventTypes.CLICK, handlerNames: ['handler1'] },
|
||||
* { eventType: ArEventTypes.INPUT, handlerNames: ['handler2'] },
|
||||
* ],
|
||||
* exclude: [
|
||||
* { eventType: ArEventTypes.KEYDOWN, handlerNames: ['handler1'] },
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
class EventsManager {
|
||||
public static hook(options?: EventHandlerOptions) {
|
||||
public static hook(
|
||||
options?: EventHandlerOptions | null,
|
||||
dependencies?: { [key: string]: any },
|
||||
) {
|
||||
const includedHandlers = new Set<string>()
|
||||
const excludedHandlers = new Set<string>()
|
||||
|
||||
@@ -95,30 +91,24 @@ class EventsManager {
|
||||
if (excludedHandlers.has(handlerKey)) {
|
||||
continue
|
||||
}
|
||||
document.addEventListener(eventType, handler)
|
||||
// document.addEventListener(eventType, () => handler(dependencies))
|
||||
}
|
||||
}
|
||||
|
||||
// Determine platform and hook keyboard shortcut handlers
|
||||
const isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform)
|
||||
const shortcuts = isMac ? macShortcuts : windowsShortcuts
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
const key = `${isMac ? "cmd" : "ctrl"}+${event.key.toLowerCase()}`
|
||||
const eventType = shortcuts[key]
|
||||
if (eventType && !excludedHandlers.has(`${eventType}:handler`)) {
|
||||
if (
|
||||
includedHandlers.size === 0 ||
|
||||
includedHandlers.has(`${eventType}:handler`)
|
||||
) {
|
||||
eventHandlers[eventType].handler()
|
||||
event.preventDefault() // Prevent default action for the shortcut
|
||||
}
|
||||
}
|
||||
})
|
||||
// DEFAULT HANDLERS: Hook formatting handler (single keydown handler that checks key press and
|
||||
// calls corresponding handler for eg. for bold and italic formatting)
|
||||
// document.addEventListener("keydown", (event) => {
|
||||
// if (event.ctrlKey || event.metaKey) {
|
||||
// const eventType = shortcuts[event.key.toLowerCase()]
|
||||
// if (eventType && eventHandlers[eventType]) {
|
||||
// event.preventDefault() // Prevent default action for the shortcut
|
||||
// ;(eventHandlers[eventType] as EventHandlerCollection).handler(
|
||||
// dependencies,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
// Export the module
|
||||
export default EventsManager
|
||||
export { ArEventTypes }
|
||||
|
||||
10
packages/event-manager/src/types.d.ts
vendored
10
packages/event-manager/src/types.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
export type EventHandlerOptions = {
|
||||
include?: { eventType: ArEventTypes; handlerNames: string[] }[]
|
||||
exclude?: { eventType: ArEventTypes; handlerNames: string[] }[]
|
||||
}
|
||||
|
||||
export interface EventHandlers {
|
||||
[key: string]: {
|
||||
[handlerName: string]: () => void
|
||||
}
|
||||
}
|
||||
|
||||
27
packages/event-manager/tsconfig.json
Normal file
27
packages/event-manager/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
41
packages/event-manager/vite-dev.config.ts
Normal file
41
packages/event-manager/vite-dev.config.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
|
||||
},
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
20
packages/event-manager/vite-run.config.ts
Normal file
20
packages/event-manager/vite-run.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "src/setupTests",
|
||||
mockReset: true,
|
||||
},
|
||||
})
|
||||
40
packages/event-manager/vite.config.ts
Normal file
40
packages/event-manager/vite.config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
|
||||
},
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
31
packages/formatter/build-tools/build.sh
Executable file
31
packages/formatter/build-tools/build.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get the directory of the current script
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Default values
|
||||
DEV_FLAG=""
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"
|
||||
do
|
||||
case $arg in
|
||||
--dev)
|
||||
DEV_FLAG="--dev"
|
||||
shift # Remove --dev from processing
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
|
||||
echo "[BUILD:SH] Removing build if exists"
|
||||
rm -rf build
|
||||
echo "[BUILD:SH] Checking TS Types"
|
||||
npx tsc
|
||||
echo "[BUILD:SH] Initiating build..."
|
||||
# Conditionally use vite-dev.config.ts if --dev flag is present
|
||||
if [ "$DEV_FLAG" == "--dev" ]; then
|
||||
vite build --config vite-dev.config.ts
|
||||
else
|
||||
vite build
|
||||
fi
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"name": "@sampadak/formatter",
|
||||
"version": "1.0.0",
|
||||
"main": "build/index.js",
|
||||
"types": "./build/types/index.d.ts",
|
||||
"main": "./build/cjs/index.js",
|
||||
"module": "./build/es/index.js",
|
||||
"scripts": {
|
||||
"build": "rm -rf build && tsc",
|
||||
"test": "jest"
|
||||
"dev": "vite --config vite-run.config.ts",
|
||||
"start": "serve -s build",
|
||||
"build": "./build-tools/build.sh",
|
||||
"build:sm": "./build-tools/build.sh --dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.0"
|
||||
|
||||
@@ -62,10 +62,7 @@ export const applyTool = (
|
||||
format: keyof TextFormat,
|
||||
formatValue: string | boolean | number | undefined,
|
||||
selectionInfo: SelectionInfo,
|
||||
contentState: {
|
||||
entireSelectionFormatted?: boolean
|
||||
contentRegistry: FlexContentType
|
||||
},
|
||||
contentState: FlexContentType,
|
||||
callback: FunctionType,
|
||||
) => {
|
||||
if (selectionInfo) {
|
||||
@@ -83,7 +80,7 @@ export const applyTool = (
|
||||
selectionInfo,
|
||||
contentStateClone.contentRegistry,
|
||||
)
|
||||
contentStateClone.entireSelectionFormatted = entireSelectionFormatted
|
||||
// contentStateClone.entireSelectionFormatted = entireSelectionFormatted
|
||||
|
||||
selectedNodes?.forEach((id: string) => {
|
||||
const currentConfig = contentStateClone.contentRegistry[id]
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"outDir": "build"
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"downlevelIteration": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
42
packages/formatter/vite-dev.config.ts
Normal file
42
packages/formatter/vite-dev.config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
|
||||
},
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
20
packages/formatter/vite-run.config.ts
Normal file
20
packages/formatter/vite-run.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "src/setupTests",
|
||||
mockReset: true,
|
||||
},
|
||||
})
|
||||
41
packages/formatter/vite.config.ts
Normal file
41
packages/formatter/vite.config.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
|
||||
},
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
27
packages/history/tsconfig.json
Normal file
27
packages/history/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
27
packages/persistence/tsconfig.json
Normal file
27
packages/persistence/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.0"
|
||||
|
||||
27
packages/plugin-manager/tsconfig.json
Normal file
27
packages/plugin-manager/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
32
packages/selection-manager/build-tools/build.sh
Executable file
32
packages/selection-manager/build-tools/build.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get the directory of the current script
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Default values
|
||||
DEV_FLAG=""
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"
|
||||
do
|
||||
case $arg in
|
||||
--dev)
|
||||
DEV_FLAG="--dev"
|
||||
shift # Remove --dev from processing
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
|
||||
echo "[BUILD:SH] Removing build if exists"
|
||||
rm -rf build
|
||||
echo "[BUILD:SH] Checking TS Types"
|
||||
npx tsc
|
||||
echo "[BUILD:SH] Initiating build..."
|
||||
# Conditionally use vite-dev.config.ts if --dev flag is present
|
||||
if [ "$DEV_FLAG" == "--dev" ]; then
|
||||
vite build --config vite-dev.config.ts
|
||||
else
|
||||
vite build
|
||||
fi
|
||||
cp src/types.d.ts build/types
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "@sampadak/selection-manager",
|
||||
"version": "1.0.0",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"main": "build/cjs/index.js",
|
||||
"module": "build/es/index.js",
|
||||
"types": "build/types/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "rm -rf build && tsc && cp src/types.d.ts build",
|
||||
"test": "jest"
|
||||
"build": "./build-tools/build.sh",
|
||||
"build:sm": "./build-tools/build.sh --dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.0"
|
||||
|
||||
@@ -3,26 +3,14 @@ import { ISelectionManager } from "./types"
|
||||
import { processCaret, processRange } from "./util"
|
||||
|
||||
class SelectionManager {
|
||||
static getSelection(
|
||||
contentRegistry: FlexContentType,
|
||||
doc?: Document,
|
||||
win?: Window,
|
||||
atomicContentSelector?: string,
|
||||
): SelectionInfo | null {
|
||||
const docObj = doc || document
|
||||
const selection = (win || window).getSelection()
|
||||
atomicContentSelector = atomicContentSelector || "ar-Text"
|
||||
static getSelection(contentRegistry: FlexContentType): SelectionInfo | null {
|
||||
const selection = window.getSelection()
|
||||
if (selection) {
|
||||
if (selection.type === "Range") {
|
||||
return processRange(
|
||||
selection,
|
||||
docObj,
|
||||
contentRegistry,
|
||||
atomicContentSelector,
|
||||
)
|
||||
} else if (selection.type === "Caret" && contentRegistry) {
|
||||
return processCaret(selection, contentRegistry, atomicContentSelector)
|
||||
}
|
||||
// if (selection.type === "Range") {
|
||||
// return processRange(docObj, contentRegistry, atomicContentSelector)
|
||||
// } else if (selection.type === "Caret" && contentRegistry) {
|
||||
// return processCaret(contentRegistry, atomicContentSelector)
|
||||
// }
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -44,4 +32,4 @@ class SelectionManager {
|
||||
}
|
||||
|
||||
export default SelectionManager as ISelectionManager
|
||||
export * from "./util"
|
||||
export * from "./util"
|
||||
|
||||
@@ -1,22 +1,59 @@
|
||||
import { FlexContentType, SelectionInfo, TextInfo } from "@armco/types"
|
||||
|
||||
export const getTextContainerId = (
|
||||
node: Node | HTMLElement,
|
||||
atomicContentSelector: string,
|
||||
) => {
|
||||
export const getTextContainerId = (node: Node | HTMLElement) => {
|
||||
const textElementNode =
|
||||
node.nodeType === Node.TEXT_NODE
|
||||
? node.parentElement?.closest(atomicContentSelector)
|
||||
? node.parentElement?.closest(".ar-Text")
|
||||
: node
|
||||
return (textElementNode as HTMLElement)?.id
|
||||
}
|
||||
|
||||
export function getActiveTextAndChunk(
|
||||
contentRegistry: FlexContentType,
|
||||
normalizedCursorPosition: number | null,
|
||||
) {
|
||||
const anchorNode = window.getSelection()?.anchorNode
|
||||
const activeText =
|
||||
anchorNode && contentRegistry[getTextContainerId(anchorNode)]
|
||||
let activeChunk
|
||||
if (activeText?.chunks && normalizedCursorPosition !== null) {
|
||||
activeChunk = Object.values((activeText as TextInfo).chunks).find(
|
||||
(chunk) =>
|
||||
chunk.start <= normalizedCursorPosition &&
|
||||
chunk.end >= normalizedCursorPosition,
|
||||
)
|
||||
} else {
|
||||
// activeChunk
|
||||
}
|
||||
// if (!activeChunk) {
|
||||
console.log(contentRegistry)
|
||||
console.log(normalizedCursorPosition)
|
||||
// }
|
||||
return { activeText, activeChunk }
|
||||
}
|
||||
|
||||
export function getCursorPosition() {
|
||||
return window.getSelection()?.anchorOffset || 0
|
||||
}
|
||||
|
||||
export function getNormalizedCursorPosition() {
|
||||
const selection = window.getSelection()
|
||||
if (selection?.anchorNode) {
|
||||
return calculateOffset(
|
||||
selection.getRangeAt(0),
|
||||
selection.anchorNode,
|
||||
selection.anchorOffset,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const processRange = (
|
||||
selection: Selection,
|
||||
docObj: Document,
|
||||
contentRegistry: FlexContentType,
|
||||
atomicContentSelector: string,
|
||||
): SelectionInfo => {
|
||||
const selection = window.getSelection()
|
||||
const selectionInfo: SelectionInfo = {
|
||||
startOffset: -1,
|
||||
endOffset: -1,
|
||||
@@ -28,54 +65,50 @@ export const processRange = (
|
||||
raw: selection,
|
||||
}
|
||||
|
||||
const range = selection.getRangeAt(0)
|
||||
const content = range.cloneContents()
|
||||
if (selection) {
|
||||
const range = selection.getRangeAt(0)
|
||||
const content = range.cloneContents()
|
||||
|
||||
if (
|
||||
content.childNodes.length === 1 &&
|
||||
content.firstChild?.nodeType === Node.TEXT_NODE
|
||||
) {
|
||||
const parent = range.startContainer.parentElement
|
||||
?.closest(atomicContentSelector)
|
||||
?.cloneNode(true)
|
||||
parent && content.replaceChild(parent, content.firstChild)
|
||||
}
|
||||
if (
|
||||
content.childNodes.length === 1 &&
|
||||
content.firstChild?.nodeType === Node.TEXT_NODE
|
||||
) {
|
||||
const parent = range.startContainer.parentElement
|
||||
?.closest(atomicContentSelector)
|
||||
?.cloneNode(true)
|
||||
parent && content.replaceChild(parent, content.firstChild)
|
||||
}
|
||||
|
||||
const startParentId = getTextContainerId(
|
||||
range.startContainer,
|
||||
atomicContentSelector,
|
||||
)
|
||||
const endParentId = getTextContainerId(
|
||||
range.endContainer,
|
||||
atomicContentSelector,
|
||||
)
|
||||
const startParentId = getTextContainerId(range.startContainer)
|
||||
const endParentId = getTextContainerId(range.endContainer)
|
||||
|
||||
if (startParentId && endParentId) {
|
||||
const startOffset = calculateOffset(
|
||||
range,
|
||||
range.startContainer,
|
||||
range.startOffset,
|
||||
)
|
||||
const endOffset = calculateOffset(
|
||||
range,
|
||||
range.endContainer,
|
||||
range.endOffset,
|
||||
)
|
||||
if (startParentId && endParentId) {
|
||||
const startOffset = calculateOffset(
|
||||
range,
|
||||
range.startContainer,
|
||||
range.startOffset,
|
||||
)
|
||||
const endOffset = calculateOffset(
|
||||
range,
|
||||
range.endContainer,
|
||||
range.endOffset,
|
||||
)
|
||||
|
||||
// Update selectionInfo properties
|
||||
selectionInfo.startParentId = startParentId
|
||||
selectionInfo.endParentId = endParentId
|
||||
selectionInfo.startOffset = startOffset
|
||||
selectionInfo.endOffset = endOffset
|
||||
// Update selectionInfo properties
|
||||
selectionInfo.startParentId = startParentId
|
||||
selectionInfo.endParentId = endParentId
|
||||
selectionInfo.startOffset = startOffset
|
||||
selectionInfo.endOffset = endOffset
|
||||
|
||||
const walker = docObj.createTreeWalker(content, NodeFilter.SHOW_ELEMENT)
|
||||
const walker = docObj.createTreeWalker(content, NodeFilter.SHOW_ELEMENT)
|
||||
|
||||
while (walker.nextNode()) {
|
||||
const currNode = walker.currentNode as HTMLElement
|
||||
const node = (currNode?.closest(atomicContentSelector) ||
|
||||
range.commonAncestorContainer) as HTMLElement
|
||||
if (node) {
|
||||
selectionInfo.selectedNodes.add(node.id)
|
||||
while (walker.nextNode()) {
|
||||
const currNode = walker.currentNode as HTMLElement
|
||||
const node = (currNode?.closest(atomicContentSelector) ||
|
||||
range.commonAncestorContainer) as HTMLElement
|
||||
if (node) {
|
||||
selectionInfo.selectedNodes.add(node.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,65 +117,61 @@ export const processRange = (
|
||||
}
|
||||
|
||||
export const processCaret = (
|
||||
selection: Selection,
|
||||
contentRegistry: FlexContentType,
|
||||
atomicContentSelector: string, // Eg. .ar-Text
|
||||
): SelectionInfo | null => {
|
||||
if (selection.anchorNode) {
|
||||
const selectionInfo: SelectionInfo = {
|
||||
startOffset: -1,
|
||||
endOffset: -1,
|
||||
startParentId: "",
|
||||
endParentId: "",
|
||||
startChunkId: "",
|
||||
endChunkId: "",
|
||||
selectedNodes: new Set(),
|
||||
raw: selection,
|
||||
}
|
||||
let anchorNode: Node | HTMLElement | null = selection.anchorNode
|
||||
if (
|
||||
anchorNode.nodeType === Node.ELEMENT_NODE &&
|
||||
(anchorNode as HTMLElement).classList.contains("ar-FlexContent")
|
||||
) {
|
||||
// Implies there's no text yet, so we get first text config as
|
||||
// at least 1 is always expected to be present
|
||||
anchorNode = (anchorNode as HTMLElement).querySelector(
|
||||
atomicContentSelector,
|
||||
)
|
||||
}
|
||||
const normalizedCursorPosition =
|
||||
anchorNode &&
|
||||
calculateOffset(
|
||||
selection.getRangeAt(0),
|
||||
anchorNode,
|
||||
selection.anchorOffset,
|
||||
)
|
||||
const activeText =
|
||||
anchorNode &&
|
||||
contentRegistry[getTextContainerId(anchorNode, atomicContentSelector)]
|
||||
const activeChunk =
|
||||
activeText?.chunks && normalizedCursorPosition !== null
|
||||
? Object.values((activeText as TextInfo).chunks).find(
|
||||
(chunk) =>
|
||||
chunk.start <= normalizedCursorPosition &&
|
||||
chunk.end >= normalizedCursorPosition,
|
||||
)
|
||||
: undefined
|
||||
if (normalizedCursorPosition) {
|
||||
selectionInfo.startOffset = normalizedCursorPosition
|
||||
selectionInfo.endOffset = normalizedCursorPosition
|
||||
}
|
||||
if (activeText && activeChunk) {
|
||||
selectionInfo.startParentId = activeText.id
|
||||
selectionInfo.endParentId = activeText.id
|
||||
selectionInfo.startChunkId = activeChunk.id
|
||||
selectionInfo.endChunkId = activeChunk.id
|
||||
}
|
||||
return selectionInfo
|
||||
}
|
||||
// const selection = window.getSelection()
|
||||
// if (selection?.anchorNode) {
|
||||
// const selectionInfo: SelectionInfo = {
|
||||
// startOffset: -1,
|
||||
// endOffset: -1,
|
||||
// startParentId: "",
|
||||
// endParentId: "",
|
||||
// startChunkId: "",
|
||||
// endChunkId: "",
|
||||
// selectedNodes: new Set(),
|
||||
// raw: selection,
|
||||
// }
|
||||
// let anchorNode: Node | HTMLElement | null = selection.anchorNode
|
||||
// if (
|
||||
// anchorNode.nodeType === Node.ELEMENT_NODE &&
|
||||
// (anchorNode as HTMLElement).classList.contains("ar-FlexContent")
|
||||
// ) {
|
||||
// // Implies there's no text yet, so we get first text config as
|
||||
// // at least 1 is always expected to be present
|
||||
// anchorNode = (anchorNode as HTMLElement).querySelector(
|
||||
// atomicContentSelector,
|
||||
// )
|
||||
// }
|
||||
// const normalizedCursorPosition = getNormalizedCursorPosition()
|
||||
// const { activeText, activeChunk } = getActiveTextAndChunk(
|
||||
// contentRegistry,
|
||||
// atomicContentSelector,
|
||||
// normalizedCursorPosition,
|
||||
// )
|
||||
// if (normalizedCursorPosition !== null) {
|
||||
// selectionInfo.startOffset = normalizedCursorPosition
|
||||
// selectionInfo.endOffset = normalizedCursorPosition
|
||||
// }
|
||||
// if (activeText && activeChunk) {
|
||||
// selectionInfo.startParentId = activeText.id
|
||||
// selectionInfo.endParentId = activeText.id
|
||||
// selectionInfo.startChunkId = activeChunk.id
|
||||
// selectionInfo.endChunkId = activeChunk.id
|
||||
// }
|
||||
// return selectionInfo
|
||||
// }
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* The calculateOffset function calculates the cursor position considering the entire text length of
|
||||
* all the chunks within the parent element with the class .ar-Text.
|
||||
*
|
||||
* @param range Range at index 0 of current selection
|
||||
* @param container chunk within which cursor position is known and used to calculate within parent
|
||||
* @param offset cursor position
|
||||
* @returns number representing cursor position within parent
|
||||
*/
|
||||
export function calculateOffset(
|
||||
range: Range,
|
||||
container: Node,
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"outDir": "build"
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
36
packages/selection-manager/vite-dev.config.ts
Normal file
36
packages/selection-manager/vite-dev.config.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d).{ts,tsx}")),
|
||||
},
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [new RegExp("@armco/*"), "uuid"],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
20
packages/selection-manager/vite-run.config.ts
Normal file
20
packages/selection-manager/vite-run.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "src/setupTests",
|
||||
mockReset: true,
|
||||
},
|
||||
})
|
||||
41
packages/selection-manager/vite.config.ts
Normal file
41
packages/selection-manager/vite.config.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|index).{ts,tsx}")),
|
||||
},
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
27
packages/syntax-highlighter/tsconfig.json
Normal file
27
packages/syntax-highlighter/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.0"
|
||||
|
||||
27
packages/toolbar/tsconfig.json
Normal file
27
packages/toolbar/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "build",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
31
packages/ui/build-tools/build.sh
Executable file
31
packages/ui/build-tools/build.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get the directory of the current script
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Default values
|
||||
DEV_FLAG=""
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"
|
||||
do
|
||||
case $arg in
|
||||
--dev)
|
||||
DEV_FLAG="--dev"
|
||||
shift # Remove --dev from processing
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
|
||||
echo "[BUILD:SH] Removing build if exists"
|
||||
rm -rf build
|
||||
echo "[BUILD:SH] Checking TS Types"
|
||||
npx tsc
|
||||
echo "[BUILD:SH] Initiating build..."
|
||||
# Conditionally use vite-dev.config.ts if --dev flag is present
|
||||
if [ "$DEV_FLAG" == "--dev" ]; then
|
||||
vite build --config vite-dev.config.ts
|
||||
else
|
||||
vite build
|
||||
fi
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"name": "@sampadak/ui",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.tsx",
|
||||
"types": "./build/types/FlexContent.d.ts",
|
||||
"main": "./build/cjs/FlexContent.js",
|
||||
"module": "./build/es/FlexContent.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest"
|
||||
"dev": "vite --config vite-run.config.ts",
|
||||
"start": "serve -s build",
|
||||
"build": "./build-tools/build.sh",
|
||||
"build:sm": "./build-tools/build.sh --dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.0"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*
|
||||
*/
|
||||
import {
|
||||
CompositionEvent,
|
||||
FormEvent,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@@ -18,37 +18,33 @@ import {
|
||||
ArPopoverSlots,
|
||||
ChunkInfo,
|
||||
CompInfo,
|
||||
EventHandlerCollection,
|
||||
FlexContentProps,
|
||||
FlexContentState,
|
||||
FlexContentType,
|
||||
LibRepoType,
|
||||
SelectionInfo,
|
||||
TextInfo,
|
||||
TextProps,
|
||||
} from "@armco/types"
|
||||
import {
|
||||
applyTool, // Formatting
|
||||
} from "@sampadak/formatter"
|
||||
// DomHelper,
|
||||
// PopoverV2,
|
||||
import selectionManager from "@sampadak/selection-manager"
|
||||
// splitChunkAtCursor, // Formatting
|
||||
// TextFormatter,
|
||||
import * as atoms from "../../atoms"
|
||||
import * as molecules from "../../molecules"
|
||||
import * as components from "@armco/components"
|
||||
import { PopoverV2 } from "@armco/components"
|
||||
import { eventHandlers } from "@sampadak/event-manager/handlers"
|
||||
import EventManager from "@sampadak/event-manager"
|
||||
import selectionManager, {
|
||||
getActiveTextAndChunk,
|
||||
getCursorPosition,
|
||||
getNormalizedCursorPosition,
|
||||
} from "@sampadak/selection-manager"
|
||||
import TextFormatter from "./TextFormatter"
|
||||
import MemoWrapper from "./MemoWrapper"
|
||||
import "./FlexContent.scss"
|
||||
|
||||
const demoDummyState: {
|
||||
entireSelectionFormatted?: boolean
|
||||
contentRegistry: FlexContentType
|
||||
} = {
|
||||
contentRegistry: {},
|
||||
}
|
||||
const demoDummyState: FlexContentType = {}
|
||||
|
||||
Array.from({ length: 4 }, (i: number) => {
|
||||
const id = uuid()
|
||||
demoDummyState.contentRegistry[id] = {
|
||||
demoDummyState[id] = {
|
||||
id,
|
||||
name: "Text",
|
||||
text: `lorem ipsum${i}`,
|
||||
order: i,
|
||||
chunks: {},
|
||||
@@ -57,37 +53,16 @@ Array.from({ length: 4 }, (i: number) => {
|
||||
return null
|
||||
})
|
||||
|
||||
const MemoWrapper = memo(
|
||||
(props: { componentName: string; props: TextProps; repo: LibRepoType }) => {
|
||||
const { componentName, props: componentProps, repo } = props
|
||||
const Component = repo[componentName]
|
||||
return Component ? (
|
||||
<Component key={componentProps.descriptor.id} {...componentProps} />
|
||||
) : null
|
||||
},
|
||||
(prevProps, props) => {
|
||||
return (
|
||||
JSON.stringify(prevProps.props.descriptor.chunks) !==
|
||||
JSON.stringify(props.props.descriptor.chunks)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const initId = uuid()
|
||||
const initText = {
|
||||
[initId]: {
|
||||
id: initId,
|
||||
name: "Text",
|
||||
text: "",
|
||||
order: 0,
|
||||
chunks: {},
|
||||
length: -1,
|
||||
},
|
||||
}
|
||||
const DEFAULT_PREVENTERS = ["Enter", "Backspace", "Delete"]
|
||||
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||
const isAndroid = /Android/.test(navigator.userAgent)
|
||||
|
||||
const FlexContent = (props: FlexContentProps): JSX.Element => {
|
||||
const {
|
||||
@@ -96,211 +71,117 @@ const FlexContent = (props: FlexContentProps): JSX.Element => {
|
||||
hasFloatingFormatter,
|
||||
content = initText,
|
||||
demo,
|
||||
dev,
|
||||
onCommit,
|
||||
theme,
|
||||
} = props
|
||||
const [contentState, updateContentState] = useState<{
|
||||
entireSelectionFormatted?: boolean
|
||||
contentRegistry: FlexContentType
|
||||
}>()
|
||||
const [state, setState] = useState<FlexContentState>({
|
||||
cursorPosition: 0,
|
||||
registry: {},
|
||||
})
|
||||
const [formatterDisplayed, displayFormatter] = useState<boolean>(false)
|
||||
const activeChunkRef = useRef<ChunkInfo>()
|
||||
const flexContentRef = useRef<HTMLDivElement>(null)
|
||||
const selectionInfoRef = useRef<SelectionInfo | null>()
|
||||
const keyRef = useRef<string>()
|
||||
const repo: LibRepoType = useMemo(() => ({ ...atoms, ...molecules }), [])
|
||||
const winObj = useMemo(() => DomHelper.getWindowElement(demo), [demo])
|
||||
const docObj = useMemo(() => DomHelper.getDocumentElement(demo), [demo])
|
||||
const memoizedSelectionHandler = useCallback(
|
||||
() =>
|
||||
contentState?.contentRegistry && selectionManager.getSelection(
|
||||
contentState.contentRegistry,
|
||||
docObj,
|
||||
winObj,
|
||||
".ar-Text",
|
||||
),
|
||||
[contentState, docObj, winObj],
|
||||
)
|
||||
const repo: { [key: string]: any } = useMemo(() => ({ ...components }), [])
|
||||
|
||||
const memoizedSelectionHandler = useCallback(() => {
|
||||
const { activeChunk } = getActiveTextAndChunk(
|
||||
state.registry,
|
||||
getNormalizedCursorPosition(),
|
||||
)
|
||||
state.cursorPosition = getCursorPosition()
|
||||
activeChunkRef.current = activeChunk
|
||||
selectionInfoRef.current = selectionManager.getSelection(state.registry)
|
||||
setState({ ...state })
|
||||
}, [state.registry])
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "b") {
|
||||
event.preventDefault()
|
||||
selectionInfoRef.current &&
|
||||
contentState &&
|
||||
applyTool(
|
||||
"bold",
|
||||
true,
|
||||
selectionInfoRef.current,
|
||||
contentState,
|
||||
(state) => {
|
||||
updateContentState(state)
|
||||
selectionInfoRef.current = null
|
||||
},
|
||||
)
|
||||
// undoSlots()
|
||||
}
|
||||
}
|
||||
|
||||
docObj?.addEventListener("keydown", handleKeyDown)
|
||||
|
||||
return () => {
|
||||
docObj?.removeEventListener("keydown", handleKeyDown)
|
||||
}
|
||||
}, [contentState, docObj])
|
||||
|
||||
useEffect(() => {
|
||||
const useContent = content
|
||||
? { contentRegistry: content }
|
||||
: demo
|
||||
? demoDummyState
|
||||
: { contentRegistry: content || {} }
|
||||
useContent?.contentRegistry &&
|
||||
Object.values(useContent.contentRegistry).forEach(
|
||||
(obj: TextInfo | CompInfo) => {
|
||||
obj.length = (obj.text as string)?.length
|
||||
if (Object.keys(obj.chunks).length === 0) {
|
||||
const id = uuid()
|
||||
const end = obj.text ? (obj.text as string).length - 1 : 0
|
||||
obj.chunks[id] = {
|
||||
id,
|
||||
start: 0,
|
||||
end,
|
||||
text: obj.text.substring(0, end),
|
||||
formats: {},
|
||||
parent: obj.id,
|
||||
}
|
||||
const useContent: FlexContentType = demo ? demoDummyState : content || {}
|
||||
useContent &&
|
||||
Object.values(useContent).forEach((obj: TextInfo | CompInfo) => {
|
||||
obj.length = (obj.text as string)?.length
|
||||
if (Object.keys(obj.chunks).length === 0) {
|
||||
const id = uuid()
|
||||
const end = obj.text ? (obj.text as string).length - 1 : 0
|
||||
obj.chunks[id] = {
|
||||
id,
|
||||
start: 0,
|
||||
end,
|
||||
text: obj.text.substring(0, end),
|
||||
formats: {},
|
||||
parent: obj.id,
|
||||
}
|
||||
},
|
||||
)
|
||||
updateContentState(useContent)
|
||||
}
|
||||
})
|
||||
setState({ registry: useContent })
|
||||
}, [content, demo])
|
||||
|
||||
useEffect(() => {
|
||||
const flexContentDom = flexContentRef.current
|
||||
const displayFormatterHandler = () =>
|
||||
winObj?.getSelection()?.toString().length && displayFormatter(true)
|
||||
// flexContentDom?.addEventListener("focus", memoizedSelectionHandler)
|
||||
docObj?.addEventListener("selectionchange", memoizedSelectionHandler)
|
||||
flexContentDom?.addEventListener("mouseup", displayFormatterHandler)
|
||||
return () => {
|
||||
// flexContentDom?.removeEventListener("focus", memoizedSelectionHandler)
|
||||
docObj?.removeEventListener("selectionchange", memoizedSelectionHandler)
|
||||
flexContentDom?.removeEventListener("mouseup", displayFormatterHandler)
|
||||
}
|
||||
}, [winObj, docObj, memoizedSelectionHandler, flexContentRef])
|
||||
document?.addEventListener("selectionchange", memoizedSelectionHandler)
|
||||
return () =>
|
||||
document?.removeEventListener("selectionchange", memoizedSelectionHandler)
|
||||
}, [memoizedSelectionHandler])
|
||||
|
||||
useEffect(() => {
|
||||
if (flexContentRef.current) {
|
||||
flexContentRef.current &&
|
||||
setTimeout(() => flexContentRef.current?.focus(), 10)
|
||||
}
|
||||
}, [flexContentRef])
|
||||
|
||||
const textConfigs =
|
||||
contentState &&
|
||||
Object.entries(contentState.contentRegistry as FlexContentType)
|
||||
const textConfigs = Object.entries(state.registry)
|
||||
textConfigs?.sort((t1, t2) => t1[1].order - t2[1].order)
|
||||
|
||||
const handleInput = (e: FormEvent<HTMLDivElement>) => {
|
||||
e.preventDefault()
|
||||
const input = (e as unknown as InputEvent).data
|
||||
const selection = winObj?.getSelection()
|
||||
if (selection) {
|
||||
const range = selection.getRangeAt(0)
|
||||
const anchorText =
|
||||
(selection.anchorNode as HTMLElement).textContent + (input || "")
|
||||
const cursorPosition = selection.anchorOffset
|
||||
const startContainer = range.startContainer
|
||||
if (flexContentRef.current) {
|
||||
const childNodes = Array.from(flexContentRef.current.childNodes)
|
||||
childNodes.forEach((node) => {
|
||||
node.nodeType === Node.TEXT_NODE && node.remove()
|
||||
})
|
||||
}
|
||||
|
||||
if (contentState && activeChunkRef.current) {
|
||||
const content = contentState.contentRegistry
|
||||
const textConfig = content[activeChunkRef.current.parent]
|
||||
textConfig.text = input // TODO: Fix this value, might only capture chunk's text
|
||||
textConfig.cursorPosition = cursorPosition
|
||||
if (
|
||||
startContainer?.parentNode &&
|
||||
!(startContainer.parentNode as HTMLElement).classList.contains(
|
||||
"ar-FlexContent",
|
||||
)
|
||||
) {
|
||||
textConfig.startContainer = startContainer
|
||||
}
|
||||
const activeText =
|
||||
contentState.contentRegistry[activeChunkRef.current.parent]
|
||||
const activeChunk = activeText?.chunks[activeChunkRef.current.id]
|
||||
activeChunk.text = anchorText
|
||||
activeChunk.end = activeChunk.start + activeChunk.text.length
|
||||
updateContentState({ ...contentState })
|
||||
}
|
||||
onCommit && onCommit()
|
||||
keyRef.current = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const contentRenders = (
|
||||
<div
|
||||
ref={flexContentRef}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
className="ar-FlexContent w-100"
|
||||
tabIndex={-1}
|
||||
onMouseUp={() =>
|
||||
window?.getSelection()?.toString().length && displayFormatter(true)
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
const selection = winObj?.getSelection()
|
||||
if (selection && contentState) {
|
||||
const range = selection.getRangeAt(0)
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
splitChunkAtCursor(
|
||||
selection,
|
||||
contentState,
|
||||
activeChunkRef,
|
||||
updateContentState,
|
||||
)
|
||||
} else if (e.key === "Backspace") {
|
||||
if (range && range.startOffset > 0) {
|
||||
range.setStart(range.startContainer, range.startOffset - 1)
|
||||
range.deleteContents()
|
||||
}
|
||||
} else if (e.key === "Delete") {
|
||||
if (isMac || isIOS || isAndroid) {
|
||||
if (range.startOffset > 0) {
|
||||
range.setStart(range.startContainer, range.startOffset - 1)
|
||||
range.deleteContents()
|
||||
}
|
||||
} else {
|
||||
// On Windows, "Delete" key deletes character after the cursor
|
||||
if (
|
||||
range.startContainer.textContent &&
|
||||
range.startOffset < range.startContainer.textContent.length
|
||||
) {
|
||||
range.setEnd(range.startContainer, range.startOffset + 1)
|
||||
range.deleteContents()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const updatedState = (
|
||||
eventHandlers.keydown as EventHandlerCollection
|
||||
).onKeyDown(e, state, activeChunkRef)
|
||||
setState({ ...updatedState })
|
||||
}}
|
||||
onBeforeInput={(e) => {
|
||||
e.preventDefault()
|
||||
const updatedState = (
|
||||
eventHandlers.input as EventHandlerCollection
|
||||
).handleInput(
|
||||
(e as CompositionEvent<HTMLInputElement>).data,
|
||||
state,
|
||||
activeChunkRef,
|
||||
)
|
||||
setState({ ...updatedState })
|
||||
onCommit && onCommit(flexContentRef, state.registry, activeChunkRef)
|
||||
keyRef.current = undefined
|
||||
}}
|
||||
onBeforeInput={handleInput}
|
||||
contentEditable={isEditable}
|
||||
suppressContentEditableWarning={true}
|
||||
>
|
||||
{textConfigs?.map((entry) => {
|
||||
const props: TextProps = {
|
||||
descriptor: entry[1] as TextInfo,
|
||||
descriptor: {
|
||||
...(entry[1] as TextInfo),
|
||||
},
|
||||
allowFormatting,
|
||||
isEditable,
|
||||
demo,
|
||||
}
|
||||
if (
|
||||
activeChunkRef.current &&
|
||||
Object.keys(entry[1].chunks).indexOf(activeChunkRef.current.id) > -1
|
||||
) {
|
||||
props.descriptor.cursorPosition = state.cursorPosition
|
||||
}
|
||||
return (
|
||||
<MemoWrapper
|
||||
key={`flex-content-item-${entry[0]}`}
|
||||
componentName={entry[1].name}
|
||||
repo={repo}
|
||||
Component={repo[entry[1].name || "Text"]}
|
||||
props={props}
|
||||
/>
|
||||
)
|
||||
@@ -312,7 +193,7 @@ const FlexContent = (props: FlexContentProps): JSX.Element => {
|
||||
<>
|
||||
{isEditable && allowFormatting && hasFloatingFormatter ? (
|
||||
<PopoverV2
|
||||
classes="align-self-start w-100 border-bottom"
|
||||
classes={`align-self-start w-100${dev ? " flex-grow-0" : ""}`}
|
||||
contentClasses="invert-bg"
|
||||
isOpen={formatterDisplayed}
|
||||
onClose={() => displayFormatter(false)}
|
||||
@@ -327,9 +208,9 @@ const FlexContent = (props: FlexContentProps): JSX.Element => {
|
||||
activeFormats={activeChunkRef.current?.formats}
|
||||
demo={demo}
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
content={contentState}
|
||||
content={state.registry}
|
||||
callback={(state) => {
|
||||
updateContentState(state)
|
||||
setState({ ...state })
|
||||
selectionInfoRef.current = null
|
||||
}}
|
||||
selectionInfo={selectionInfoRef.current}
|
||||
@@ -340,43 +221,49 @@ const FlexContent = (props: FlexContentProps): JSX.Element => {
|
||||
) : (
|
||||
contentRenders
|
||||
)}
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-4">
|
||||
<h6>Content Registry</h6>
|
||||
<pre className="my-3">
|
||||
{JSON.stringify(
|
||||
contentState?.contentRegistry,
|
||||
(key, value) => {
|
||||
if (key.startsWith("__reactFiber")) {
|
||||
if (value.stateNode?.data) {
|
||||
return value.stateNode.data
|
||||
} else {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
if (key === "id" || key === "length") {
|
||||
return undefined
|
||||
}
|
||||
return value
|
||||
},
|
||||
2,
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<h6>Active Chunk (ref - no live update)</h6>
|
||||
<pre className="my-3">
|
||||
{JSON.stringify(activeChunkRef.current, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<h6>Selection Info (ref - no live update)</h6>
|
||||
<pre className="my-3">
|
||||
{JSON.stringify(selectionInfoRef.current, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{dev && (
|
||||
<>
|
||||
<hr />
|
||||
<div className="vw-100 flex-grow-1 overflow-auto p-3">
|
||||
<div className="row">
|
||||
<div className="col-4">
|
||||
<h6>Content Registry</h6>
|
||||
<pre className="my-3">
|
||||
{JSON.stringify(
|
||||
state.registry,
|
||||
(key, value) => {
|
||||
if (key.startsWith("__reactFiber")) {
|
||||
if (value.stateNode?.data) {
|
||||
return value.stateNode.data
|
||||
} else {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
if (key === "id" || key === "length") {
|
||||
return undefined
|
||||
}
|
||||
return value
|
||||
},
|
||||
2,
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<h6>Active Chunk (ref - no live update)</h6>
|
||||
<pre className="my-3">
|
||||
{JSON.stringify(activeChunkRef.current, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<h6>Selection Info (ref - no live update)</h6>
|
||||
<pre className="my-3">
|
||||
{JSON.stringify(selectionInfoRef.current, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
19
packages/ui/src/MemoWrapper.tsx
Normal file
19
packages/ui/src/MemoWrapper.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { TextProps } from "@armco/types"
|
||||
import { ComponentType, memo } from "react"
|
||||
|
||||
const MemoWrapper = memo(
|
||||
(props: { props: TextProps; Component: ComponentType }) => {
|
||||
const { Component, props: componentProps } = props
|
||||
return Component ? (
|
||||
<Component key={componentProps.descriptor.id} {...componentProps} />
|
||||
) : null
|
||||
},
|
||||
(prevProps, props) => {
|
||||
return (
|
||||
JSON.stringify(prevProps.props.descriptor.chunks) !==
|
||||
JSON.stringify(props.props.descriptor.chunks)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default MemoWrapper
|
||||
6
packages/ui/src/Test.tsx
Normal file
6
packages/ui/src/Test.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import ReactDOM from "react-dom/client"
|
||||
import FlexContent from "./FlexContent"
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||
|
||||
root.render(<FlexContent />)
|
||||
@@ -4,8 +4,9 @@ import {
|
||||
TextFormatterProps,
|
||||
TextFormat,
|
||||
} from "@armco/types"
|
||||
import { applyTool } from "@sampadak/formatter"
|
||||
import TextTool from "./TextTool"
|
||||
import "./TextFormatter.component.scss"
|
||||
import "./TextFormatter.scss"
|
||||
|
||||
const editToolGroups: {
|
||||
[key: string]: {
|
||||
|
||||
@@ -5,13 +5,8 @@ import {
|
||||
ListItemContent,
|
||||
TextToolProps,
|
||||
} from "@armco/types"
|
||||
import {
|
||||
ColorSelectorRadio,
|
||||
List,
|
||||
LoadableIcon,
|
||||
PopoverV2,
|
||||
Tooltip,
|
||||
} from "../.."
|
||||
import { List, Icon, PopoverV2, Tooltip } from "@armco/components"
|
||||
import ColorSelectorRadio from "./ColorSelectorRadio"
|
||||
|
||||
const popovers = (restTools?: Array<ListItemContent>, theme?: ArThemes) => ({
|
||||
textColor: (
|
||||
@@ -90,20 +85,24 @@ const TextTool = ({
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<span>
|
||||
<LoadableIcon
|
||||
size={(size || "1rem") as string}
|
||||
<Icon
|
||||
icon={icon}
|
||||
color={iconColor}
|
||||
attributes={{
|
||||
size: (size || "1rem") as string,
|
||||
colors: { fillColor: iconColor },
|
||||
}}
|
||||
/>
|
||||
{category === "color" && (
|
||||
<div className="ar-TextTool__color-tool-indicator" />
|
||||
)}
|
||||
</span>
|
||||
{subIcon && (
|
||||
<LoadableIcon
|
||||
size={subIconSize || "0.8rem"}
|
||||
<Icon
|
||||
icon={subIcon}
|
||||
color={iconColor}
|
||||
attributes={{
|
||||
size: subIconSize || "0.8rem",
|
||||
colors: { fillColor: iconColor },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": [
|
||||
"dom",
|
||||
@@ -23,6 +23,5 @@
|
||||
],
|
||||
"target": "es5",
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["build", "plop-templates", "node_modules", "src/**/*.test.*", "src/**/*.spec.*", "src/stories", "scripts"]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
42
packages/ui/vite-dev.config.ts
Normal file
42
packages/ui/vite-dev.config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|index).{ts,tsx}")),
|
||||
},
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
20
packages/ui/vite-run.config.ts
Normal file
20
packages/ui/vite-run.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "src/setupTests",
|
||||
mockReset: true,
|
||||
},
|
||||
})
|
||||
41
packages/ui/vite.config.ts
Normal file
41
packages/ui/vite.config.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|index).{ts,tsx}")),
|
||||
},
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useRoutes } from "react-router-dom"
|
||||
import * as pages from "./pages"
|
||||
import Helper from "./utils/helper"
|
||||
import ROUTES from "./routes"
|
||||
|
||||
Helper.populateComponentsInRoutes(ROUTES, pages)
|
||||
|
||||
interface RouterProps {}
|
||||
|
||||
const Router = (props: RouterProps): JSX.Element | null => useRoutes(ROUTES)
|
||||
|
||||
export default Router
|
||||
@@ -1,6 +0,0 @@
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
|
||||
import type { RootState, AppDispatch } from "./store"
|
||||
|
||||
// Use throughout your app instead of plain `useDispatch` and `useSelector`
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
@@ -1,3 +0,0 @@
|
||||
/* PLOP_INJECT_IMPORT */
|
||||
|
||||
export /* PLOP_INJECT_EXPORT */ {}
|
||||
@@ -1,9 +0,0 @@
|
||||
const ROUTES = [
|
||||
{
|
||||
path: "/",
|
||||
class: "landing",
|
||||
element: "Home",
|
||||
},
|
||||
]
|
||||
|
||||
export default ROUTES
|
||||
@@ -1,18 +0,0 @@
|
||||
html, body, #root {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {},
|
||||
})
|
||||
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type AppThunk<ReturnType = void> = ThunkAction<
|
||||
ReturnType,
|
||||
RootState,
|
||||
unknown,
|
||||
Action<string>
|
||||
>
|
||||
6
src/app/types/route.d.ts
vendored
6
src/app/types/route.d.ts
vendored
@@ -1,6 +0,0 @@
|
||||
interface RouteConfig {
|
||||
path: String
|
||||
class?: String
|
||||
element: String | JSX.Element | null
|
||||
children?: Array<RouteConfig>
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
class Helper {
|
||||
static populateComponentsInRoutes(routes: RouteConfig[], components: any) {
|
||||
routes &&
|
||||
routes.forEach((route) => {
|
||||
const Component: JSX.ElementType =
|
||||
components[route.element as keyof object]
|
||||
route.element = <Component />
|
||||
if (route.children) {
|
||||
Helper.populateComponentsInRoutes(route.children, components)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Helper
|
||||
@@ -1,19 +1,14 @@
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom/client"
|
||||
import { BrowserRouter } from "react-router-dom"
|
||||
import { Provider } from "react-redux"
|
||||
import { store } from "./app/store"
|
||||
import Router from "./app/Router"
|
||||
import "./app/static/styles/global.scss"
|
||||
import Editor from "@sampadak/ui"
|
||||
import "@armco/components/build/es/index.css"
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<Provider store={store}>
|
||||
<Router />
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
<div className="d-flex flex-column vh-100">
|
||||
<Editor dev isEditable allowFormatting hasFloatingFormatter />
|
||||
</div>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
2
src/react-app-env.d.ts
vendored
2
src/react-app-env.d.ts
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
readonly NODE_ENV: "development" | "production" | "test"
|
||||
readonly NODE_ENV: "development" | "production"
|
||||
readonly PUBLIC_URL: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "packages"],
|
||||
"include": ["src"],
|
||||
"exclude": ["**/build"]
|
||||
}
|
||||
|
||||
42
vite-dev.config.ts
Normal file
42
vite-dev.config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|index).{ts,tsx}")),
|
||||
},
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
20
vite-run.config.ts
Normal file
20
vite-run.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "src/setupTests",
|
||||
mockReset: true,
|
||||
},
|
||||
})
|
||||
@@ -1,20 +1,41 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "src/setupTests",
|
||||
mockReset: true,
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|index).{ts,tsx}")),
|
||||
},
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user