[Bug] Added visibility of clone tool (#381)
* [bug] Added display of clone buttons * Reverted changes from sticky notes * feat show those style options when the text tool is selected * Add clone handles to sticky Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
parent
e2814943e9
commit
7563575ef5
9 changed files with 28 additions and 377 deletions
|
@ -2,12 +2,7 @@ import * as React from 'react'
|
|||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles'
|
||||
import { useTldrawApp } from '~hooks'
|
||||
import {
|
||||
DMCheckboxItem,
|
||||
DMContent,
|
||||
DMRadioItem,
|
||||
DMTriggerIcon,
|
||||
} from '~components/Primitives/DropdownMenu'
|
||||
import { DMCheckboxItem, DMContent, DMRadioItem } from '~components/Primitives/DropdownMenu'
|
||||
import {
|
||||
CircleIcon,
|
||||
DashDashedIcon,
|
||||
|
@ -69,9 +64,13 @@ const ALIGN_ICONS = {
|
|||
const themeSelector = (s: TDSnapshot) => (s.settings.isDarkMode ? 'dark' : 'light')
|
||||
|
||||
const showTextStylesSelector = (s: TDSnapshot) => {
|
||||
const pageId = s.appState.currentPageId
|
||||
const { activeTool, currentPageId: pageId } = s.appState
|
||||
const page = s.document.pages[pageId]
|
||||
return s.document.pageStates[pageId].selectedIds.some((id) => 'text' in page.shapes[id])
|
||||
|
||||
return (
|
||||
activeTool === 'text' ||
|
||||
s.document.pageStates[pageId].selectedIds.some((id) => 'text' in page.shapes[id])
|
||||
)
|
||||
}
|
||||
|
||||
export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {
|
||||
|
|
|
@ -2917,7 +2917,11 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||
this.currentTool.onShapeBlur?.()
|
||||
}
|
||||
|
||||
onShapeClone: TLShapeCloneHandler = (info, e) => this.currentTool.onShapeClone?.(info, e)
|
||||
onShapeClone: TLShapeCloneHandler = (info, e) => {
|
||||
this.originPoint = this.getPagePoint(info.point)
|
||||
this.updateInputs(info, e)
|
||||
this.currentTool.onShapeClone?.(info, e)
|
||||
}
|
||||
|
||||
onRenderCountChange = (ids: string[]) => {
|
||||
const appState = this.getAppState()
|
||||
|
|
|
@ -31,6 +31,8 @@ export class DrawUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
pointCache: Record<string, number[]> = {}
|
||||
|
||||
canClone = true
|
||||
|
||||
getShape = (props: Partial<T>): T => {
|
||||
return Utils.deepMerge<T>(
|
||||
{
|
||||
|
|
|
@ -21,6 +21,8 @@ export class RectangleUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
canBind = true
|
||||
|
||||
canClone = true
|
||||
|
||||
getShape = (props: Partial<T>): T => {
|
||||
return Utils.deepMerge<T>(
|
||||
{
|
||||
|
|
|
@ -22,8 +22,12 @@ export class StickyUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
canEdit = true
|
||||
|
||||
canClone = true
|
||||
|
||||
hideResizeHandles = true
|
||||
|
||||
showCloneHandles = true
|
||||
|
||||
getShape = (props: Partial<T>): T => {
|
||||
return Utils.deepMerge<T>(
|
||||
{
|
||||
|
|
|
@ -24,6 +24,8 @@ export class TextUtil extends TDShapeUtil<T, E> {
|
|||
|
||||
canBind = true
|
||||
|
||||
canClone = true
|
||||
|
||||
getShape = (props: Partial<T>): T => {
|
||||
return Utils.deepMerge<T>(
|
||||
{
|
||||
|
|
|
@ -191,15 +191,15 @@ export class SelectTool extends BaseTool<Status> {
|
|||
if (key === 'Tab') {
|
||||
if (this.status === Status.Idle && this.app.selectedIds.length === 1) {
|
||||
const [selectedId] = this.app.selectedIds
|
||||
|
||||
const clonedShape = this.getShapeClone(selectedId, 'right')
|
||||
|
||||
if (clonedShape) {
|
||||
this.app.createShapes(clonedShape)
|
||||
|
||||
this.setStatus(Status.Idle)
|
||||
this.app.setEditingId(clonedShape.id)
|
||||
this.app.select(clonedShape.id)
|
||||
if (clonedShape.type === TDShapeType.Sticky) {
|
||||
this.app.select(clonedShape.id)
|
||||
this.app.setEditingId(clonedShape.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
338
replace-paths.ts
338
replace-paths.ts
|
@ -1,338 +0,0 @@
|
|||
#! /usr/bin/env node
|
||||
|
||||
import * as program from 'commander'
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
||||
import { sync } from 'globby'
|
||||
import { dirname, relative, resolve } from 'path'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import JSON5 from 'json5'
|
||||
|
||||
/*
|
||||
"baseUrl": ".",
|
||||
"outDir": "lib",
|
||||
"paths": {
|
||||
"src/*": ["src/*"]
|
||||
},
|
||||
*/
|
||||
|
||||
export interface IRawTSConfig {
|
||||
extends?: string
|
||||
compilerOptions?: {
|
||||
baseUrl?: string
|
||||
outDir?: string
|
||||
rootDir?: string
|
||||
paths?: { [key: string]: string[] }
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITSConfig {
|
||||
baseUrl?: string
|
||||
outDir?: string
|
||||
rootDir?: string
|
||||
compilerOptions?: Record<string, unknown>
|
||||
paths?: { [key: string]: string[] }
|
||||
}
|
||||
|
||||
export const mapPaths = (
|
||||
paths: { [key: string]: string[] },
|
||||
mapper: (x: string) => string
|
||||
): { [key: string]: string[] } => {
|
||||
const dest = {} as { [key: string]: string[] }
|
||||
Object.keys(paths).forEach((key) => {
|
||||
dest[key] = paths[key].map(mapper)
|
||||
})
|
||||
return dest
|
||||
}
|
||||
|
||||
export const loadConfig = (file: string): ITSConfig => {
|
||||
const fileToParse = fs.readFileSync(file)
|
||||
|
||||
const parsedJsonFile = JSON5.parse(fileToParse as unknown as string)
|
||||
|
||||
const {
|
||||
extends: extendsPath,
|
||||
compilerOptions: { baseUrl, outDir, rootDir, paths } = {
|
||||
baseUrl: undefined,
|
||||
outDir: undefined,
|
||||
rootDir: undefined,
|
||||
paths: undefined,
|
||||
},
|
||||
} = parsedJsonFile as IRawTSConfig
|
||||
|
||||
const config: ITSConfig = {}
|
||||
if (baseUrl) {
|
||||
config.baseUrl = baseUrl
|
||||
}
|
||||
if (outDir) {
|
||||
config.outDir = outDir
|
||||
}
|
||||
if (rootDir) {
|
||||
config.rootDir = rootDir
|
||||
}
|
||||
if (paths) {
|
||||
config.paths = paths
|
||||
}
|
||||
if (extendsPath) {
|
||||
const childConfigDirPath = path.dirname(file)
|
||||
const parentConfigPath = path.resolve(childConfigDirPath, extendsPath)
|
||||
const parentConfigDirPath = path.dirname(parentConfigPath)
|
||||
const currentExtension = path.extname(parentConfigPath)
|
||||
|
||||
let parentExtendedConfigFile = path.format({
|
||||
name: parentConfigPath,
|
||||
ext: currentExtension === '' ? '.json' : '',
|
||||
})
|
||||
|
||||
/* Ensure without a doubt there's no double extension */
|
||||
if (/\.json\.json$/.test(parentExtendedConfigFile)) {
|
||||
parentExtendedConfigFile = parentExtendedConfigFile.replace(/\.json\.json$/, '.json')
|
||||
}
|
||||
|
||||
const parentConfig = loadConfig(parentExtendedConfigFile)
|
||||
|
||||
if (parentConfig.baseUrl) {
|
||||
parentConfig.baseUrl = path.resolve(parentConfigDirPath, parentConfig.baseUrl)
|
||||
}
|
||||
|
||||
return {
|
||||
...parentConfig,
|
||||
...config,
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
program
|
||||
.version('0.0.1')
|
||||
.option('-p, --project <file>', 'path to tsconfig.json')
|
||||
.option('-s, --src <path>', 'source root path')
|
||||
.option('-o, --out <path>', 'output root path')
|
||||
.option('-v, --verbose', 'output logs')
|
||||
|
||||
program.on('--help', () => {
|
||||
console.log(`
|
||||
$ tscpath -p tsconfig.json
|
||||
`)
|
||||
})
|
||||
|
||||
program.parse(process.argv)
|
||||
|
||||
const {
|
||||
out: flagOut,
|
||||
project = 'tsconfig.json',
|
||||
src: flagSrc,
|
||||
verbose = false,
|
||||
} = program as {
|
||||
out?: string | undefined
|
||||
project?: string
|
||||
src?: string | undefined
|
||||
verbose?: boolean
|
||||
}
|
||||
|
||||
const verboseLog = (...args: any[]): void => {
|
||||
if (verbose) {
|
||||
console.log(...args)
|
||||
}
|
||||
}
|
||||
|
||||
const configFile = resolve(process.cwd(), project)
|
||||
|
||||
const rootDir = resolve(process.cwd())
|
||||
|
||||
verboseLog(`Using tsconfig: ${configFile}`)
|
||||
|
||||
const exitingErr = (): any => {
|
||||
throw new Error('--- exiting tsconfig-replace-paths due to parameters missing ---')
|
||||
}
|
||||
|
||||
const missingConfigErr = (property: string): any => {
|
||||
console.error(`Whoops! Please set ${property} in your tsconfig or supply a flag`)
|
||||
exitingErr()
|
||||
}
|
||||
|
||||
const missingDirectoryErr = (directory: string, flag: string): any => {
|
||||
console.error(
|
||||
`Whoops! ${directory} must be specified in your project => --project ${project}, or flagged with directory => ${flag} './path'`
|
||||
)
|
||||
exitingErr()
|
||||
}
|
||||
|
||||
// Imported the TS Config
|
||||
const returnedTsConfig = loadConfig(configFile)
|
||||
|
||||
// Destructure only the necessary keys, and rename to give context
|
||||
const {
|
||||
baseUrl,
|
||||
paths,
|
||||
outDir: tsConfigOutDir = '',
|
||||
rootDir: tsConfigRootDir = rootDir,
|
||||
} = returnedTsConfig
|
||||
|
||||
// If no flagSrc or tsConfigRootDir, error
|
||||
if (!flagSrc && tsConfigRootDir === '') {
|
||||
missingConfigErr('compilerOptions.rootDir')
|
||||
}
|
||||
|
||||
// If no flagOut or tsConfigOutDir, error
|
||||
if (!flagOut && tsConfigOutDir === '') {
|
||||
missingConfigErr('compilerOptions.outDir')
|
||||
}
|
||||
|
||||
// Are we going to use the flag or ts config for src?
|
||||
let usingSrcDir: string
|
||||
if (flagSrc) {
|
||||
verboseLog('Using flag --src')
|
||||
usingSrcDir = resolve(flagSrc)
|
||||
} else {
|
||||
verboseLog('Using compilerOptions.rootDir from your tsconfig')
|
||||
usingSrcDir = resolve(tsConfigRootDir)
|
||||
}
|
||||
if (!usingSrcDir) {
|
||||
missingDirectoryErr('rootDir', '--src')
|
||||
}
|
||||
|
||||
// Log which src is being used
|
||||
verboseLog(`Using src: ${usingSrcDir}`)
|
||||
|
||||
// Are we going to use the flag or ts config for out?
|
||||
let usingOutDir: string
|
||||
if (flagOut) {
|
||||
verboseLog('Using flag --out')
|
||||
usingOutDir = resolve(flagOut)
|
||||
} else {
|
||||
verboseLog('Using compilerOptions.outDir from your tsconfig')
|
||||
usingOutDir = resolve(tsConfigOutDir)
|
||||
}
|
||||
if (!usingOutDir) {
|
||||
missingDirectoryErr('outDir', '--out')
|
||||
}
|
||||
|
||||
// Log which out is being used
|
||||
verboseLog(`Using out: ${usingOutDir}`)
|
||||
|
||||
if (!baseUrl) {
|
||||
throw new Error('compilerOptions.baseUrl is not set')
|
||||
}
|
||||
if (!paths) {
|
||||
throw new Error('compilerOptions.paths is not set')
|
||||
}
|
||||
if (!usingOutDir) {
|
||||
throw new Error('compilerOptions.outDir is not set')
|
||||
}
|
||||
if (!usingSrcDir) {
|
||||
throw new Error('compilerOptions.rootDir is not set')
|
||||
}
|
||||
|
||||
verboseLog(`baseUrl: ${baseUrl}`)
|
||||
verboseLog(`rootDir: ${usingSrcDir}`)
|
||||
verboseLog(`outDir: ${usingOutDir}`)
|
||||
verboseLog(`paths: ${JSON.stringify(paths, null, 2)}`)
|
||||
|
||||
const configDir = dirname(configFile)
|
||||
|
||||
const basePath = resolve(configDir, baseUrl)
|
||||
verboseLog(`basePath: ${basePath}`)
|
||||
|
||||
const outPath = usingOutDir || resolve(basePath, usingOutDir)
|
||||
verboseLog(`outPath: ${outPath}`)
|
||||
|
||||
const outFileToSrcFile = (x: string): string => resolve(usingSrcDir, relative(outPath, x))
|
||||
|
||||
const aliases = Object.keys(paths)
|
||||
.filter((path) => path.startsWith('~'))
|
||||
.map((alias) => ({
|
||||
prefix: alias.replace(/\*$/, ''),
|
||||
aliasPaths: paths[alias as keyof typeof paths].map((p) =>
|
||||
resolve(basePath, p.replace(/\*$/, ''))
|
||||
),
|
||||
}))
|
||||
.filter(({ prefix }) => prefix)
|
||||
verboseLog(`aliases: ${JSON.stringify(aliases, null, 2)}`)
|
||||
|
||||
const toRelative = (from: string, x: string): string => {
|
||||
const rel = relative(from, x)
|
||||
return (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
const exts = ['.js', '.jsx', '.ts', '.tsx', '.d.ts', '.json']
|
||||
|
||||
let replaceCount = 0
|
||||
|
||||
const absToRel = (modulePath: string, outFile: string): string => {
|
||||
const alen = aliases.length
|
||||
|
||||
for (let j = 0; j < alen; j += 1) {
|
||||
const { prefix, aliasPaths } = aliases[j]
|
||||
|
||||
if (modulePath.startsWith(prefix)) {
|
||||
const modulePathRel = modulePath.substring(prefix.length)
|
||||
const srcFile = outFileToSrcFile(outFile)
|
||||
const outRel = relative(basePath, outFile)
|
||||
|
||||
verboseLog(`${outRel} (source: ${relative(basePath, srcFile)}):`)
|
||||
verboseLog(`\timport '${modulePath}'`)
|
||||
|
||||
const len = aliasPaths.length
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
const apath = aliasPaths[i]
|
||||
const moduleSrc = resolve(apath, modulePathRel)
|
||||
if (existsSync(moduleSrc) || exts.some((ext) => existsSync(moduleSrc + ext))) {
|
||||
const rel = toRelative(dirname(srcFile), moduleSrc)
|
||||
|
||||
replaceCount += 1
|
||||
|
||||
verboseLog(
|
||||
`\treplacing '${modulePath}' -> '${rel}' referencing ${relative(basePath, moduleSrc)}`
|
||||
)
|
||||
return rel
|
||||
}
|
||||
}
|
||||
verboseLog(`\tcould not replace ${modulePath}`)
|
||||
}
|
||||
}
|
||||
|
||||
return modulePath
|
||||
}
|
||||
|
||||
const requireRegex = /(?:import|require)\(['"]([^'"]*)['"]\)/g
|
||||
const importRegex = /(?:import|from) ['"]([^'"]*)['"]/g
|
||||
|
||||
const replaceImportStatement = (orig: string, matched: string, outFile: string): string => {
|
||||
const index = orig.indexOf(matched)
|
||||
return (
|
||||
orig.substring(0, index) + absToRel(matched, outFile) + orig.substring(index + matched.length)
|
||||
)
|
||||
}
|
||||
|
||||
const replaceAlias = (text: string, outFile: string): string =>
|
||||
text
|
||||
.replace(requireRegex, (orig, matched) => replaceImportStatement(orig, matched, outFile))
|
||||
.replace(importRegex, (orig, matched) => replaceImportStatement(orig, matched, outFile))
|
||||
|
||||
// import relative to absolute path
|
||||
const files = sync(`${outPath}/**/*.{js,jsx,ts,tsx}`, {
|
||||
dot: true,
|
||||
noDir: true,
|
||||
} as any).map((x) => resolve(x))
|
||||
|
||||
let changedFileCount = 0
|
||||
|
||||
const flen = files.length
|
||||
let count = 0
|
||||
|
||||
for (let i = 0; i < flen; i += 1) {
|
||||
const file = files[i]
|
||||
const text = readFileSync(file, 'utf8')
|
||||
const prevReplaceCount = replaceCount
|
||||
const newText = replaceAlias(text, file)
|
||||
if (text !== newText) {
|
||||
changedFileCount += 1
|
||||
verboseLog(`${file}: replaced ${replaceCount - prevReplaceCount} paths`)
|
||||
writeFileSync(file, newText, 'utf8')
|
||||
count = count + 1
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Replaced ${replaceCount} paths in ${changedFileCount} files`)
|
28
yarn.lock
28
yarn.lock
|
@ -3513,16 +3513,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
|
||||
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
|
||||
|
||||
"@uqt/ts-path-replace@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@uqt/ts-path-replace/-/ts-path-replace-1.1.1.tgz#c753fe92aa55575e7249c9defc908e311e37b93d"
|
||||
integrity sha512-6OBpyAJRDAeneM1m8drI4e7P71kyoACdPVN5OMjsTmzT9becSZz95KVSRRQyljdDSCUPhdGzxOi5iBGp/eBYPQ==
|
||||
dependencies:
|
||||
lodash.merge "^4.6.2"
|
||||
minimist "^1.2.5"
|
||||
node-watch "^0.7.1"
|
||||
replace-in-file "^6.1.0"
|
||||
|
||||
"@use-gesture/core@10.1.5":
|
||||
version "10.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.1.5.tgz#4b956bc8aa6354c20c668930c5cacb16fe4415e4"
|
||||
|
@ -4745,7 +4735,7 @@ chalk@^3.0.0:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
|
||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
|
@ -10849,11 +10839,6 @@ node-source-walk@^4.0.0, node-source-walk@^4.2.0:
|
|||
dependencies:
|
||||
"@babel/parser" "^7.0.0"
|
||||
|
||||
node-watch@^0.7.1:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.7.2.tgz#545f057da8500487eb8287adcb4cb5a7338d7e21"
|
||||
integrity sha512-g53VjSARRv1JdST0LZRIg8RiuLr1TaBbVPsVvxh0/0Ymvi0xYUjDuoqQQAWtHJQUXhiShowPT/aXKNeHBcyQsw==
|
||||
|
||||
nodemailer@^6.4.16:
|
||||
version "6.7.1"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.1.tgz#09f72f8b375f7b259291757007bcd902c0174c6e"
|
||||
|
@ -12460,15 +12445,6 @@ repeating@^2.0.0:
|
|||
dependencies:
|
||||
is-finite "^1.0.0"
|
||||
|
||||
replace-in-file@^6.1.0:
|
||||
version "6.3.2"
|
||||
resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-6.3.2.tgz#0f19835137177c89932f45df319f3539a019484f"
|
||||
integrity sha512-Dbt5pXKvFVPL3WAaEB3ZX+95yP0CeAtIPJDwYzHbPP5EAHn+0UoegH/Wg3HKflU9dYBH8UnBC2NvY3P+9EZtTg==
|
||||
dependencies:
|
||||
chalk "^4.1.2"
|
||||
glob "^7.2.0"
|
||||
yargs "^17.2.1"
|
||||
|
||||
request@^2.88.0:
|
||||
version "2.88.2"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||
|
@ -15161,7 +15137,7 @@ yargs@^14.2.2:
|
|||
y18n "^4.0.0"
|
||||
yargs-parser "^15.0.1"
|
||||
|
||||
yargs@^17.0.1, yargs@^17.2.1:
|
||||
yargs@^17.0.1:
|
||||
version "17.2.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
|
||||
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
|
||||
|
|
Loading…
Reference in a new issue