Upgrade web build to vite, move vega back up to 5+

This commit is contained in:
Nathan Evans 2022-02-04 18:02:42 -08:00
Родитель 31d5aa1dae
Коммит c90b1f9746
107 изменённых файлов: 5325 добавлений и 9102 удалений

5468
.pnp.cjs сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -17,8 +17,7 @@
"test:ci": "jest --coverage",
"git-is-clean": "essex git-is-clean",
"ci": "run-s lint: build: bundle: test:ci git-is-clean",
"release": "run-s clean: build: publish:",
"start": "yarn start:webapp"
"release": "run-s clean: build: publish:"
},
"workspaces": {
"packages": [

Просмотреть файл

@ -15,8 +15,7 @@
},
"scripts": {
"clean": "essex clean",
"build": "essex build",
"start": "essex watch"
"build": "essex build"
},
"dependencies": {
"chroma-js": "^2.1.2",

Просмотреть файл

@ -19,8 +19,7 @@
"build_lib": "essex build",
"copy_json_esm": "ncp src/themes dist/esm/themes",
"copy_json_cjs": "ncp src/themes dist/cjs/themes",
"assets": "run-p copy_json_esm copy_json_cjs",
"start": "essex watch"
"assets": "run-p copy_json_esm copy_json_cjs"
},
"dependencies": {
"@thematic/color": "workspace:packages/color",

Просмотреть файл

@ -15,8 +15,7 @@
},
"scripts": {
"clean": "essex clean",
"build": "essex build",
"start": "essex watch"
"build": "essex build"
},
"dependencies": {
"@thematic/core": "workspace:packages/core",

Просмотреть файл

@ -15,8 +15,7 @@
},
"scripts": {
"clean": "essex clean",
"build": "essex build",
"start": "essex watch"
"build": "essex build"
},
"dependencies": {
"@essex-js-toolkit/hooks": "^1.1.5",

Просмотреть файл

@ -15,8 +15,7 @@
},
"scripts": {
"clean": "essex clean",
"build": "essex build",
"start": "essex watch"
"build": "essex build"
},
"dependencies": {
"@thematic/color": "workspace:packages/color",

Просмотреть файл

@ -15,8 +15,7 @@
},
"scripts": {
"clean": "essex clean",
"build": "essex build",
"start": "essex watch"
"build": "essex build"
},
"dependencies": {
"@thematic/core": "workspace:packages/core",
@ -25,10 +24,10 @@
"devDependencies": {
"@essex/scripts": "^19.0.5",
"@types/node": "^14.18.5",
"vega": "^5.21.0"
"vega": "5.15.0"
},
"peerDependencies": {
"vega": ">= 5.13.0"
"vega": "5"
},
"peerDependenciesMeta": {
"vega": {

Просмотреть файл

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<link
href="https://fonts.googleapis.com/css?family=Gochi Hand:300,400,700"
rel="stylesheet"
type="text/css"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Thematic</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

Просмотреть файл

@ -6,10 +6,10 @@
"private": true,
"license": "MIT",
"scripts": {
"start": "webpack-dev-server",
"start:webapp": "yarn start",
"bundle": "webpack",
"clean": "essex clean build"
"clean": "essex clean build",
"build": "tsc && vite build",
"start": "vite",
"start:webapp": "yarn start"
},
"repository": {
"type": "git",
@ -36,11 +36,11 @@
"redux-actions": "^2.6.5",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.4.1",
"vega": "^4"
"vega": "5.15.0"
},
"devDependencies": {
"@essex/scripts": "^19.0.5",
"@essex/webpack-config": "^19.0.2",
"@essex/vite-config": "^18.0.2",
"@types/d3-axis": "^1.0.16",
"@types/d3-scale": "^2.2.6",
"@types/d3-selection": "^1.4.3",
@ -52,11 +52,7 @@
"@types/redux-actions": "^2.6.2",
"@types/redux-logger": "^3.0.9",
"@types/redux-thunk": "^2.1.32",
"raw-loader": "^4.0.2",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2",
"worker-loader": "^2.0.0"
"vite": "^2.7.10"
},
"browserslist": [
">0.2%",

13
packages/webapp/src/App.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Theme } from '@thematic/core'
import './App.css'
export interface AppProps {
theme: Theme
}
export declare const App: import('react-redux').ConnectedComponent<
({ theme }: AppProps) => JSX.Element,
import('react-redux').Omit<AppProps, 'theme'>
>

Просмотреть файл

@ -0,0 +1,71 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
import { ThematicFluentProvider } from '@thematic/fluent'
import { ApplicationStyles } from '@thematic/react'
import { connect } from 'react-redux'
import { ControlPanel } from './components/ControlPanel'
import { ThemeEditor } from './components/ThemeEditor'
import { ThemeViewer } from './components/ThemeViewer'
import './App.css'
const AppComponent = ({ theme }) =>
_jsxs(
ThematicFluentProvider,
{
theme: theme,
children: [
_jsx(ApplicationStyles, {}, void 0),
_jsxs(
'div',
{
className: 'App',
children: [
_jsx(
'div',
{
className: 'controls',
style: {
backgroundColor: theme.application().faint().hex(),
borderBottom: `1px solid ${theme
.application()
.border()
.hex()}`,
},
children: _jsx(ControlPanel, {}, void 0),
},
void 0,
),
_jsxs(
'div',
{
className: 'main',
children: [
_jsx(
'div',
{
className: 'editor',
children: _jsx(ThemeEditor, {}, void 0),
},
void 0,
),
_jsx(
'div',
{
className: 'viewer',
children: _jsx(ThemeViewer, {}, void 0),
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
export const App = connect(state => ({
theme: state.theme,
}))(AppComponent)

Просмотреть файл

@ -0,0 +1,7 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import './index.css'
import { FC } from 'react'
export declare const ApplicationPalette: FC

Просмотреть файл

@ -0,0 +1,87 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import './index.css'
import { useThematic } from '@thematic/react'
import { useMemo } from 'react'
import { ColorStrip } from '../ColorStrip'
const applicationPrimaryKeys = ['accent', 'foreground', 'background', 'border']
const applicationSignalKeys = ['success', 'warning', 'error']
const applicationLowKeys = ['faint', 'lowContrast', 'lowMidContrast']
const applicationHighKeys = ['midContrast', 'midHighContrast', 'highContrast']
export const ApplicationPalette = () => {
const theme = useThematic()
const primaries = useColors(applicationPrimaryKeys, theme)
const signals = useColors(applicationSignalKeys, theme)
const secondaries = useColors(applicationLowKeys, theme)
const elements = useColors(applicationHighKeys, theme)
const styles = {
swatch: {
border: `1px solid ${theme.application().border().hex()}`,
},
label: {
width: 80,
},
}
return _jsxs(
'div',
{
className: 'application-palette',
children: [
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: primaries,
swatchStyle: styles.swatch,
labelStyle: styles.label,
},
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: signals,
swatchStyle: styles.swatch,
labelStyle: styles.label,
},
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: secondaries,
swatchStyle: styles.swatch,
labelStyle: styles.label,
},
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: elements,
swatchStyle: styles.swatch,
labelStyle: styles.label,
},
void 0,
),
],
},
void 0,
)
}
function useColors(keys, theme) {
return useMemo(() => {
const app = theme.application()
return keys.map(key => ({
color: app[key]().hex(),
label: key,
secondaryLabel: app[key]().hex(),
}))
}, [keys, theme])
}

12
packages/webapp/src/components/ColorPalette/ColorBand.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,12 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { FC } from 'react'
export interface ColorBandProps {
colors: string[]
foreground: string
label: string
width: number
}
export declare const ColorBand: FC<ColorBandProps>

Просмотреть файл

@ -0,0 +1,63 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
export const ColorBand = ({ colors, foreground, label, width }) => {
// hard-coded 83 is the label width + (2 * padding) + (2 * margin) + (2 * band margin)
// 65 + 10 + 4 + 4
const slice = width ? (width - 83) / colors.length : 0
const colorBlocks = colors.map((color, index) => {
return _jsx(
'div',
{
style: {
width: slice,
flexShrink: 0,
background: color,
height: 30,
margin: 0,
padding: 0,
},
},
`Color-${index}`,
)
})
return _jsxs(
'div',
{
style: {
display: 'flex',
},
children: [
_jsx(
'div',
{
style: {
width: 65,
flexShrink: 0,
height: 20,
lineHeight: '20px',
color: foreground,
textAlign: 'right',
margin: 2,
padding: 5,
},
children: label,
},
void 0,
),
_jsx(
'div',
{
style: {
margin: 2,
display: 'flex',
flexFlow: 'row',
flexWrap: 'wrap',
},
children: colorBlocks,
},
void 0,
),
],
},
void 0,
)
}

13
packages/webapp/src/components/ColorPalette/ColorStrip.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { FC } from 'react'
export interface ColorStripProps {
colors: string[]
labelColors?: string[]
foreground: string
background: string
label: string
}
export declare const ColorStrip: FC<ColorStripProps>

Просмотреть файл

@ -0,0 +1,70 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
export const ColorStrip = ({
colors,
labelColors,
foreground,
background,
label,
}) => {
const colorBlocks = colors.map((color, index) => {
return _jsx(
'div',
{
title: `${label} color ${color}`,
style: {
width: 50,
flexShrink: 0,
background: color,
height: 20,
lineHeight: '20px',
color: labelColors != null ? labelColors[index] : background,
textAlign: 'center',
margin: 2,
padding: 5,
},
children: color,
},
`Color-${index}`,
)
})
return _jsxs(
'div',
{
style: {
display: 'flex',
},
children: [
_jsx(
'div',
{
style: {
width: 65,
flexShrink: 0,
height: 20,
lineHeight: '20px',
color: foreground,
textAlign: 'right',
margin: 2,
padding: 5,
},
children: label,
},
void 0,
),
_jsx(
'div',
{
style: {
display: 'flex',
flexFlow: 'row',
flexWrap: 'wrap',
},
children: colorBlocks,
},
void 0,
),
],
},
void 0,
)
}

8
packages/webapp/src/components/ColorPalette/Contrast.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
import { FC } from 'react'
export interface ContrastProps {
foreground: string
background: string
error: string
showLink?: boolean
}
export declare const Contrast: FC<ContrastProps>

Просмотреть файл

@ -0,0 +1,54 @@
import {
jsx as _jsx,
Fragment as _Fragment,
jsxs as _jsxs,
} from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { IconButton } from '@fluentui/react'
import { contrast } from '@thematic/color'
const WCAG = 4.5
const ICON_SIZE = '0.7em'
const NOTE = 'WCAG guidelines recommend a minimum contrast ratio of 4.5:1'
export const Contrast = ({ foreground, background, error, showLink }) => {
const c = contrast(foreground, background)
const style = {}
const low = c < WCAG
if (low) {
style.color = error
}
const display = Math.round(c * 100) / 100
const buttonStyles = {
root: {
width: ICON_SIZE,
height: ICON_SIZE,
},
icon: {
fontSize: ICON_SIZE,
},
}
return _jsxs(
_Fragment,
{
children: [
_jsx('span', { style: style, title: NOTE, children: display }, void 0),
showLink && low
? _jsx(
IconButton,
{
iconProps: { iconName: 'NavigateExternalInline' },
styles: buttonStyles,
href: 'https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast',
title: NOTE,
target: '_blank',
},
void 0,
)
: null,
],
},
void 0,
)
}

5
packages/webapp/src/components/ColorPalette/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
import { FC } from 'react'
export interface ColorPaletteProps {
scaleItemCount: number
}
export declare const ColorPalette: FC<ColorPaletteProps>

Просмотреть файл

@ -0,0 +1,258 @@
import { jsxs as _jsxs, jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { mark2style, useThematic } from '@thematic/react'
import { Fragment } from 'react'
import { ApplicationPalette } from './ApplicationPalette'
import { ColorBand } from './ColorBand'
import { ColorStrip } from './ColorStrip'
import { Contrast } from './Contrast'
// this is the same number we use under the hood when constructing the continuous scales,
// so it makes sense to use here and potentially allow the gradation bands to be visible,
// because that's how the actual encodings will work. for many of the mid-range interpolated
// values there can be a duplicate color at this level, so we know it is fine enough to cover
// what the scale puts forth
const BAND_SLICES = 100
const BAND_WIDTH = 720
export const ColorPalette = ({ scaleItemCount }) => {
const theme = useThematic()
const accentColor = theme.application().accent().hex()
const foregroundColor = theme.application().foreground().hex()
const backgroundColor = theme.application().background().hex()
const errorColor = theme.application().error().hex()
const accent = _jsx(
Fragment,
{
children: _jsxs(
'span',
{ style: { color: accentColor }, children: ['Accent ', accentColor] },
void 0,
),
},
void 0,
)
const background = _jsx(
Fragment,
{
children: _jsxs(
'span',
{
style: { color: foregroundColor },
children: ['Background ', backgroundColor],
},
void 0,
),
},
void 0,
)
const foreground = _jsx(
Fragment,
{
children: _jsxs(
'span',
{
style: { color: foregroundColor },
children: ['Foreground ', foregroundColor],
},
void 0,
),
},
void 0,
)
const bandDomain = [0, BAND_SLICES - 1]
const nominal = theme.scales().nominal(scaleItemCount).toArray()
const nominalBold = theme.scales().nominalBold(scaleItemCount).toArray()
const nominalMuted = theme.scales().nominalMuted(scaleItemCount).toArray()
const sequential = theme.scales().sequential(bandDomain).toArray()
const diverging = theme.scales().diverging(bandDomain).toArray()
const sequential2 = theme.scales().sequential2(bandDomain).toArray()
const diverging2 = theme.scales().diverging2(bandDomain).toArray()
const greys = theme.scales().greys(bandDomain).toArray()
return _jsxs(
'div',
{
style: {
border: `1px solid ${foregroundColor}`,
background: backgroundColor,
padding: '10px 30px 30px 30px',
color: foregroundColor,
textAlign: 'left',
},
children: [
_jsx(
'h2',
{ style: { color: foregroundColor }, children: 'Application colors' },
void 0,
),
_jsx(ApplicationPalette, {}, void 0),
_jsxs(
'ul',
{
style: {
fontSize: 18,
listStyleType: 'none',
paddingLeft: 0,
},
children: [
_jsxs(
'li',
{
children: [
foreground,
' on ',
background,
' (contrast ratio:',
' ',
_jsx(
Contrast,
{
foreground: foregroundColor,
background: backgroundColor,
error: errorColor,
showLink: true,
},
void 0,
),
')',
],
},
void 0,
),
_jsxs(
'li',
{
children: [
accent,
' on ',
background,
' (contrast ratio:',
' ',
_jsx(
Contrast,
{
foreground: accentColor,
background: backgroundColor,
error: errorColor,
showLink: true,
},
void 0,
),
')',
],
},
void 0,
),
],
},
void 0,
),
_jsx(
'h2',
{ style: { color: foregroundColor }, children: 'Scales' },
void 0,
),
_jsxs(
'div',
{
style: {
display: 'flex',
flexDirection: 'column',
fontSize: 12,
width: BAND_WIDTH,
padding: 8,
...mark2style(theme.plotArea()),
},
children: [
_jsx(
ColorStrip,
{
label: 'Nominal ',
foreground: foregroundColor,
background: backgroundColor,
colors: nominal,
},
void 0,
),
_jsx(
ColorStrip,
{
label: 'Nominal+',
foreground: foregroundColor,
background: backgroundColor,
colors: nominalBold,
labelColors: nominalMuted,
},
void 0,
),
_jsx(
ColorStrip,
{
label: 'Nominal-',
foreground: foregroundColor,
background: backgroundColor,
colors: nominalMuted,
labelColors: nominalBold,
},
void 0,
),
_jsx(
ColorBand,
{
label: 'Sequential',
foreground: foregroundColor,
colors: sequential,
width: BAND_WIDTH,
},
void 0,
),
_jsx(
ColorBand,
{
label: 'Sequential2',
foreground: foregroundColor,
colors: sequential2,
width: BAND_WIDTH,
},
void 0,
),
_jsx(
ColorBand,
{
label: 'Diverging',
foreground: foregroundColor,
colors: diverging,
width: BAND_WIDTH,
},
void 0,
),
_jsx(
ColorBand,
{
label: 'Diverging2',
foreground: foregroundColor,
colors: diverging2,
width: BAND_WIDTH,
},
void 0,
),
_jsx(
ColorBand,
{
label: 'Greys',
foreground: foregroundColor,
colors: greys,
width: BAND_WIDTH,
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
}

13
packages/webapp/src/components/ColorStrip/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { CSSProperties, FC } from 'react'
import './index.css'
export interface ColorStripProps {
colorDefinitions: any[]
vertical?: boolean
swatchStyle?: CSSProperties
labelStyle?: CSSProperties
}
export declare const ColorStrip: FC<ColorStripProps>

Просмотреть файл

@ -0,0 +1,77 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
import './index.css'
const ColorBlock = ({
color,
label,
secondaryLabel,
vertical,
swatchStyle,
labelStyle,
}) => {
const direction = vertical ? 'vertical' : 'horizontal'
return _jsxs(
'div',
{
className: `color-block-${direction}`,
children: [
label
? _jsx(
'div',
{
className: `color-block-primary-label-${direction}`,
style: labelStyle,
children: label,
},
void 0,
)
: null,
_jsx(
'div',
{
className: `color-block-swatch-${direction}`,
style: { ...swatchStyle, backgroundColor: `${color}` },
},
void 0,
),
secondaryLabel
? _jsx(
'div',
{
className: `color-block-secondary-label-${direction}`,
children: secondaryLabel,
},
void 0,
)
: null,
],
},
void 0,
)
}
export const ColorStrip = ({
colorDefinitions,
vertical,
swatchStyle,
labelStyle,
}) => {
const direction = vertical ? 'vertical' : 'horizontal'
return _jsx(
'div',
{
className: `color-strip-${direction}`,
children: colorDefinitions.map(def =>
_jsx(
ColorBlock,
{
...def,
vertical: vertical,
swatchStyle: swatchStyle,
labelStyle: labelStyle,
},
`${def.color}-${def.label}`,
),
),
},
void 0,
)
}

22
packages/webapp/src/components/ControlPanel/ControlPanel.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,22 @@
import { ColorBlindnessMode } from '@thematic/color'
import { ThemeListing, Theme } from '@thematic/core'
import { FC } from 'react'
import './index.css'
export interface ControlPanelProps {
themes: ThemeListing[]
themeInfo: ThemeListing
chartSize: number
drawNodes: boolean
drawLinks: boolean
scaleItemCount: number
colorBlindnessMode: ColorBlindnessMode
onThemeLoaded: (theme: Theme) => void
onThemeChange: (t: ThemeListing) => void
onThemeVariantToggled: () => void
onChartSizeChange: (n: number) => void
onDrawNodesChange: (d: boolean) => void
onDrawLinksChange: (d: boolean) => void
onScaleItemCountChange: (value: number) => void
onColorBlindnessModeChange: (mode: ColorBlindnessMode) => void
}
export declare const ControlPanel: FC<ControlPanelProps>

Просмотреть файл

@ -0,0 +1,297 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Dropdown, Toggle, SpinButton, Position } from '@fluentui/react'
import { ColorBlindnessMode, colorBlindnessInfo } from '@thematic/color'
import { ColorPickerButton } from '@thematic/fluent'
import { useState, useCallback } from 'react'
import { EnumDropdown } from '../EnumDropdown'
import './index.css'
const SCALE_MIN = 1
const SCALE_MAX = 1000
export const ControlPanel = ({
themes,
themeInfo,
chartSize,
drawNodes,
drawLinks,
scaleItemCount,
colorBlindnessMode,
onThemeLoaded,
onThemeChange,
onThemeVariantToggled,
onChartSizeChange,
onDrawNodesChange,
onDrawLinksChange,
onScaleItemCountChange,
onColorBlindnessModeChange,
}) => {
const handleThemeChange = (e, v) => {
if (v) {
const found = themes.find(t => t.id === v.key)
if (found) {
onThemeChange(found)
}
}
}
const handleChartIncrement = useCallback(
() => onChartSizeChange(chartSize + 100),
[onChartSizeChange, chartSize],
)
const handleChartDecrement = useCallback(
() => onChartSizeChange(chartSize - 100),
[onChartSizeChange, chartSize],
)
const handleChartValidate = useCallback(
v => {
const num = parseInt(v, 10)
if (!isNaN(num) && num >= 100) {
onChartSizeChange(num)
}
},
[onChartSizeChange],
)
const handleDarkChange = (e, v) => {
setDark(!dark)
onThemeVariantToggled()
}
const handleDrawNodesChange = (e, v) => onDrawNodesChange(v)
const handleDrawLinksChange = (e, v) => onDrawLinksChange(v)
const changeValue = useCallback(
(value, change = 0) => {
const num = parseInt(value, 10)
if (!isNaN(num)) {
const updated = num + change
if (updated >= SCALE_MIN && updated <= SCALE_MAX) {
onScaleItemCountChange(updated)
}
}
},
[onScaleItemCountChange],
)
const handleScaleValidate = useCallback(v => changeValue(v), [changeValue])
const handleScaleIncrement = useCallback(
v => {
changeValue(v, 1)
},
[changeValue],
)
const handleScaleDecrement = useCallback(
v => changeValue(v, -1),
[changeValue],
)
const handleColorBlindnessChange = e => {
onColorBlindnessModeChange(e)
}
const [dark, setDark] = useState(false)
const cbInfo = colorBlindnessInfo(colorBlindnessMode)
const renderDropdownOption = useCallback(option => {
return _jsx(
'div',
{
style: {
borderLeft: `8px solid ${option.data.accent}`,
paddingLeft: 8,
},
children: option.text,
},
void 0,
)
}, [])
return _jsxs(
'div',
{
className: 'control-wrapper',
children: [
_jsx('h1', { children: 'thematic' }, void 0),
_jsxs(
'div',
{
className: 'controls',
children: [
_jsx(
'div',
{
className: 'control',
children: _jsx(
Dropdown,
{
label: 'Theme',
selectedKey: themeInfo.id,
onChange: handleThemeChange,
options: themes.map(t => ({
key: t.id,
text: t.name,
data: t,
})),
onRenderOption: renderDropdownOption,
styles: {
root: {
display: 'flex',
flexDirection: 'column',
},
},
},
void 0,
),
},
void 0,
),
_jsx(
'div',
{
className: 'control',
children: _jsx(
ColorPickerButton,
{ label: 'Accent', onChange: onThemeLoaded },
void 0,
),
},
void 0,
),
_jsx(
'div',
{
className: 'control',
children: _jsx(
Toggle,
{
label: 'Dark mode',
checked: dark,
onChange: handleDarkChange,
},
void 0,
),
},
void 0,
),
_jsx(
'div',
{
className: 'control spinner',
children: _jsx(
SpinButton,
{
label: 'Scale items',
labelPosition: Position.top,
value: scaleItemCount.toString(),
min: SCALE_MIN,
max: SCALE_MAX,
step: 1,
onIncrement: handleScaleIncrement,
onDecrement: handleScaleDecrement,
onValidate: handleScaleValidate,
incrementButtonAriaLabel: 'Increase value by 1',
decrementButtonAriaLabel: 'Decrease value by 1',
},
void 0,
),
},
void 0,
),
_jsx(
'div',
{
className: 'control spinner',
children: _jsx(
SpinButton,
{
label: 'Chart size',
labelPosition: Position.top,
value: chartSize.toString(),
min: 100,
max: 2000,
step: 100,
onIncrement: handleChartIncrement,
onDecrement: handleChartDecrement,
onValidate: handleChartValidate,
incrementButtonAriaLabel: 'Increase value by 1',
decrementButtonAriaLabel: 'Decrease value by 1',
},
void 0,
),
},
void 0,
),
_jsx(
'div',
{
className: 'control',
children: _jsx(
Toggle,
{
label: 'Draw nodes',
checked: drawNodes,
onChange: handleDrawNodesChange,
},
void 0,
),
},
void 0,
),
_jsx(
'div',
{
className: 'control',
children: _jsx(
Toggle,
{
label: 'Draw links',
checked: drawLinks,
onChange: handleDrawLinksChange,
},
void 0,
),
},
void 0,
),
_jsxs(
'div',
{
className: 'control',
children: [
_jsx(
EnumDropdown,
{
enumeration: ColorBlindnessMode,
label: 'Color blindness',
selected: colorBlindnessMode,
onChange: handleColorBlindnessChange,
styles: {
root: {
display: 'flex',
flexDirection: 'column',
width: 150,
},
},
},
void 0,
),
colorBlindnessMode !== ColorBlindnessMode.None &&
_jsx(
'div',
{
className: 'cb-info',
children: `${cbInfo.description}. Affecting ${
cbInfo.incidence < 0.01
? '<1'
: `~${Math.round(cbInfo.incidence * 100)}`
}% of males.`,
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
}

22
packages/webapp/src/components/ControlPanel/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,22 @@
/// <reference types="react" />
export declare const ControlPanel: import('react-redux').ConnectedComponent<
import('react').FC<import('./ControlPanel').ControlPanelProps>,
import('react-redux').Omit<
import('./ControlPanel').ControlPanelProps,
| 'colorBlindnessMode'
| 'themes'
| 'themeInfo'
| 'chartSize'
| 'drawNodes'
| 'drawLinks'
| 'scaleItemCount'
| 'onThemeLoaded'
| 'onThemeChange'
| 'onThemeVariantToggled'
| 'onChartSizeChange'
| 'onDrawNodesChange'
| 'onDrawLinksChange'
| 'onScaleItemCountChange'
| 'onColorBlindnessModeChange'
>
>

Просмотреть файл

@ -0,0 +1,37 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { connect } from 'react-redux'
import {
themeLoaded,
themeSelected,
themeVariantToggled,
chartSizeChanged,
drawNodesChanged,
drawLinksChanged,
scaleItemCountChanged,
colorBlindnessModeChanged,
} from '../../state/actions'
import { ControlPanel as ControlPanelComponent } from './ControlPanel'
export const ControlPanel = connect(
state => ({
themes: state.themes,
themeInfo: state.themeInfo,
chartSize: state.ui.chartSize,
drawNodes: state.ui.drawNodes,
drawLinks: state.ui.drawLinks,
scaleItemCount: state.ui.scaleItemCount,
colorBlindnessMode: state.ui.colorBlindnessMode,
}),
{
onThemeLoaded: themeLoaded,
onThemeChange: themeSelected,
onThemeVariantToggled: themeVariantToggled,
onChartSizeChange: chartSizeChanged,
onDrawNodesChange: drawNodesChanged,
onDrawLinksChange: drawLinksChanged,
onScaleItemCountChange: scaleItemCountChanged,
onColorBlindnessModeChange: colorBlindnessModeChanged,
},
)(ControlPanelComponent)

11
packages/webapp/src/components/CoolerPicker/ColorSelection.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,11 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Theme } from '@thematic/core'
import { FC } from 'react'
import './index.css'
export interface ColorSelectionProps {
onThemeLoaded: (theme: Theme) => void
}
export declare const ColorSelection: FC<ColorSelectionProps>

Просмотреть файл

@ -0,0 +1,21 @@
import { jsx as _jsx } from 'react/jsx-runtime'
import { ColorPicker, ColorPickerLayout } from '@thematic/fluent'
import './index.css'
export const ColorSelection = ({ onThemeLoaded }) => {
return _jsx(
'div',
{
className: 'color-controls-area',
children: _jsx(
ColorPicker,
{
onChange: onThemeLoaded,
layout: ColorPickerLayout.SideBySide,
styles: { sliders: { width: 400 } },
},
void 0,
),
},
void 0,
)
}

14
packages/webapp/src/components/CoolerPicker/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,14 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Theme } from '@thematic/core'
import { FC } from 'react'
import './index.css'
export interface CoolerPickerProps {
themeLoaded: (theme: Theme) => void
}
export declare const CoolerPicker: import('react-redux').ConnectedComponent<
FC<CoolerPickerProps>,
import('react-redux').Omit<CoolerPickerProps, 'themeLoaded'>
>

Просмотреть файл

@ -0,0 +1,18 @@
import { jsx as _jsx } from 'react/jsx-runtime'
import { connect } from 'react-redux'
import { themeLoaded } from '../../state/actions'
import { ColorSelection } from './ColorSelection'
import './index.css'
const CoolerPickerComponent = ({ themeLoaded }) => {
return _jsx(
'div',
{
className: 'cooler-picker',
children: _jsx(ColorSelection, { onThemeLoaded: themeLoaded }, void 0),
},
void 0,
)
}
export const CoolerPicker = connect(null, {
themeLoaded,
})(CoolerPickerComponent)

12
packages/webapp/src/components/CoolerPicker/options.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,12 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export declare const backgroundHueShiftOptions: {
label: string
value: number
}[]
export declare const backgroundLevelOptions: {
label: string
value: number
}[]

Просмотреть файл

@ -0,0 +1,45 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export const backgroundHueShiftOptions = [
{
label: '-170 Complement',
value: -170,
},
{
label: '-60 Analogous',
value: -60,
},
{
label: '-30 Analogous',
value: -30,
},
{
label: '0 Monochrome',
value: 0,
},
{
label: '+30 Analogous',
value: 30,
},
{
label: '+60 Analogous',
value: 60,
},
{
label: '+170 Complement',
value: 170,
},
]
const getBackgroundLevelOptions = () => {
const options = []
for (let i = 0; i <= 100; i += 10) {
options.push({
label: '' + i,
value: i,
})
}
return options
}
export const backgroundLevelOptions = getBackgroundLevelOptions()

34
packages/webapp/src/components/DownloadLink/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,34 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import {
IStyleFunctionOrObject,
ILinkStyleProps,
ILinkStyles,
} from '@fluentui/react'
import { FC } from 'react'
export interface DownloadLinkProps {
/**
* Filename to download with
*/
filename: string
/**
* Value of the file contents. Direct passthrough to Blob constructor.
* https://www.w3.org/TR/FileAPI/#dfn-Blob
*/
blobParts: Blob[] | BufferSource[] | string[]
/**
* Additional blob options. Direct passthrough to Blob constructor.
* https://www.w3.org/TR/FileAPI/#dfn-BlobPropertyBag
*/
options?: BlobPropertyBag
/**
* Display text for the link.
* Will default to "Download <filename>" if not provided
*/
label?: string
/** Optional passthrough styles for the link */
styles?: IStyleFunctionOrObject<ILinkStyleProps, ILinkStyles>
}
export declare const DownloadLink: FC<DownloadLinkProps>

Просмотреть файл

@ -0,0 +1,28 @@
import { jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Link } from '@fluentui/react'
export const DownloadLink = ({
filename,
blobParts,
options,
label,
styles,
}) => {
const blob = new Blob(blobParts, options)
const url = URL.createObjectURL(blob)
const lbl = label || `Download ${filename}`
return _jsx(
Link,
{
styles: styles,
title: lbl,
href: url,
download: filename,
children: lbl,
},
void 0,
)
}

37
packages/webapp/src/components/EnumButtonBar/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,37 @@
/// <reference types="react" />
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import {
ICommandBarStyleProps,
ICommandBarStyles,
IStyleFunctionOrObject,
} from '@fluentui/react'
export interface WrapperStyles {
root: React.CSSProperties
label: React.CSSProperties
}
export interface EnumButtonBarProps<E> {
enumeration: any
selected?: E
onChange?: (selected: string | number) => void
wrapperStyles?: WrapperStyles
commandBarStyles?: IStyleFunctionOrObject<
ICommandBarStyleProps,
ICommandBarStyles
>
iconNames?: string[]
iconOnly?: boolean
label?: string
}
export declare function EnumButtonBar<E>({
enumeration,
selected,
onChange,
wrapperStyles,
commandBarStyles,
iconNames,
iconOnly,
label,
}: EnumButtonBarProps<E>): JSX.Element

Просмотреть файл

@ -0,0 +1,76 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { CommandBar, Label } from '@fluentui/react'
import { useThematic } from '@thematic/react'
import { useCallback, useMemo } from 'react'
const splitCamel = str => str.replace(/([a-z\d])([A-Z])/g, '$1 $2')
export function EnumButtonBar({
enumeration,
selected,
onChange,
wrapperStyles,
commandBarStyles,
iconNames,
iconOnly,
label,
}) {
const theme = useThematic()
const selectedKey = enumeration[enumeration[selected]]
const handleChange = useCallback(
s => {
if (onChange) {
onChange(s)
}
},
[onChange],
)
const options = useMemo(
() =>
Object.entries(enumeration)
.filter(m => isNaN(parseInt(m[0]))) // only keep the int copy from the enum obj
.map((m, i) => {
const key = m[1]
const text = splitCamel(m[0])
return {
key,
text,
checked: selectedKey === key,
iconProps: iconNames ? { iconName: iconNames[i] } : undefined,
iconOnly,
onClick: () => handleChange(key),
}
}),
[enumeration, handleChange, iconNames, iconOnly, selectedKey],
)
const rootStyle = {
// TODO: this width is used to prevent automatic collapsing with overflow items
// it may not be what we always want, however
width: options.length * 46.75,
...(wrapperStyles ? wrapperStyles.root : {}),
}
const labelStyle = {
...(wrapperStyles ? wrapperStyles.label : {}),
}
const commandStyles = {
primarySet: {
border: `1px solid ${theme.application().border().hex()}`,
paddingleft: 0,
paddingRight: 0,
},
...commandBarStyles,
}
return _jsxs(
'div',
{
style: rootStyle,
children: [
label && _jsx(Label, { style: labelStyle, children: label }, void 0),
_jsx(CommandBar, { items: options, styles: commandStyles }, void 0),
],
},
void 0,
)
}

28
packages/webapp/src/components/EnumDropdown/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,28 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import {
IDropdownStyles,
IDropdownStyleProps,
IStyleFunctionOrObject,
} from '@fluentui/react'
export interface EnumDropdownProps<E> {
enumeration: any
label?: string
selected?: E
onChange?: (selected: string | number) => void
styles?: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles>
}
/**
* This component wraps a Fluent Dropdown, automatically creating the options from a supplied enum.
* https://developer.microsoft.com/en-us/fluentui#/controls/web/dropdown
* TODO: add additional passthrough props. We really just override the options array and the onChange callback.
*/
export declare function EnumDropdown<E>({
enumeration,
label,
selected,
onChange,
styles,
}: EnumDropdownProps<E>): JSX.Element

Просмотреть файл

@ -0,0 +1,45 @@
import { jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Dropdown } from '@fluentui/react'
import { useCallback, useState } from 'react'
/**
* This component wraps a Fluent Dropdown, automatically creating the options from a supplied enum.
* https://developer.microsoft.com/en-us/fluentui#/controls/web/dropdown
* TODO: add additional passthrough props. We really just override the options array and the onChange callback.
*/
export function EnumDropdown({
enumeration,
label,
selected,
onChange = () => null,
styles,
}) {
const options = Object.entries(enumeration)
.filter(m => isNaN(parseInt(m[0])))
.map(m => ({ key: m[1], text: m[0] }))
const handleChange = useCallback(
(e, v) => {
if (v) {
setSelectedKey(v.key)
onChange(v.key)
}
},
[onChange],
)
const key = enumeration[enumeration[selected]]
const [selectedKey, setSelectedKey] = useState(key)
return _jsx(
Dropdown,
{
label: label,
options: options,
selectedKey: selectedKey,
onChange: handleChange,
styles: styles,
},
void 0,
)
}

13
packages/webapp/src/components/FluentControls/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Theme } from '@thematic/core'
import { FC } from 'react'
export interface FluentControlsComponentProps {
themeLoaded: (theme: Theme) => void
}
export declare const FluentControls: import('react-redux').ConnectedComponent<
FC<FluentControlsComponentProps>,
import('react-redux').Omit<FluentControlsComponentProps, 'themeLoaded'>
>

Просмотреть файл

@ -0,0 +1,243 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { ScaleType } from '@thematic/core'
import {
ScaleDropdown,
ScaleTypeChoiceGroup,
ColorPicker,
ColorPickerButton,
} from '@thematic/fluent'
import { useThematic } from '@thematic/react'
import { useState, useCallback, useMemo } from 'react'
import { connect } from 'react-redux'
import { themeLoaded } from '../../state/actions'
const FluentControlsComponent = ({ themeLoaded }) => {
const theme = useThematic()
const [scale, setScale] = useState('<none>')
const handleScaleChange = useCallback((e, option) => setScale(option.key), [])
const [scaleType, setScaleType] = useState(ScaleType.Linear)
const handleScaleTypeChange = useCallback(type => setScaleType(type), [])
const handlePickerChange = useCallback(t => themeLoaded(t), [themeLoaded])
const labelStyle = useMemo(
() => ({
fontWeight: 'bold',
color: theme.application().accent().hex(),
}),
[theme],
)
const actionStyle = useMemo(
() => ({
fontSize: 12,
fontFamily: 'monospace',
color: theme.application().warning().hex(),
}),
[theme],
)
return _jsxs(
'div',
{
style: {
fontSize: 14,
overflowY: 'scroll',
},
children: [
_jsx(
'p',
{
children:
'The @thematic/fluent package contains a few custom Fluent controls you can use in your applications to allow Thematic-specific interactions.',
},
void 0,
),
_jsxs(
'div',
{
style: controlsStyle,
children: [
_jsxs(
'div',
{
style: controlStyle,
children: [
_jsxs(
'p',
{
children: [
_jsx(
'span',
{ style: labelStyle, children: 'ScaleDropdown:' },
void 0,
),
' a Dropdown that pre-loads Thematic scale options.',
],
},
void 0,
),
_jsx(
ScaleDropdown,
{
placeholder: 'Choose scale',
onChange: handleScaleChange,
},
void 0,
),
_jsxs(
'p',
{ style: actionStyle, children: [' onChange: ', scale] },
void 0,
),
],
},
void 0,
),
_jsxs(
'div',
{
style: controlStyle,
children: [
_jsxs(
'p',
{
children: [
_jsx(
'span',
{
style: labelStyle,
children: 'ScaleTypeChoiceGroup:',
},
void 0,
),
' a ChoiceGroup that pre-loads Thematic scale types.',
],
},
void 0,
),
_jsx(
ScaleTypeChoiceGroup,
{
selectedType: scaleType,
onChange: handleScaleTypeChange,
},
void 0,
),
_jsxs(
'p',
{
style: actionStyle,
children: [' onChange: ', scaleType],
},
void 0,
),
],
},
void 0,
),
_jsxs(
'div',
{
style: controlStyle,
children: [
_jsxs(
'p',
{
children: [
_jsx(
'span',
{ style: labelStyle, children: 'ColorPicker:' },
void 0,
),
' a ColorPicker that emits Thematic parameters.',
],
},
void 0,
),
_jsx(
ColorPicker,
{ theme: theme, onChange: handlePickerChange },
void 0,
),
_jsxs(
'p',
{
style: actionStyle,
children: [
' ',
'onChange: ',
theme.application().accent().hex(),
],
},
void 0,
),
],
},
void 0,
),
_jsxs(
'div',
{
style: controlStyle,
children: [
_jsxs(
'p',
{
children: [
_jsx(
'span',
{
style: labelStyle,
children: 'ColorPickerButton:',
},
void 0,
),
' a DropdownButton that hosts a Thematic ColorPicker.',
],
},
void 0,
),
_jsx(
ColorPickerButton,
{ theme: theme, onChange: handlePickerChange },
void 0,
),
_jsxs(
'p',
{
style: actionStyle,
children: [
' ',
'onChange: ',
theme.application().accent().hex(),
],
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
}
export const FluentControls = connect(null, {
themeLoaded,
})(FluentControlsComponent)
const controlsStyle = {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-around',
}
const controlStyle = {
width: 320,
padding: 8,
margin: 8,
}

10
packages/webapp/src/components/FluentViewer/FluentPalette.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { FluentTheme } from '@thematic/fluent'
import { FC } from 'react'
export interface FluentPaletteProps {
theme: FluentTheme
}
export declare const FluentPalette: FC<FluentPaletteProps>

Просмотреть файл

@ -0,0 +1,129 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
import { ColorStrip } from '../ColorStrip'
const primaryKeys = [
'themeDarker',
'themeDark',
'themeDarkAlt',
'themePrimary',
'themeSecondary',
'themeTertiary',
'themeLight',
'themeLighter',
'themeLighterAlt',
]
const foregroundKeys = [
'black',
'neutralDark',
'neutralPrimary',
'neutralPrimaryAlt',
'neutralSecondary',
'neutralTertiary',
]
const backgroundKeys = [
'neutralTertiaryAlt',
'neutralQuaternary',
'neutralQuaternaryAlt',
'neutralLight',
'neutralLighter',
'neutralLighterAlt',
'white',
]
export const FluentPalette = ({ theme }) => {
const json = theme.toFluent()
const mapkeys = keys =>
keys.map(key => ({
color: json.palette[key],
label: key,
secondaryLabel: json.palette[key],
}))
const primaries = mapkeys(primaryKeys)
const foregrounds = mapkeys(foregroundKeys)
const backgrounds = mapkeys(backgroundKeys)
const styles = {
root: {
display: 'flex',
},
swatch: {
border: `1px solid ${theme.application().border().hex()}`,
},
header: {
color: theme.application().foreground().hex(),
fontSize: 14,
},
}
return _jsxs(
'div',
{
style: styles.root,
children: [
_jsxs(
'div',
{
children: [
_jsx(
'div',
{ style: styles.header, children: 'Primary' },
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: primaries,
swatchStyle: styles.swatch,
},
void 0,
),
],
},
void 0,
),
_jsxs(
'div',
{
children: [
_jsx(
'div',
{ style: styles.header, children: 'Foreground' },
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: foregrounds,
swatchStyle: styles.swatch,
},
void 0,
),
],
},
void 0,
),
_jsxs(
'div',
{
children: [
_jsx(
'div',
{ style: styles.header, children: 'Background' },
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: backgrounds,
swatchStyle: styles.swatch,
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
}

6
packages/webapp/src/components/FluentViewer/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
import { FC } from 'react'
/**
* This component hosts the Fluent Theme slots in the style of Thematic.
* A running version can be found here: https://aka.ms/themedesigner
*/
export declare const FluentViewer: FC

Просмотреть файл

@ -0,0 +1,40 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { loadFluentTheme } from '@thematic/fluent'
import { useThematic } from '@thematic/react'
import { useMemo } from 'react'
import { DownloadLink } from '../DownloadLink'
import { FluentPalette } from './FluentPalette'
/**
* This component hosts the Fluent Theme slots in the style of Thematic.
* A running version can be found here: https://aka.ms/themedesigner
*/
export const FluentViewer = () => {
const theme = useThematic()
const fluentTheme = useMemo(() => loadFluentTheme(theme), [theme])
const value = useMemo(
() => JSON.stringify(fluentTheme.toFluent(), null, 2),
[fluentTheme],
)
return _jsxs(
'div',
{
children: [
_jsx(FluentPalette, { theme: fluentTheme }, void 0),
_jsx(
DownloadLink,
{
filename: `${theme.name}-${theme.variant}-fluent.json`,
blobParts: [value],
styles: { root: { fontSize: '0.5em' } },
},
void 0,
),
],
},
void 0,
)
}

2
packages/webapp/src/components/GIMP/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
import { FC } from 'react'
export declare const GimpEditor: FC

Просмотреть файл

@ -0,0 +1,55 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { TextField } from '@fluentui/react'
import { gimp } from '@thematic/core'
import { useThematic } from '@thematic/react'
import { useMemo } from 'react'
import { DownloadLink } from '../DownloadLink'
// the deep nesting of the component requires several layers of size adjustment
const styles = {
root: {
width: '100%',
height: '90%',
},
wrapper: {
height: '100%',
},
fieldGroup: {
height: '100%',
},
field: {
height: '100%',
fontFamily: 'monospace',
fontSize: 11,
},
}
export const GimpEditor = () => {
const theme = useThematic()
const value = useMemo(() => theme.transform(gimp), [theme])
return _jsxs(
'div',
{
style: { width: '100%' },
children: [
_jsx(
TextField,
{ styles: styles, multiline: true, readOnly: true, value: value },
void 0,
),
_jsx(
DownloadLink,
{
filename: `${theme.name}-${theme.variant}.gpl`,
blobParts: [value],
styles: { root: { fontSize: '0.5em' } },
},
void 0,
),
],
},
void 0,
)
}

8
packages/webapp/src/components/JSON/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
import { FC } from 'react'
export interface JSONPaneProps {
scaleItemCount?: number
}
export declare const JSONPane: import('react-redux').ConnectedComponent<
FC<JSONPaneProps>,
import('react-redux').Omit<JSONPaneProps, 'scaleItemCount'>
>

Просмотреть файл

@ -0,0 +1,39 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { useThematic } from '@thematic/react'
import { useMemo } from 'react'
import { connect } from 'react-redux'
import { DownloadLink } from '../DownloadLink'
import { JSONEditor } from '../JSONEditor'
const JSONPaneComponent = ({ scaleItemCount }) => {
const theme = useThematic()
const value = useMemo(
() => theme.toJSON({ scaleItemCount }),
[theme, scaleItemCount],
)
return _jsxs(
'div',
{
style: { width: '100%', height: '90%' },
children: [
_jsx(JSONEditor, { value: value }, void 0),
_jsx(
DownloadLink,
{
filename: `${theme.name}-${theme.variant}.json`,
blobParts: [JSON.stringify(value, null, 2)],
styles: { root: { fontSize: '0.5em' } },
},
void 0,
),
],
},
void 0,
)
}
export const JSONPane = connect(state => ({
scaleItemCount: state.ui.scaleItemCount,
}))(JSONPaneComponent)

5
packages/webapp/src/components/JSONEditor/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
import { FC } from 'react'
export interface JSONEditorProps {
value: any
}
export declare const JSONEditor: FC<JSONEditorProps>

Просмотреть файл

@ -0,0 +1,36 @@
import { jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { TextField } from '@fluentui/react'
// the deep nesting of the component requires several layers of size adjustment
const styles = {
root: {
width: '100%',
height: '100%',
},
wrapper: {
height: '100%',
},
fieldGroup: {
height: '100%',
},
field: {
height: '100%',
fontFamily: 'monospace',
fontSize: 11,
},
}
export const JSONEditor = ({ value }) => {
return _jsx(
TextField,
{
styles: styles,
multiline: true,
readOnly: true,
value: JSON.stringify(value, null, 2),
},
void 0,
)
}

12
packages/webapp/src/components/MarkGrid/GridCell.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,12 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { SelectionState } from '@thematic/core'
import { FC } from 'react'
export interface GridCellProps {
name: string
size: number
selectionState?: SelectionState
}
export declare const GridCell: FC<GridCellProps>

Просмотреть файл

@ -0,0 +1,70 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
import { useThematic, mark2style } from '@thematic/react'
import { useMemo } from 'react'
import { Rect, Circle, Line, Arc, Text } from '../svg'
const selectMark = key => {
switch (key) {
case 'rect':
case 'plotArea':
case 'area':
case 'tooltip':
return Rect
case 'circle':
case 'process':
case 'node':
return Circle
case 'line':
case 'rule':
case 'flow':
case 'link':
case 'axisLine':
case 'axisTicks':
case 'gridLines':
return Line
case 'arc':
return Arc
case 'text':
case 'axisTickLabels':
case 'axisTitle':
return Text
default:
throw new Error('Unsupported mark type')
}
}
export const GridCell = ({ name, size, selectionState }) => {
const theme = useThematic()
const Mark = selectMark(name)
const exampleSize = size * 1.5
const exampleStyle = useMemo(
() => ({
...mark2style(theme.plotArea()),
width: exampleSize,
height: exampleSize,
}),
[theme, exampleSize],
)
const config = theme[name]({ selectionState })
return _jsxs(
'div',
{
className: 'mark-grid-cell',
children: [
_jsx(
'h2',
{ className: 'mark-grid-cell-title', children: name },
void 0,
),
_jsx(
'div',
{
className: 'mark-grid-cell-example',
style: exampleStyle,
children: _jsx(Mark, { config: config, size: size }, void 0),
},
void 0,
),
],
},
void 0,
)
}

3
packages/webapp/src/components/MarkGrid/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
import { FC } from 'react'
import './index.css'
export declare const MarkGrid: FC

Просмотреть файл

@ -0,0 +1,105 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { SelectionState } from '@thematic/core'
import { useThematic } from '@thematic/react'
import { useState, useCallback, useMemo } from 'react'
import { EnumButtonBar } from '../EnumButtonBar'
import { GridCell } from './GridCell'
import './index.css'
const SIZE = 30
const markKeys = [
'rect',
'area',
'circle',
'arc',
'line',
'rule',
'node',
'link',
'process',
'flow',
'text',
]
const chromeKeys = [
'plotArea',
'gridLines',
'axisLine',
'axisTicks',
'axisTickLabels',
'axisTitle',
'tooltip',
]
export const MarkGrid = () => {
const theme = useThematic()
const [selectionState, setSelectionState] = useState(SelectionState.Normal)
const handleSelectionStateChange = useCallback(s => {
setSelectionState(s)
}, [])
const labelStyle = useMemo(
() => ({
fontSize: '0.5em',
fontWeight: 'bold',
color: theme.application().lowContrast().hex(),
}),
[theme],
)
return _jsxs(
'div',
{
style: { display: 'flex', flexDirection: 'column' },
children: [
_jsx('div', { style: labelStyle, children: 'On-chart marks' }, void 0),
_jsxs(
'div',
{
className: 'mark-grid',
children: [
markKeys.map(key =>
_jsx(
GridCell,
{ name: key, size: SIZE, selectionState: selectionState },
key,
),
),
_jsx(
EnumButtonBar,
{
enumeration: SelectionState,
selected: selectionState,
onChange: handleSelectionStateChange,
iconNames: [
'CheckMark',
'CheckBoxComposite',
'CheckBoxCompositeReversed',
'GenericScanFilled',
'Hide',
'StatusCircleQuestionMark',
],
iconOnly: true,
label: 'Selection state',
},
void 0,
),
],
},
void 0,
),
_jsx('div', { style: labelStyle, children: 'Chart chrome' }, void 0),
_jsx(
'div',
{
className: 'mark-grid',
children: chromeKeys.map(key =>
_jsx(GridCell, { name: key, size: SIZE }, key),
),
},
void 0,
),
],
},
void 0,
)
}

5
packages/webapp/src/components/Office/OfficePalette.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
import { FC } from 'react'
export interface OfficePaletteProps {
colors: any
}
export declare const OfficePalette: FC<OfficePaletteProps>

Просмотреть файл

@ -0,0 +1,95 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { useThematic } from '@thematic/react'
import { ColorStrip } from '../ColorStrip'
const mainKeys = [
'dark1',
'light1',
'dark2',
'light2',
'hyperlink',
'followedHyperlink',
]
const accentKeys = [
'accent1',
'accent2',
'accent3',
'accent4',
'accent5',
'accent6',
]
export const OfficePalette = ({ colors }) => {
const theme = useThematic()
const mapkeys = keys =>
keys.map(key => ({
color: colors[key],
label: key,
secondaryLabel: colors[key],
}))
const mains = mapkeys(mainKeys)
const accents = mapkeys(accentKeys)
const styles = {
root: {
display: 'flex',
},
swatch: {
border: `1px solid ${theme.application().border().hex()}`,
},
header: {
color: theme.application().foreground().hex(),
fontSize: 14,
},
}
return _jsxs(
'div',
{
style: styles.root,
children: [
_jsxs(
'div',
{
children: [
_jsx(
'div',
{ style: styles.header, children: 'Primary' },
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: mains,
swatchStyle: styles.swatch,
},
void 0,
),
],
},
void 0,
),
_jsxs(
'div',
{
children: [
_jsx('div', { style: styles.header, children: 'Accent' }, void 0),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: accents,
swatchStyle: styles.swatch,
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
}

2
packages/webapp/src/components/Office/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
import { FC } from 'react'
export declare const Office: FC

Просмотреть файл

@ -0,0 +1,33 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { office } from '@thematic/core'
import { useThematic } from '@thematic/react'
import { useMemo } from 'react'
import { DownloadLink } from '../DownloadLink'
import { OfficePalette } from './OfficePalette'
export const Office = () => {
const theme = useThematic()
const ofc = useMemo(() => theme.transform(office), [theme])
const str = useMemo(() => JSON.stringify(ofc, null, 2), [ofc])
return _jsxs(
'div',
{
children: [
_jsx(OfficePalette, { colors: ofc }, void 0),
_jsx(
DownloadLink,
{
filename: `${theme.name}-${theme.variant}-office.json`,
blobParts: [str],
styles: { root: { fontSize: '0.5em' } },
},
void 0,
),
],
},
void 0,
)
}

5
packages/webapp/src/components/PowerBI/PowerBIPalette.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
import { FC } from 'react'
export interface PowerBIPaletteProps {
colors: any
}
export declare const PowerBIPalette: FC<PowerBIPaletteProps>

Просмотреть файл

@ -0,0 +1,109 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { useThematic } from '@thematic/react'
import { ColorStrip } from '../ColorStrip'
const mainKeys = ['foreground', 'background', 'tableAccent']
export const PowerBIPalette = ({ colors }) => {
const theme = useThematic()
const mapkeys = keys =>
keys.map(key => ({
color: colors[key],
label: key,
secondaryLabel: colors[key],
}))
const mains = mapkeys(mainKeys)
const dataColorsLeft = colors.dataColors.slice(0, 6).map(c => ({
color: c,
label: c,
}))
const dataColorsRight = colors.dataColors.slice(6).map(c => ({
color: c,
secondaryLabel: c,
}))
const styles = {
root: {
display: 'flex',
},
header: {
color: theme.application().foreground().hex(),
fontSize: 14,
},
swatch: {
border: `1px solid ${theme.application().border().hex()}`,
},
}
return _jsxs(
'div',
{
style: styles.root,
children: [
_jsxs(
'div',
{
children: [
_jsx(
'div',
{ style: styles.header, children: 'Primary' },
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: mains,
swatchStyle: styles.swatch,
},
void 0,
),
],
},
void 0,
),
_jsxs(
'div',
{
children: [
_jsx(
'div',
{ style: styles.header, children: 'Data Colors' },
void 0,
),
_jsxs(
'div',
{
style: { display: 'flex', alignItems: ' flex-start' },
children: [
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: dataColorsLeft,
swatchStyle: styles.swatch,
},
void 0,
),
_jsx(
ColorStrip,
{
vertical: true,
colorDefinitions: dataColorsRight,
swatchStyle: styles.swatch,
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
}

2
packages/webapp/src/components/PowerBI/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
import { FC } from 'react'
export declare const PowerBiEditor: FC

Просмотреть файл

@ -0,0 +1,33 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { powerbi } from '@thematic/core'
import { useThematic } from '@thematic/react'
import { useMemo } from 'react'
import { DownloadLink } from '../DownloadLink'
import { PowerBIPalette } from './PowerBIPalette'
export const PowerBiEditor = () => {
const theme = useThematic()
const pbi = useMemo(() => theme.transform(powerbi), [theme])
const str = useMemo(() => JSON.stringify(pbi, null, 2), [pbi])
return _jsxs(
'div',
{
children: [
_jsx(PowerBIPalette, { colors: pbi }, void 0),
_jsx(
DownloadLink,
{
filename: `${theme.name}-${theme.variant}-powerbi.json`,
blobParts: [str],
styles: { root: { fontSize: '0.5em' } },
},
void 0,
),
],
},
void 0,
)
}

6
packages/webapp/src/components/ThemeEditor/ThemeEditor.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
import { FC } from 'react'
import './index.css'
export interface ThemeEditorProps {
scaleItemCount: number
}
export declare const ThemeEditor: FC<ThemeEditorProps>

Просмотреть файл

@ -0,0 +1,150 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Pivot, PivotItem } from '@fluentui/react'
import { ColorPalette } from '../ColorPalette'
import { CoolerPicker } from '../CoolerPicker'
import { FluentViewer } from '../FluentViewer'
import { GimpEditor } from '../GIMP'
import { JSONPane } from '../JSON'
import { MarkGrid } from '../MarkGrid'
import { Office } from '../Office'
import { PowerBiEditor } from '../PowerBI'
import './index.css'
export const ThemeEditor = ({ scaleItemCount }) => {
return _jsxs(
'div',
{
className: 'editor-wrapper',
children: [
_jsxs(
Pivot,
{
children: [
_jsx(
PivotItem,
{
className: 'tab',
headerText: 'Color Picker',
children: _jsx(CoolerPicker, {}, void 0),
},
void 0,
),
_jsx(
PivotItem,
{
className: 'tab',
headerText: 'Marks',
children: _jsx(MarkGrid, {}, void 0),
},
void 0,
),
_jsx(
PivotItem,
{
className: 'tab',
headerText: 'Fluent UI',
children: _jsx(FluentViewer, {}, void 0),
},
void 0,
),
_jsx(
PivotItem,
{
className: 'tab',
headerText: 'Office',
children: _jsx(Office, {}, void 0),
},
void 0,
),
_jsx(
PivotItem,
{
className: 'tab',
headerText: 'Power BI',
children: _jsx(PowerBiEditor, {}, void 0),
},
void 0,
),
_jsx(
PivotItem,
{
className: 'tab',
headerText: 'GIMP',
children: _jsx(GimpEditor, {}, void 0),
},
void 0,
),
_jsx(
PivotItem,
{
className: 'tab',
headerText: 'JSON',
children: _jsx(JSONPane, {}, void 0),
},
void 0,
),
],
},
void 0,
),
_jsx(ColorPalette, { scaleItemCount: scaleItemCount }, void 0),
_jsxs(
'div',
{
className: 'footer',
children: [
_jsxs(
'div',
{
className: 'privacy',
children: [
'This site does not collect any personal information or use cookies.\u00A0',
_jsx(
'a',
{
target: '_blank',
rel: 'noreferrer',
href: 'https://privacy.microsoft.com/en-us/privacystatement/',
children:
"Read Microsoft's statement on Privacy and Cookies",
},
void 0,
),
'.',
],
},
void 0,
),
_jsxs(
'div',
{
className: 'github',
children: [
'Contribute at\u00A0',
_jsx(
'a',
{
target: '_blank',
rel: 'noreferrer',
href: 'https://github.com/microsoft/thematic',
children: 'GitHub',
},
void 0,
),
'.',
],
},
void 0,
),
],
},
void 0,
),
],
},
void 0,
)
}

8
packages/webapp/src/components/ThemeEditor/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/// <reference types="react" />
export declare const ThemeEditor: import('react-redux').ConnectedComponent<
import('react').FC<import('./ThemeEditor').ThemeEditorProps>,
import('react-redux').Omit<
import('./ThemeEditor').ThemeEditorProps,
'scaleItemCount'
>
>

Просмотреть файл

@ -0,0 +1,9 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { connect } from 'react-redux'
import { ThemeEditor as ThemeEditorComponent } from './ThemeEditor'
export const ThemeEditor = connect(state => ({
scaleItemCount: state.ui.scaleItemCount,
}))(ThemeEditorComponent)

10
packages/webapp/src/components/ThemeViewer/ThemeViewer.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
import { FC } from 'react'
import { Graph } from '../../interfaces'
import './index.css'
export interface ThemeViewerProps {
graph: Graph
chartSize: number
drawNodes: boolean
drawLinks: boolean
}
export declare const ThemeViewer: FC<ThemeViewerProps>

Просмотреть файл

@ -0,0 +1,114 @@
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Pivot, PivotItem } from '@fluentui/react'
import { FluentControls } from '../FluentControls'
import { D3Chart } from '../charts/D3'
import { VegaChart, charts } from '../charts/Vega'
import { D3Graph } from '../graphs/D3'
import './index.css'
export const ThemeViewer = ({ graph, chartSize, drawNodes, drawLinks }) => {
const common = {
width: chartSize,
height: chartSize * 0.75,
}
const graphs = [
_jsx(
D3Graph,
{ graph: graph, ...common, drawNodes: drawNodes, drawLinks: drawLinks },
'graph-1',
),
_jsx(
D3Graph,
{
graph: graph,
...common,
drawNodes: drawNodes,
drawLinks: drawLinks,
categoricalFill: true,
},
'graph-2',
),
_jsx(
D3Graph,
{
graph: graph,
...common,
drawNodes: drawNodes,
drawLinks: drawLinks,
sequentialFill: true,
},
'graph-3',
),
]
return _jsx(
'div',
{
className: 'theme-wrapper',
children: _jsxs(
Pivot,
{
children: [
_jsx(
PivotItem,
{
headerText: 'Thematic controls',
children: _jsx(FluentControls, {}, void 0),
},
void 0,
),
_jsx(
PivotItem,
{
headerText: 'Example visualizations',
children: _jsxs(
'div',
{
className: 'example-grid',
children: [
graphs.map((graph, i) =>
_jsx(
'div',
{ className: 'example-grid-item', children: graph },
`example-graph-${i}`,
),
),
_jsx(
'div',
{
className: 'example-grid-item',
children: _jsx(D3Chart, { ...common }, void 0),
},
`example-rar-chart`,
),
charts.map(chart =>
_jsx(
'div',
{
className: 'example-grid-item',
children: _jsx(
VegaChart,
{ type: chart, ...common },
void 0,
),
},
`example-chart-${chart}`,
),
),
],
},
void 0,
),
},
void 0,
),
],
},
void 0,
),
},
void 0,
)
}

8
packages/webapp/src/components/ThemeViewer/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/// <reference types="react" />
export declare const ThemeViewer: import('react-redux').ConnectedComponent<
import('react').FC<import('./ThemeViewer').ThemeViewerProps>,
import('react-redux').Omit<
import('./ThemeViewer').ThemeViewerProps,
'chartSize' | 'drawNodes' | 'drawLinks' | 'graph'
>
>

Просмотреть файл

@ -0,0 +1,12 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { connect } from 'react-redux'
import { ThemeViewer as ThemeViewerComponent } from './ThemeViewer'
export const ThemeViewer = connect(state => ({
chartSize: state.ui.chartSize,
drawNodes: state.ui.drawNodes,
drawLinks: state.ui.drawLinks,
graph: state.graph,
}))(ThemeViewerComponent)

9
packages/webapp/src/components/charts/D3/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,9 @@
import { FC } from 'react'
export interface ChartProps {
width?: number
height?: number
}
/**
* Simple chart using raw d3, intended to show how scale bindings work
*/
export declare const D3Chart: FC<ChartProps>

Просмотреть файл

@ -0,0 +1,175 @@
import { jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { SelectionState } from '@thematic/core'
import { chart, plotArea, axis, circle } from '@thematic/d3'
import { useThematic } from '@thematic/react'
import { axisLeft, axisBottom } from 'd3-axis'
import { scaleLinear } from 'd3-scale'
import { select } from 'd3-selection'
import { useLayoutEffect, useRef, useState, useMemo } from 'react'
const MARGIN = 25
const LEGEND_WIDTH = 20
const MIN_RADIUS = 5
const MAX_RADIUS = 15
const CIRCLE_FILL_OPACITY = 0.8
const DATA_LENGTH = 20
/**
* Simple chart using raw d3, intended to show how scale bindings work
*/
export const D3Chart = ({ width = 800, height = 600 }) => {
const theme = useThematic()
const ref = useRef(null)
const [svg, setSvg] = useState()
const [plot, setPlot] = useState()
// random data for scatterplot
// TODO: load in a meaningful dataset to demonstrate the scatterplot better?
const data = useMemo(() => {
return new Array(DATA_LENGTH).fill({}).map(() => ({
weight: 1 - Math.random() * 2,
count: Math.random(),
x: Math.random(),
y: Math.random(),
}))
}, [])
const plotHeight = useMemo(() => height - MARGIN * 2, [height])
const plotWidth = useMemo(() => width - MARGIN * 2 - LEGEND_WIDTH, [width])
const xScale = useMemo(() => scaleLinear().range([0, plotWidth]), [plotWidth])
const yScale = useMemo(
() => scaleLinear().range([plotHeight, 0]),
[plotHeight],
)
const cxScale = useMemo(
() => scaleLinear().range([MAX_RADIUS, plotWidth - MAX_RADIUS]),
[plotWidth],
)
const cyScale = useMemo(
() => scaleLinear().range([plotHeight - MAX_RADIUS, MAX_RADIUS]),
[plotHeight],
)
useLayoutEffect(() => {
if (ref) select(ref.current).select('*').remove()
const svg = select(ref.current).call(chart, theme, {
width,
height,
})
setSvg(svg)
}, [height, ref, theme, width])
useLayoutEffect(() => {
if (svg) {
svg.selectAll('g').remove()
const g = svg.append('g').call(plotArea, theme, {
// room for axes
marginBottom: MARGIN,
marginLeft: MARGIN,
marginTop: MARGIN,
marginRight: MARGIN + LEGEND_WIDTH,
})
setPlot(g)
}
}, [svg, theme])
useLayoutEffect(() => {
if (plot) {
plot.selectAll('circle').remove()
// create a series of random circles, with the fill bound to a sequential scale
const circ = theme.circle({
selectionState: d =>
d.weight < 50 ? SelectionState.Selected : SelectionState.Suppressed,
scaleBindings: {
fill: {
scale: theme.scales().diverging2([-1, 1]),
accessor: d => d.weight,
},
radius: {
scale: scaleLinear().range([MIN_RADIUS, MAX_RADIUS]),
accessor: d => d.count,
},
},
overrides: {
strokeWidth: 2,
strokeOpacity: 0.8,
stroke: '#222',
fillOpacity: CIRCLE_FILL_OPACITY,
},
})
plot
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.call(circle, circ)
.attr('cx', d => cxScale(d.x))
.attr('cy', d => cyScale(d.y))
}
}, [theme, plot, cxScale, cyScale, data])
useLayoutEffect(() => {
if (svg) {
// x axis
svg.append('g').call(axis, theme, axisBottom(xScale), {
attr: {
transform: `translate(${MARGIN}, ${plotHeight + MARGIN})`,
},
})
// y axis
svg.append('g').call(axis, theme, axisLeft(yScale), {
attr: {
transform: `translate(${MARGIN}, ${MARGIN})`,
},
})
}
}, [theme, svg, plotHeight, xScale, yScale])
// add a very simple legend to show the gradient
useLayoutEffect(() => {
if (svg) {
svg.select('.legend').remove()
const g = svg
.append('g')
.attr('class', 'legend')
.attr('transform', `translate(${plotWidth + MARGIN},${MARGIN})`)
g.append('rect')
.attr('width', LEGEND_WIDTH)
.attr('height', plotHeight)
.call(plotArea, theme)
const slices = new Array(plotHeight).fill(1)
const scale = theme.scales().diverging2([0, plotHeight])
g.selectAll('rect')
.data(slices)
.enter()
.append('rect')
.attr('width', LEGEND_WIDTH)
.attr('height', 1)
.attr('x', 1)
.attr('y', (d, i) => plotHeight - i)
.attr('fill', (d, i) => scale(i).hex())
.attr('fill-opacity', CIRCLE_FILL_OPACITY)
const labels = [
{
t: '1',
y: 8,
},
{
t: '0',
y: plotHeight / 2,
},
{
t: '-1',
y: plotHeight - 8,
},
]
g.selectAll('text')
.data(labels)
.enter()
.append('text')
.text(d => d.t)
.attr('fill', theme.axisTickLabels().fill().hex())
.attr('font-size', 10)
.attr('x', LEGEND_WIDTH / 2)
.attr('y', d => d.y)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
}
}, [theme, svg, plotWidth, plotHeight])
return _jsx('svg', { ref: ref }, void 0)
}

8
packages/webapp/src/components/charts/Vega/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
import { FC } from 'react'
export declare const charts: string[]
export interface VegaChartProps {
type: string
width?: number
height?: number
}
export declare const VegaChart: FC<VegaChartProps>

Просмотреть файл

@ -0,0 +1,52 @@
import { jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { useThematic } from '@thematic/react'
import { vega as decorator } from '@thematic/vega'
import { memo, useLayoutEffect, useRef, useMemo } from 'react'
import { parse, View } from 'vega'
import area from './specs/area.json'
import bar from './specs/bar.json'
import donut from './specs/donut.json'
import heatmap from './specs/heatmap.json'
import line from './specs/line.json'
import pyramid from './specs/pyramid.json'
import scatterPlot from './specs/scatter-plot.json'
import stackedArea from './specs/stacked-area.json'
import stackedBar from './specs/stacked-bar.json'
import sunburst from './specs/sunburst.json'
const specs = {
heatmap,
bar,
stackedBar,
line,
area,
stackedArea,
scatterPlot,
donut,
pyramid,
sunburst,
}
export const charts = Object.keys(specs)
export const VegaChart = memo(function VegaChart({
type,
width = 800,
height = 600,
}) {
const theme = useThematic()
const ref = useRef(null)
const view = useMemo(() => {
const spec = specs[type]
const merged = decorator(theme, spec, width, height)
const parsed = parse(merged)
return new View(parsed).renderer('svg')
}, [height, theme, type, width])
useLayoutEffect(() => {
if (ref.current != null) {
view.initialize(ref.current).run()
}
})
return _jsx('div', { ref: ref }, void 0)
})

Просмотреть файл

@ -8,23 +8,31 @@ import { memo, useLayoutEffect, useRef, useMemo, FC } from 'react'
import { parse, View } from 'vega'
export const charts = [
'heatmap',
'bar',
'stacked-bar',
'line',
'area',
'stacked-area',
'scatter-plot',
'donut',
'pyramid',
'sunburst',
]
import area from './specs/area.json'
import bar from './specs/bar.json'
import donut from './specs/donut.json'
import heatmap from './specs/heatmap.json'
import line from './specs/line.json'
import pyramid from './specs/pyramid.json'
import scatterPlot from './specs/scatter-plot.json'
import stackedArea from './specs/stacked-area.json'
import stackedBar from './specs/stacked-bar.json'
import sunburst from './specs/sunburst.json'
const specs = charts.reduce((acc, cur) => {
acc[cur] = require(`./specs/${cur}.json`)
return acc
}, {})
const specs = {
heatmap,
bar,
stackedBar,
line,
area,
stackedArea,
scatterPlot,
donut,
pyramid,
sunburst,
}
export const charts = Object.keys(specs)
export interface VegaChartProps {
type: string

12
packages/webapp/src/components/graphs/D3/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,12 @@
import { FC } from 'react'
import { Graph } from '../../../interfaces'
export interface GraphProps {
graph: Graph
width?: number
height?: number
drawNodes?: boolean
drawLinks?: boolean
categoricalFill?: boolean
sequentialFill?: boolean
}
export declare const D3Graph: FC<GraphProps>

Просмотреть файл

@ -0,0 +1,177 @@
import { jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { ScaleType, SelectionState } from '@thematic/core'
import {
circle,
line,
applyNominalAttrWithSignalState,
sequential,
chart,
plotArea,
} from '@thematic/d3'
import { useThematic } from '@thematic/react'
import { scaleLinear } from 'd3-scale'
import { select } from 'd3-selection'
import {
useLayoutEffect,
useState,
useRef,
useEffect,
useCallback,
} from 'react'
import { bounds } from '../../../util/graph'
export const D3Graph = ({
graph,
width = 800,
height = 600,
drawNodes = true,
drawLinks = true,
categoricalFill = false,
sequentialFill = false,
}) => {
const theme = useThematic()
const { nodes, edges } = graph
const ref = useRef(null)
const [nodeHover, setNodeHover] = useState(null)
const [nodeSelect, setNodeSelect] = useState(null)
const getNodeSelectionState = useCallback(
d => {
if (d.id === nodeHover) {
return SelectionState.Hovered
}
if (d.id === nodeSelect) {
return SelectionState.Selected
}
if (nodeSelect) {
return SelectionState.Suppressed
}
return SelectionState.Normal
},
[nodeHover, nodeSelect],
)
useLayoutEffect(() => {
const [xmin, xmax, ymin, ymax] = bounds(nodes)
const r = theme.node().radius()
const xScale = scaleLinear()
.domain([xmin, xmax])
.range([r, width - r])
const yScale = scaleLinear()
.domain([ymin, ymax])
.range([r, height - r])
select(ref.current).select('*').remove()
const svg = select(ref.current).call(chart, theme, { width, height })
const g = svg.append('g').call(plotArea, theme, {
on: {
mouseup: () => setNodeSelect(null),
},
})
const nmap = nodes.reduce((acc, cur) => {
acc[cur.id] = cur
return acc
}, {})
g.selectAll('line')
.data(edges)
.enter()
.append('line')
.attr('class', 'link')
.attr('x1', d => xScale(nmap[d.source].x))
.attr('x2', d => xScale(nmap[d.target].x))
.attr('y1', d => yScale(nmap[d.source].y))
.attr('y2', d => yScale(nmap[d.target].y))
.call(line, theme.link({ selectionState: SelectionState.Hidden }))
g.selectAll('.node')
.data(nodes)
.enter()
.append('circle')
.attr('class', 'node')
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.style('cursor', 'pointer')
.on('mouseover', d => setNodeHover(d.id))
.on('mouseout', () => setNodeHover(null))
.on('mouseup', d => setNodeSelect(d.id))
.call(circle, theme.node({ selectionState: SelectionState.Hidden }))
}, [theme, graph, width, height, nodes, edges])
useEffect(() => {
select(ref.current)
.selectAll('line')
.call(
line,
theme.link({
selectionState: drawLinks
? SelectionState.Normal
: SelectionState.Hidden,
}),
)
}, [theme, graph, drawLinks, width, height])
useEffect(() => {
select(ref.current)
.selectAll('.node')
.call(
circle,
theme.node({
selectionState: drawNodes
? SelectionState.Normal
: SelectionState.Hidden,
}),
)
}, [theme, graph, drawNodes, width, height])
useEffect(() => {
const n = select(ref.current).selectAll('.node')
if (drawNodes) {
if (categoricalFill) {
const unique = nodes.reduce((acc, cur) => {
if (cur.community != null) {
acc[cur.community] = true
}
return acc
}, {})
n.call(
applyNominalAttrWithSignalState,
'fill',
getNodeSelectionState,
d => d.community,
theme,
Object.keys(unique).length,
)
} else if (sequentialFill) {
const domain = nodes.map(node => node.weight).sort((a, b) => a - b)
n.call(
circle,
theme.node({
selectionState: getNodeSelectionState,
scaleBindings: {
fill: {
scale: sequential(theme, domain, ScaleType.Quantile),
accessor: d => d.weight,
},
},
}),
)
} else {
n.call(
circle,
theme.node({
selectionState: getNodeSelectionState,
}),
)
}
}
}, [
theme,
graph,
drawNodes,
width,
height,
nodeHover,
nodeSelect,
categoricalFill,
sequentialFill,
nodes,
getNodeSelectionState,
])
return _jsx('svg', { ref: ref }, void 0)
}

18
packages/webapp/src/components/svg/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,18 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { FC, ReactNode } from 'react'
export interface MarkProps {
children: ReactNode
size: number
}
export interface ThemedMarkProps {
config: any
size: number
}
export declare const Rect: FC<ThemedMarkProps>
export declare const Circle: FC<ThemedMarkProps>
export declare const Line: FC<ThemedMarkProps>
export declare const Arc: FC<ThemedMarkProps>
export declare const Text: FC<ThemedMarkProps>

Просмотреть файл

@ -0,0 +1,98 @@
import { jsx as _jsx } from 'react/jsx-runtime'
const svgAttrs = config => ({
fill: config.fill().hex(),
fillOpacity: config.fillOpacity(),
stroke: config.stroke().hex(),
strokeOpacity: config.strokeOpacity(),
strokeWidth: config.strokeWidth(),
})
const Mark = props => {
const { children, size } = props
return _jsx(
'svg',
{
width: size,
height: size,
style: { backgroundColor: 'none' },
children: children,
},
void 0,
)
}
export const Rect = props => {
const { config, size } = props
return _jsx(
Mark,
{
...props,
children: _jsx(
'rect',
{ width: size, height: size, ...svgAttrs(config) },
void 0,
),
},
void 0,
)
}
export const Circle = props => {
const { config, size } = props
return _jsx(
Mark,
{
...props,
children: _jsx(
'circle',
{ ...svgAttrs(config), cx: size / 2, cy: size / 2, r: size * 0.48 },
void 0,
),
},
void 0,
)
}
export const Line = props => {
const { config, size } = props
return _jsx(
Mark,
{
...props,
children: _jsx(
'line',
{ ...svgAttrs(config), x1: 0, x2: size, y1: size / 2, y2: size / 2 },
void 0,
),
},
void 0,
)
}
export const Arc = props => {
const { config, size } = props
const d = `M0 0 L ${size} 0 A ${size / 4} ${size / 5} 0 0 1 0 0`
return _jsx(
Mark,
{ ...props, children: _jsx('path', { ...svgAttrs(config), d: d }, void 0) },
void 0,
)
}
export const Text = props => {
const { config, size } = props
return _jsx(
Mark,
{
...props,
children: _jsx(
'text',
{
...svgAttrs(config),
fontSize: `${config.fontSize()}px`,
fontWeight: config.fontWeight(),
x: size / 2,
y: size * 0.67,
textAnchor: 'middle',
children: '10',
},
void 0,
),
},
void 0,
)
}

23
packages/webapp/src/data/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,23 @@
export declare const graph: {
nodes: (
| {
id: number
weight: number
community: number
x: number
y: number
}
| {
id: number
weight: number
x: number
y: number
community?: undefined
}
)[]
edges: {
source: number
target: number
weight: number
}[]
}

Просмотреть файл

@ -0,0 +1,6 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import graphData from './graph.json'
export const graph = graphData.graph

1
packages/webapp/src/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
import './index.css'

Просмотреть файл

@ -0,0 +1,17 @@
import { jsx as _jsx } from 'react/jsx-runtime'
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { App } from './App'
import { store, init } from './state'
import './index.css'
store.dispatch(init)
const root = document.createElement('div')
document.body.appendChild(root)
render(
_jsx(Provider, { store: store, children: _jsx(App, {}, void 0) }, void 0),
root,
)

21
packages/webapp/src/interfaces.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
export declare type NodeId = string | number
export interface Node {
id: NodeId
weight: number
community?: number
x: number
y: number
}
export interface Edge {
source: NodeId
target: NodeId
weight: number
}
export interface Graph {
nodes: Node[]
edges: Edge[]
}

Просмотреть файл

@ -0,0 +1 @@
export {}

Просмотреть файл

@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

До

Ширина:  |  Высота:  |  Размер: 2.6 KiB

65
packages/webapp/src/react-app-env.d.ts поставляемый
Просмотреть файл

@ -1,65 +0,0 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test'
PUBLIC_URL: string
}
}
declare module '*.bmp' {
const src: string
export default src
}
declare module '*.gif' {
const src: string
export default src
}
declare module '*.jpg' {
const src: string
export default src
}
declare module '*.jpeg' {
const src: string
export default src
}
declare module '*.png' {
const src: string
export default src
}
declare module '*.webp' {
const src: string
export default src
}
declare module '*.svg' {
import * as React from 'react'
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>
const src: string
export default src
}
declare module '*.module.css' {
const classes: { [key: string]: string }
export default classes
}
declare module '*.module.scss' {
const classes: { [key: string]: string }
export default classes
}
declare module '*.module.sass' {
const classes: { [key: string]: string }
export default classes
}

57
packages/webapp/src/state/actions/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,57 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Params, ColorBlindnessMode } from '@thematic/color'
import { Theme, ThemeListing } from '@thematic/core'
import { Dispatch } from 'redux'
import { Graph } from '../../interfaces'
export declare const themesLoaded: import('redux-actions').ActionFunction1<
ThemeListing[],
import('redux-actions').Action<ThemeListing[]>
>
export declare const themeInfoSelected: import('redux-actions').ActionFunction1<
ThemeListing,
import('redux-actions').Action<ThemeListing>
>
export declare const themeLoaded: import('redux-actions').ActionFunction1<
Theme,
import('redux-actions').Action<Theme>
>
export declare const themeEdited: import('redux-actions').ActionFunctionAny<
import('redux-actions').Action<any>
>
export declare const themeVariantToggled: import('redux-actions').ActionFunctionAny<
import('redux-actions').Action<any>
>
export declare const paramsChanged: import('redux-actions').ActionFunction1<
Params,
import('redux-actions').Action<Params>
>
export declare const chartSizeChanged: import('redux-actions').ActionFunction1<
number,
import('redux-actions').Action<number>
>
export declare const drawNodesChanged: import('redux-actions').ActionFunction1<
boolean,
import('redux-actions').Action<boolean>
>
export declare const drawLinksChanged: import('redux-actions').ActionFunction1<
boolean,
import('redux-actions').Action<boolean>
>
export declare const graphLoaded: import('redux-actions').ActionFunction1<
Graph,
import('redux-actions').Action<Graph>
>
export declare const scaleItemCountChanged: import('redux-actions').ActionFunction1<
number,
import('redux-actions').Action<number>
>
export declare const colorBlindnessModeChanged: import('redux-actions').ActionFunction1<
ColorBlindnessMode,
import('redux-actions').Action<ColorBlindnessMode>
>
export declare const themeSelected: (
themeInfo: ThemeListing,
) => (dispatch: Dispatch) => void

Просмотреть файл

@ -0,0 +1,23 @@
import { loadById } from '@thematic/core'
import { createAction } from 'redux-actions'
export const themesLoaded = createAction('App:Themes:Loaded')
export const themeInfoSelected = createAction('App:Themes:Selected')
export const themeLoaded = createAction('App:Theme:Loaded')
export const themeEdited = createAction('App:Theme:Edited')
export const themeVariantToggled = createAction('App:Theme:VariantToggled')
export const paramsChanged = createAction('App:CoolerPicker:Params:All:Changed')
export const chartSizeChanged = createAction('App:Viewer:ChartSize:Changed')
export const drawNodesChanged = createAction('App:Viewer:DrawNodes:Changed')
export const drawLinksChanged = createAction('App:Viewer:DrawLinks:Changed')
export const graphLoaded = createAction('App:Viewer:Graph:Loaded')
export const scaleItemCountChanged = createAction(
'App:Viewer:ScaleItems:Changed',
)
export const colorBlindnessModeChanged = createAction(
'App:Viewer:ColorBlindnessMode:Changed',
)
export const themeSelected = themeInfo => dispatch => {
const theme = loadById(themeInfo.id)
dispatch(themeInfoSelected(themeInfo))
dispatch(themeLoaded(theme))
}

47
packages/webapp/src/state/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,47 @@
/**
* State entry point for the app - kicks off async starter data, etc.
*/
export declare const init: any
export declare const store: import('redux').Store<
import('redux').EmptyObject & {
themes: import('@thematic/core').ThemeListing[]
themeInfo: import('@thematic/core').ThemeListing
theme: any
ui: import('redux').CombinedState<{
chartSize: number
drawNodes: boolean
drawLinks: boolean
scaleItemCount: number
colorBlindnessMode: import('@thematic/color').ColorBlindnessMode
}>
graph: {
nodes: import('../interfaces').Node[]
edges: import('../interfaces').Edge[]
}
params: {
accentHue: number
accentSaturation: number
accentLuminance: number
backgroundHueShift: number
backgroundLevel: number
nominalHueStep: number
}
},
| import('redux-actions').Action<any>
| import('redux-actions').Action<import('@thematic/core').ThemeListing[]>
| import('redux-actions').Action<import('@thematic/core').ThemeListing>
| import('redux-actions').Action<number>
| import('redux-actions').Action<boolean>
| import('redux-actions').Action<import('../interfaces').Graph>
| import('redux-actions').Action<import('@thematic/color').ColorBlindnessMode>
| import('redux-actions').Action<{
accentHue: number
accentSaturation: number
accentLuminance: number
backgroundHueShift: number
backgroundLevel: number
nominalHueStep: number
}>
> & {
dispatch: unknown
}

Просмотреть файл

@ -0,0 +1,22 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { defaultThemes } from '@thematic/core'
import { createStore, applyMiddleware, compose } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import { graph } from '../data'
import { themeSelected, graphLoaded } from './actions'
import { reducer } from './reducers'
/**
* State entry point for the app - kicks off async starter data, etc.
*/
export const init = dispatch => {
dispatch(themeSelected(defaultThemes[0]))
dispatch(graphLoaded(graph))
}
export const store = createStore(
reducer,
compose(applyMiddleware(thunk, logger)),
)

12
packages/webapp/src/state/reducers/handle.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,12 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { ActionFunctionAny, ReduxCompatibleReducer } from 'redux-actions'
/**
* Simpler helper for basic state items that just need to set a value
*/
export declare function handle<State>(
action: ActionFunctionAny<any>,
defaultState: State,
): ReduxCompatibleReducer<State, State>

Просмотреть файл

@ -0,0 +1,11 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { handleAction } from 'redux-actions'
/**
* Simpler helper for basic state items that just need to set a value
*/
export function handle(action, defaultState) {
return handleAction(action, (s, action) => action.payload, defaultState)
}

43
packages/webapp/src/state/reducers/index.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,43 @@
import { Action } from 'redux-actions'
import { Graph } from '../../interfaces'
export declare const reducer: import('redux').Reducer<
import('redux').CombinedState<{
themes: import('@thematic/core').ThemeListing[]
themeInfo: import('@thematic/core').ThemeListing
theme: any
ui: import('redux').CombinedState<{
chartSize: number
drawNodes: boolean
drawLinks: boolean
scaleItemCount: number
colorBlindnessMode: import('@thematic/color').ColorBlindnessMode
}>
graph: {
nodes: import('../../interfaces').Node[]
edges: import('../../interfaces').Edge[]
}
params: {
accentHue: number
accentSaturation: number
accentLuminance: number
backgroundHueShift: number
backgroundLevel: number
nominalHueStep: number
}
}>,
| Action<any>
| Action<import('@thematic/core').ThemeListing[]>
| Action<import('@thematic/core').ThemeListing>
| Action<number>
| Action<boolean>
| Action<Graph>
| Action<import('@thematic/color').ColorBlindnessMode>
| Action<{
accentHue: number
accentSaturation: number
accentLuminance: number
backgroundHueShift: number
backgroundLevel: number
nominalHueStep: number
}>
>

Просмотреть файл

@ -0,0 +1,64 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { defaultParams } from '@thematic/color'
import { defaultThemes, clone } from '@thematic/core'
import { combineReducers } from 'redux'
import { handleAction, handleActions } from 'redux-actions'
import {
themesLoaded,
themeInfoSelected,
themeLoaded,
themeEdited,
themeVariantToggled,
graphLoaded,
paramsChanged,
colorBlindnessModeChanged,
} from '../actions'
import { handle } from './handle'
import { ui } from './ui'
const themes = handle(themesLoaded, defaultThemes)
const themeInfo = handle(themeInfoSelected, defaultThemes[0])
const theme = handleActions(
{
[`${themeLoaded}`]: (s, action) => action.payload,
[`${themeEdited}`]: (s, action) => {
const { name, value } = action.payload
const update = {
[`${name}`]: value,
}
return clone(s, update)
},
[`${themeVariantToggled}`]: (s, action) => {
if (s.variant === 'light') {
return s.dark()
}
return s.light()
},
[`${colorBlindnessModeChanged}`]: (s, action) =>
s.colorBlindness(action.payload),
},
null,
)
const graph = handleAction(
graphLoaded,
(s, action) => {
// strip off the outer 'graph' root field used in rainbow hydra datasets
// return action.payload.graph
return {
nodes: action.payload.nodes,
edges: action.payload.edges,
}
},
{ nodes: [], edges: [] },
)
const params = handle(paramsChanged, defaultParams)
export const reducer = combineReducers({
themes,
themeInfo,
theme,
ui,
graph,
params,
})

17
packages/webapp/src/state/reducers/ui.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { ColorBlindnessMode } from '@thematic/color'
export declare const ui: import('redux').Reducer<
import('redux').CombinedState<{
chartSize: number
drawNodes: boolean
drawLinks: boolean
scaleItemCount: number
colorBlindnessMode: ColorBlindnessMode
}>,
| import('redux-actions').Action<number>
| import('redux-actions').Action<boolean>
| import('redux-actions').Action<ColorBlindnessMode>
>

Просмотреть файл

@ -0,0 +1,33 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { ColorBlindnessMode } from '@thematic/color'
import { combineReducers } from 'redux'
import {
chartSizeChanged,
drawNodesChanged,
drawLinksChanged,
scaleItemCountChanged,
colorBlindnessModeChanged,
} from '../actions'
import { handle } from './handle'
// chart width for the examples
// for the main charts this'll be a 4/3 aspect ratio
// for graphs we'll use it square
const DEFAULT_CHART_SIZE = 400
const chartSize = handle(chartSizeChanged, DEFAULT_CHART_SIZE)
const drawNodes = handle(drawNodesChanged, true)
const drawLinks = handle(drawLinksChanged, false)
const scaleItemCount = handle(scaleItemCountChanged, 10)
const colorBlindnessMode = handle(
colorBlindnessModeChanged,
ColorBlindnessMode.None,
)
export const ui = combineReducers({
chartSize,
drawNodes,
drawLinks,
scaleItemCount,
colorBlindnessMode,
})

10
packages/webapp/src/util/graph.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
/*!
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project.
*/
import { Node } from '../interfaces'
/**
* Compute the bounds for a node list
* @param nodes
*/
export declare function bounds(nodes: Node[]): [number, number, number, number]

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше