Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/util/string-manipulation.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,57 @@ export const truncateString = (fullStr, strLen, separator = ' ... ') => {
* @returns { string } - capitalized string
*/
export const capitalize = s => (s?.[0]?.toUpperCase() + s?.slice(1)) || ''

// "vendored" from https://github.com/mdevils/html-entities/blob/68a1a96/src/xml-entities.ts
const decodeXML = (str) => {
const ALPHA_INDEX = {
'&lt': '<',
'&gt': '>',
'&quot': '"',
'&apos': '\'',
'&amp': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': '\'',
'&amp;': '&',
}
if (!str || !str.length) {
return ''
}
return str.replace(/&#?[0-9a-zA-Z]+;?/g, (s) => {
if (s.charAt(1) === '#') {
const code = s.charAt(2).toLowerCase() === 'x' ?
parseInt(s.substr(3), 16) :
parseInt(s.substr(2))

if (isNaN(code) || code < -32768 || code > 65535) {
return ''
}
return String.fromCharCode(code)
}
return ALPHA_INDEX[s] || s
})
}

/**
* https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/50813259#50813259
* getTextSize - calculates a rendered text width and height in rem
* @param { string } text - a text string
* @param { number || string } fontWeight - text's font weight
* @param { number } fontSize - text's font size in pixels
* @param { string } fontFamily - text's font family
* @returns { object } - the width and height of the rendered text in rem
*/
export const getTextSize = (text, fontWeight, fontSize, fontFamily) => {
let font = `${fontWeight} ${fontSize}px ${fontFamily}`
let canvas = document.createElement('canvas')
let context = canvas.getContext('2d')
context.font = font
let metrics = typeof text === 'number'
? context.measureText(text)
: context.measureText(decodeXML(text))
return {
width: metrics.width,
}
}
166 changes: 93 additions & 73 deletions src/view/title-bar/editable-title.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect, useRef } from 'react'

import { Icons, TextField, makeStyles, getTailwindConfigColor } from '@eqworks/lumen-labs'
import { TextField, makeStyles, getTailwindConfigColor } from '@eqworks/lumen-labs'

import { useStoreState, useStoreActions } from '../../store'
import CustomButton from '../../components/custom-button'
import modes from '../../constants/modes'
import { getTextSize } from '../../util/string-manipulation'


