Skip to content

Commit

Permalink
feat: update to 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kevee committed Apr 26, 2024
1 parent 337eead commit b7e825d
Show file tree
Hide file tree
Showing 106 changed files with 10,031 additions and 18,049 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.0] - 2024-01-17

### Added

- Automated testing using Airtable block testing framework
- You can now save a table's configuration for re-use later

### Changed

- Used [faker](https://www.npmjs.com/package/@faker-js/faker) to generate random data
- Rewrote extension in TypeScript

## [0.0.4] - 2024-01-17

### Fixed
Expand Down
19 changes: 19 additions & 0 deletions babel.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const path = require('path')

module.exports = {
presets: [
'@babel/preset-typescript',
'@babel/preset-react',
['@babel/preset-env', { targets: { node: 'current' } }],
],
plugins: [
[
'babel-plugin-module-resolver',
{
alias: {
_test: path.resolve(__dirname, 'test'),
},
},
],
],
}
63 changes: 7 additions & 56 deletions frontend/app.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,17 @@
import {
Box,
Heading,
useBase,
useCursor,
Text,
colorUtils,
colors,
} from '@airtable/blocks/ui'
import React from 'react'
import GenerateRecordForm from './components/form'
import Container from './components/common/container'
import RandomRecordForm from './components/form'

/**
* The main app component. Fetches the currently selected table using
* the cursor, checks access to create records in the table, then
* renders the generate record form.
* @returns
*/
const RandomRecordGeneratorApp: React.FC = () => {
const base = useBase()
const cursor = useCursor()
const activeTable = cursor.activeTableId
const table = activeTable && base.getTableByIdIfExists(activeTable)

// There is no active table.
if (!table) {
return (
<Box padding={2}>
<Heading size="xlarge">Random Record Generator</Heading>
<Text textColor={colorUtils.getHexForColor(colors.GRAY_BRIGHT)}>
Select a table to get started.
</Text>
</Box>
)
}

const checkTablePermission = table.checkPermissionsForCreateRecord()

return (
<Box width="90%" marginY={2} marginX="auto">
{table && (
<>
<Text variant="paragraph">
Generate random records for <strong>{table.name}</strong>.{' '}
</Text>

{checkTablePermission.hasPermission ? (
<GenerateRecordForm table={table} />
) : (
<Text
variant="paragraph"
textColor={colorUtils.getHexForColor(colors.RED_DARK_1)}
>
{
// @ts-ignore
checkTablePermission.reasonDisplayString
}
</Text>
)}
</>
)}
</Box>
)
}
const RandomRecordGeneratorApp: React.FC = () => (
<Container>
<RandomRecordForm />
</Container>
)

export default RandomRecordGeneratorApp
29 changes: 29 additions & 0 deletions frontend/components/common/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import { Box, colors, colorUtils, Icon } from '@airtable/blocks/ui'

/**
* An alerting component that can be used to display messages to the user.
*/
const Alert: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<Box
role="alert"
marginY={2}
backgroundColor={colors.GRAY_LIGHT_2}
padding={2}
textColor={colorUtils.getHexForColor(colors.RED_DARK_1)}
display="flex"
borderRadius="large"
alignItems="center"
>
<Box flex={0} marginRight={3}>
<Icon
name="warning"
size={16}
fillColor={colorUtils.getHexForColor(colors.RED_DARK_1)}
/>
</Box>
<Box flex={1}>{children}</Box>
</Box>
)

export default Alert
13 changes: 13 additions & 0 deletions frontend/components/common/container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import { Box } from '@airtable/blocks/ui'

/**
* A simple container component to wrap the app in.
*/
const Container: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
<Box marginY={2} marginX="auto" paddingX={2} maxWidth={600}>
{children}
</Box>
)

export default Container
115 changes: 115 additions & 0 deletions frontend/components/form/configure-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { useEffect, useState } from 'react'
import {
Box,
Button,
FormField,
Select,
Text,
useBase,
} from '@airtable/blocks/ui'
import useRandomGenerator from '../hook/random-generator'

interface Props {
fieldConfiguration: FieldConfiguration
setFieldConfiguration: (fieldConfiguration: FieldConfiguration) => void
}

const ConfigureField: React.FC<Props> = ({
fieldConfiguration,
setFieldConfiguration,
}) => {
const [preview, setPreview] = useState(null)
const { getGeneratorsForType, getGenerator, previewGenerator } =
useRandomGenerator()
const { field, generator } = fieldConfiguration
const base = useBase()

const updatePreview = async () => {
const newPreview = await previewGenerator({
id: generator,
options: fieldConfiguration?.options,
field,
base,
})
setPreview(newPreview)
}

useEffect(() => {
if (!generator) {
return
}
updatePreview()
}, [field, generator, fieldConfiguration.options])

return (
<Box marginBottom={2} padding={2} border="default" borderRadius="default">
<Box display="flex" alignItems="end">
<FormField label={field.name} flex={1} marginBottom={0}>
<Select
options={[
...(generator ? [{ value: '_NONE', label: ' - None - ' }] : []),
...getGeneratorsForType(field.type).map((generator) => ({
value: generator.id,
label: generator.name,
})),
]}
value={generator}
onChange={(newValue) => {
const newGenerator = newValue.toString()
setFieldConfiguration({
...fieldConfiguration,
generator: newGenerator === '_NONE' ? null : newGenerator,
options: getGenerator(newGenerator)?.defaultOptions || {},
})
}}
/>
</FormField>
<Box flex={1}>
{getGenerator(fieldConfiguration.generator)?.optionsForm && (
<Box marginLeft={2}>
{React.createElement(
getGenerator(fieldConfiguration.generator)?.optionsForm,
{
field,
options: fieldConfiguration.options,
setOptions: (newOptions) => {
setFieldConfiguration({
...fieldConfiguration,
options: newOptions,
})
},
}
)}
</Box>
)}
</Box>
</Box>
{generator && (
<Box marginTop={2}>
<Text textColor="light" fontWeight={500}>
Preview
</Text>
<Box
padding={2}
border="thick"
backgroundColor="lightGray1"
display="flex"
>
<Box flex={1}>{preview}</Box>
<Box flex={0} textAlign="right">
<Button
icon="redo"
variant="secondary"
onClick={updatePreview}
aria-label="Regenerate preview"
/>
</Box>
</Box>
</Box>
)}
</Box>
)
return null
}

export default ConfigureField
36 changes: 36 additions & 0 deletions frontend/components/form/configure-fields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import ConfigureField from './configure-field'

interface Props {
fieldConfigurations: FieldConfiguration[]
setFieldConfigurations: (fieldConfiguration: FieldConfiguration[]) => void
}

const ConfigureFields: React.FC<Props> = ({
fieldConfigurations,
setFieldConfigurations,
}) => {
return (
<>
{fieldConfigurations.map((fieldConfiguration) => (
<ConfigureField
key={fieldConfiguration.field.id}
fieldConfiguration={fieldConfiguration}
setFieldConfiguration={(newConfiguration) => {
const newFieldConfigurations = [...fieldConfigurations].map(
(config) => {
if (config.field.id === fieldConfiguration.field.id) {
return newConfiguration
}
return config
}
)
setFieldConfigurations(newFieldConfigurations)
}}
/>
))}
</>
)
}

export default ConfigureFields
81 changes: 0 additions & 81 deletions frontend/components/form/field-select.tsx

This file was deleted.

Loading

0 comments on commit b7e825d

Please sign in to comment.