[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:
Proful Sadangi 2021-11-27 04:12:27 +11:00 committed by GitHub
parent e2814943e9
commit 7563575ef5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 28 additions and 377 deletions

View file

@ -2,12 +2,7 @@ import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles' import { strokes, fills, defaultTextStyle } from '~state/shapes/shared/shape-styles'
import { useTldrawApp } from '~hooks' import { useTldrawApp } from '~hooks'
import { import { DMCheckboxItem, DMContent, DMRadioItem } from '~components/Primitives/DropdownMenu'
DMCheckboxItem,
DMContent,
DMRadioItem,
DMTriggerIcon,
} from '~components/Primitives/DropdownMenu'
import { import {
CircleIcon, CircleIcon,
DashDashedIcon, DashDashedIcon,
@ -69,9 +64,13 @@ const ALIGN_ICONS = {
const themeSelector = (s: TDSnapshot) => (s.settings.isDarkMode ? 'dark' : 'light') const themeSelector = (s: TDSnapshot) => (s.settings.isDarkMode ? 'dark' : 'light')
const showTextStylesSelector = (s: TDSnapshot) => { const showTextStylesSelector = (s: TDSnapshot) => {
const pageId = s.appState.currentPageId const { activeTool, currentPageId: pageId } = s.appState
const page = s.document.pages[pageId] 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 { export const StyleMenu = React.memo(function ColorMenu(): JSX.Element {

View file

@ -2917,7 +2917,11 @@ export class TldrawApp extends StateManager<TDSnapshot> {
this.currentTool.onShapeBlur?.() 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[]) => { onRenderCountChange = (ids: string[]) => {
const appState = this.getAppState() const appState = this.getAppState()

View file

@ -31,6 +31,8 @@ export class DrawUtil extends TDShapeUtil<T, E> {
pointCache: Record<string, number[]> = {} pointCache: Record<string, number[]> = {}
canClone = true
getShape = (props: Partial<T>): T => { getShape = (props: Partial<T>): T => {
return Utils.deepMerge<T>( return Utils.deepMerge<T>(
{ {

View file

@ -21,6 +21,8 @@ export class RectangleUtil extends TDShapeUtil<T, E> {
canBind = true canBind = true
canClone = true
getShape = (props: Partial<T>): T => { getShape = (props: Partial<T>): T => {
return Utils.deepMerge<T>( return Utils.deepMerge<T>(
{ {

View file

@ -22,8 +22,12 @@ export class StickyUtil extends TDShapeUtil<T, E> {
canEdit = true canEdit = true
canClone = true
hideResizeHandles = true hideResizeHandles = true
showCloneHandles = true
getShape = (props: Partial<T>): T => { getShape = (props: Partial<T>): T => {
return Utils.deepMerge<T>( return Utils.deepMerge<T>(
{ {

View file

@ -24,6 +24,8 @@ export class TextUtil extends TDShapeUtil<T, E> {
canBind = true canBind = true
canClone = true
getShape = (props: Partial<T>): T => { getShape = (props: Partial<T>): T => {
return Utils.deepMerge<T>( return Utils.deepMerge<T>(
{ {

View file

@ -191,15 +191,15 @@ export class SelectTool extends BaseTool<Status> {
if (key === 'Tab') { if (key === 'Tab') {
if (this.status === Status.Idle && this.app.selectedIds.length === 1) { if (this.status === Status.Idle && this.app.selectedIds.length === 1) {
const [selectedId] = this.app.selectedIds const [selectedId] = this.app.selectedIds
const clonedShape = this.getShapeClone(selectedId, 'right') const clonedShape = this.getShapeClone(selectedId, 'right')
if (clonedShape) { if (clonedShape) {
this.app.createShapes(clonedShape) this.app.createShapes(clonedShape)
this.setStatus(Status.Idle) this.setStatus(Status.Idle)
this.app.setEditingId(clonedShape.id) if (clonedShape.type === TDShapeType.Sticky) {
this.app.select(clonedShape.id) this.app.select(clonedShape.id)
this.app.setEditingId(clonedShape.id)
}
} }
} }

View file

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

View file

@ -3513,16 +3513,6 @@
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== 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": "@use-gesture/core@10.1.5":
version "10.1.5" version "10.1.5"
resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.1.5.tgz#4b956bc8aa6354c20c668930c5cacb16fe4415e4" 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" ansi-styles "^4.1.0"
supports-color "^7.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" version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -10849,11 +10839,6 @@ node-source-walk@^4.0.0, node-source-walk@^4.2.0:
dependencies: dependencies:
"@babel/parser" "^7.0.0" "@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: nodemailer@^6.4.16:
version "6.7.1" version "6.7.1"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.1.tgz#09f72f8b375f7b259291757007bcd902c0174c6e" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.1.tgz#09f72f8b375f7b259291757007bcd902c0174c6e"
@ -12460,15 +12445,6 @@ repeating@^2.0.0:
dependencies: dependencies:
is-finite "^1.0.0" 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: request@^2.88.0:
version "2.88.2" version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
@ -15161,7 +15137,7 @@ yargs@^14.2.2:
y18n "^4.0.0" y18n "^4.0.0"
yargs-parser "^15.0.1" yargs-parser "^15.0.1"
yargs@^17.0.1, yargs@^17.2.1: yargs@^17.0.1:
version "17.2.1" version "17.2.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q== integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==