const commonClasses = {
Expand All @@ -13,6 +13,24 @@ const commonClasses = {
alignItems: 'center',
margin: '0 0.6rem',
display: 'flex',

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for empty line here, please.

'& .textfield-container': {
backgroundColor: getTailwindConfigColor('secondary-100'),

'& .textfield-root': {
borderTop: 0,

'& .textfield-input': {
fontFamily: 'Open Sans',
outlineWidth: 0,
outlineColor: 'transparent',
},
},

'& .textfield-root:focus-within': {
borderColor: getTailwindConfigColor('primary-500'),
},
},
},
button: {
marginLeft: '0.4rem',
Expand All @@ -29,100 +47,102 @@ const commonClasses = {
},
}

const useStyles = (mode) => makeStyles(
mode === modes.EDITOR
? {
title: {
display: 'flex',
alignItems: 'center',
color: getTailwindConfigColor('secondary-600'),
fontWeight: 700,
background: getTailwindConfigColor('secondary-100'),
fontSize: '0.875rem',
padding: '0.2rem 0.4rem',
paddingLeft: '0.6rem',
cursor: 'default',
},
...commonClasses,
}
: {
title: {
color: getTailwindConfigColor('primary-500'),
fontSize: '1.125rem',
fontWeight: 700,
},
...commonClasses,
}
const useStyles = () => makeStyles(
{
title: {
color: getTailwindConfigColor('primary-500'),
fontSize: '1.125rem',
fontWeight: 700,
},
...commonClasses,
},
)

const textFieldClasses = Object.freeze({
container: 'textfield-container',
root: 'textfield-root',
input: 'textfield-input mb-0 text-sm focus:text-secondary-900',
})

const EditableTitle = () => {
const userUpdate = useStoreActions((actions) => actions.userUpdate)
const isLoading = useStoreState((state) => state.isLoading)
const title = useStoreState((state) => state.title)
const mode = useStoreState((state) => state.ui.mode)

const classes = useStyles(mode)
const classes = useStyles()

const [editing, setEditing] = useState(false)
const [tentativeTitle, setTentativeTitle] = useState(title)

const textfieldRef = useRef(null)

useEffect(() => {
setTentativeTitle(title)
}, [title])

const renderEditButton = (
<CustomButton
horizontalMargin
className={classes.button}
type='secondary'
onClick={() => setEditing(true)}
endIcon={<Icons.Edit size="md" />}
/>
)

const renderTitle = (
<div className={classes.title} >
<div className={`edit-title-class ${classes.title}`} >
{isLoading ? '...' : title}
{
(mode === modes.QL || mode === modes.EDITOR) &&
renderEditButton
}
</div >
)

const renderTextfield = () => {
if (textfieldRef.current) {
let el = textfieldRef.current.childNodes[0][0]
const lengthSize = el.value.length > 12 ? 14 : 16
el.style.width = `${getTextSize(el.value, 700, lengthSize, 'Open Sans').width}px`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kc-leung could you determine the font family from the 'el' component and use it instead of Open Sans? I believe widget-studio uses this font in its storybook, otherwise it will probably just use whatever font is in use for the application where it is plugged in. From what I see, the design team uses PT Sans for widget-studio design, which is also in use at the moment in the snoke-dashboard. Open Sans seems to be the fallback font.

body {
  font-family: "PT Sans", "Open Sans", sans-serif;
  ...
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we make the font family for the title Open Sans or PT Sans? sally's figma link is using Open Sans

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we make the font family for the title Open Sans or PT Sans? sally's figma link is using Open Sans

@kc-leung Sorry, just saw this. I don't think you should set it to any font family. I believe the Widget Studio will just use whatever font family is in use in the application is plugged in. The only place we setup the font family is for the storybook.
The current Figma design for the Widget Studio uses PT Sans. The dashboard also uses PT Sans as the primary font at the moment.
@hyx131 @YizhiCatherineZhang what do you think?

Copy link
Contributor

@geoerika geoerika Aug 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kc look how to determine the whole font of the element at the link I previously sent. Try use all the functions there, getCssStyle, getCanvasFont & modify getTextSize to receive only 2 params, text & font.
https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
Screen Shot 2022-08-26 at 4 45 28 PM

Copy link
Contributor

@geoerika geoerika Aug 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow, there are still some inconsistencies with the length calculated here, in WS, which I don't understand where they come from. This function works perfectly for my in chart-system.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geoerika getting dynamically the font-size is not enough since some letters are smaller it is very inconsistent. I rather have it a bit larger than cutting off the title

}

return (
<TextField
variant='borderless'
classes={textFieldClasses}
value={tentativeTitle}
onChange={(v) => {
setTentativeTitle(v)
}}
onBlur={(e) => {
e.preventDefault()
e.stopPropagation()
e.nativeEvent.preventDefault()
e.nativeEvent.stopPropagation()
setEditing(false)
}}
onFocus={(e) => {
e.preventDefault()
e.stopPropagation()
e.nativeEvent.preventDefault()
e.nativeEvent.stopPropagation()
setEditing(true)
}}
deleteButton={editing}
onDelete={(e) => {
e.preventDefault()
e.stopPropagation()
e.nativeEvent.preventDefault()
e.nativeEvent.stopPropagation()
updateTitle('')
}}
/>
)
}

const updateTitle = title => userUpdate({ title })

return (
<div className={classes.outerContainer}>
<div
ref={textfieldRef}
className={classes.outerContainer}
>
{
editing
? <form
action='.'
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
e.nativeEvent.preventDefault()
e.nativeEvent.stopPropagation()
updateTitle(e.target.children[0].children[0].value)
setEditing(false)
}}
>
<TextField
autoFocus
size='lg'
value={tentativeTitle}
onChange={(v) => setTentativeTitle(v)}
onSubmit={(e) => {
e.nativeEvent.preventDefault()
e.nativeEvent.stopPropagation()
}}
onBlur={(e) => {
updateTitle(e.target.value)
setTentativeTitle(title)
setEditing(false)
}}
/>
</form>
: <>
mode === modes.EDITOR
?
<>
{renderTextfield()}
</>
:
<>
{renderTitle}
</>
}
Expand Down