@airtable/blocks/ui#Box JavaScript Examples
The following examples show how to use
@airtable/blocks/ui#Box.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: util.js From neighbor-express with MIT License | 7 votes |
export function Accordion({ title, children }) {
const [isOpen, setIsOpen] = useState(false);
return (
<Box>
<TextButton
marginY={1}
onClick={() => setIsOpen(!isOpen)}
icon={isOpen ? "chevronDown" : "chevronRight"}
>
{title}
</TextButton>
{isOpen && children}
</Box>
);
}
Example #2
Source File: FullscreenBox.js From apps-base-schema with MIT License | 6 votes |
/**
* Utility component to wrap children in a container that fills the full container.
*
* @param {Element} children
*/
export default function FullscreenBox({children, ...props}) {
return (
<Box position="absolute" top="0" bottom="0" left="0" right="0" {...props}>
{children}
</Box>
);
}
Example #3
Source File: UpdateRecordsApp.js From apps-update-records with MIT License | 6 votes |
// Container element which centers its children.
function Container({children}) {
return (
<Box
position="absolute"
top={0}
bottom={0}
left={0}
right={0}
display="flex"
alignItems="center"
justifyContent="center"
>
{children}
</Box>
);
}
Example #4
Source File: todo-app.js From apps-todo-list with MIT License | 6 votes |
function AddTaskForm({table}) {
const [taskName, setTaskName] = useState('');
function onInputChange(event) {
setTaskName(event.currentTarget.value);
}
function onSubmit(event) {
event.preventDefault();
table.createRecordAsync({
[table.primaryField.id]: taskName,
});
setTaskName('');
}
// check whether or not the user is allowed to create records with values in the primary field.
// if not, disable the form.
const isFormEnabled = table.hasPermissionToCreateRecord({
[table.primaryField.id]: undefined,
});
return (
<form onSubmit={onSubmit}>
<Box display="flex" padding={3}>
<Input
flex="auto"
value={taskName}
placeholder="New task"
onChange={onInputChange}
disabled={!isFormEnabled}
/>
<Button variant="primary" marginLeft={2} type="submit" disabled={!isFormEnabled}>
Add
</Button>
</Box>
</form>
);
}
Example #5
Source File: todo-app.js From apps-todo-list with MIT License | 6 votes |
function Task({record, table, doneField}) {
return (
<Box
fontSize={4}
paddingX={3}
paddingY={2}
marginRight={-2}
borderBottom="default"
display="flex"
alignItems="center"
>
<TaskDoneCheckbox table={table} record={record} doneField={doneField} />
<a
style={{cursor: 'pointer', flex: 'auto', padding: 8}}
onClick={() => {
expandRecord(record);
}}
>
{record.name || 'Unnamed record'}
</a>
<TaskDeleteButton table={table} record={record} />
</Box>
);
}
Example #6
Source File: index.js From neighbor-express with MIT License | 6 votes |
function MainUIComponent() {
return (
<Box padding={3}>
<StepWizard>
<RefreshQueueStep />
<SendMessagesStep />
</StepWizard>
</Box>
);
}
Example #7
Source File: index.js From apps-simple-chart with MIT License | 6 votes |
function Settings({table}) {
return (
<Box display="flex" padding={3} borderBottom="thick">
<FormField label="Table" width="33.33%" paddingRight={1} marginBottom={0}>
<TablePickerSynced globalConfigKey={GlobalConfigKeys.TABLE_ID} />
</FormField>
{table && (
<FormField label="View" width="33.33%" paddingX={1} marginBottom={0}>
<ViewPickerSynced table={table} globalConfigKey={GlobalConfigKeys.VIEW_ID} />
</FormField>
)}
{table && (
<FormField label="X-axis field" width="33.33%" paddingLeft={1} marginBottom={0}>
<FieldPickerSynced
table={table}
globalConfigKey={GlobalConfigKeys.X_FIELD_ID}
/>
</FormField>
)}
</Box>
);
}
Example #8
Source File: index.js From apps-print-records with MIT License | 6 votes |
// The toolbar contains the view picker and print button.
function Toolbar({table}) {
return (
<Box className="print-hide" padding={2} borderBottom="thick" display="flex">
<ViewPickerSynced table={table} globalConfigKey={GlobalConfigKeys.VIEW_ID} />
<Button
onClick={() => {
// Inject CSS to hide elements with the "print-hide" class name
// when the app gets printed. This lets us hide the toolbar from
// the print output.
printWithoutElementsWithClass('print-hide');
}}
marginLeft={2}
>
Print
</Button>
</Box>
);
}
Example #9
Source File: index.js From apps-print-records with MIT License | 6 votes |
function PrintRecordsApp() {
const base = useBase();
const globalConfig = useGlobalConfig();
// We want to render the list of records in this table.
const table = base.getTableByName('Collections');
// The view ID is stored in globalConfig using ViewPickerSynced.
const viewId = globalConfig.get(GlobalConfigKeys.VIEW_ID);
// The view may have been deleted, so we use getViewByIdIfExists
// instead of getViewById. getViewByIdIfExists will return null
// if the view doesn't exist.
const view = table.getViewByIdIfExists(viewId);
return (
<div>
<Toolbar table={table} />
<Box margin={3}>
<Report view={view} />
</Box>
</div>
);
}
Example #10
Source File: index.js From neighbor-express with MIT License | 6 votes |
function ComponentWithSettings() {
const [showSettings, setShowSettings] = useState(false);
useSettingsButton(function () {
setShowSettings(!showSettings);
});
return (
<>
{showSettings && (
<SettingsComponent exit={() => setShowSettings(false)} />
)}
{!showSettings && (
<Box padding={3}>
<SyncData />
</Box>
)}
</>
);
}
Example #11
Source File: Flashcard.js From apps-flashcard with MIT License | 6 votes |
export default function Flashcard({record, settings, shouldShowAnswer}) {
return (
<Box display="flex" flex="auto" overflowY="auto">
<Box marginY="auto" padding={5} width="100%">
<Box display="flex" paddingBottom={2} borderBottom="default">
{record ? (
<CustomCellRenderer field={settings.questionField} record={record} />
) : (
<Text width="100%" size="xlarge" textAlign="center">
All done!
</Text>
)}
</Box>
<Box display="flex" paddingTop={2}>
{settings.answerField && record && shouldShowAnswer ? (
<CustomCellRenderer field={settings.answerField} record={record} />
) : null}
</Box>
</Box>
</Box>
);
}
Example #12
Source File: settings.js From neighbor-express with MIT License | 6 votes |
export function SettingsComponent({ exit }) {
const globalConfig = useGlobalConfig();
return (
<Box padding={3}>
<Button variant="primary" onClick={exit} style={{ float: "right" }}>
Exit settings
</Button>
<h1> Settings </h1>
<p>
You probably won't need to do anything here unless you're just starting
out.
</p>
<FormField label="Galaxy Digital API Key">
<InputSynced globalConfigKey="GALAXY_DIGITAL_API_KEY" />
</FormField>
</Box>
);
}
Example #13
Source File: HighlightWrapper.js From apps-base-schema with MIT License | 6 votes |
/**
* Displays a small tooltip in the bottom-left corner that shows the type of the currently
* hovered link or field.
*
* Uses `ReactDOM#createPortal` to lift the HTMLElements out of SVG world.
*
* @param {string} props.text
*/
function Tooltip({text}) {
const tooltipRoot = document.getElementById('index');
return ReactDOM.createPortal(
<Box
position="absolute"
bottom={0}
left={0}
margin={2}
backgroundColor={colors.GRAY_DARK_1}
borderRadius="default"
>
<Text padding={2} textColor="white">
{text}
</Text>
</Box>,
tooltipRoot,
);
}
Example #14
Source File: settings.js From neighbor-express with MIT License | 5 votes |
function TableTemplateVariables({ tableName }) {
const globalConfig = useGlobalConfig();
if (globalConfig.get(["template_variables", tableName]) === undefined) {
globalConfig.setAsync(["template_variables", tableName], {});
}
const base = useBase();
const table = base.getTableByNameIfExists(tableName);
const tableNameToSendgrid = {
Deliveries: "delivery",
Volunteers: "volunteer",
};
return (
<Box padding={3}>
<Text>
{" "}
You can use these fields from the {table.name} table in sendgrid{" "}
</Text>
<ul>
{Object.keys(globalConfig.get(["template_variables", table.name])).map(
(f_id) => {
const field = table.getFieldIfExists(f_id);
// this should be deletable
const sendgridValue = globalConfig.get([
"template_variables",
table.name,
f_id,
]);
const sendgridFormat = `{{${tableNameToSendgrid[tableName]}.${sendgridValue}}}`;
const removeLink = (
<TextButton
onClick={() => {
globalConfig.setAsync(
["template_variables", table.name, f_id],
undefined
);
}}
>
(remove)
</TextButton>
);
return (
<li key={f_id}>
{" "}
{field.name} -> {sendgridFormat} {removeLink}
</li>
);
}
)}
</ul>
<AddTemplateVariableDialog table={table} />
</Box>
);
}
Example #15
Source File: settings.js From neighbor-express with MIT License | 5 votes |
function EmailTypeSettings({ emailType }) {
const globalConfig = useGlobalConfig();
const audienceOptions = [
{ value: "volunteer", label: "Volunteer" },
{ value: "recipient", label: "Delivery Recipient" },
];
const deliveriesTable = useBase().getTableByNameIfExists("Deliveries");
const triggerField = deliveriesTable.getFieldIfExists(
globalConfig.get("trigger_column")
);
const stageOptions = triggerField?.options.choices.map((choice) => {
return {
value: choice.id,
label: choice.name,
};
});
function deleteMe() {
globalConfig.setAsync(["email_types", emailType], undefined);
}
return (
<Box padding={3}>
<TextButton onClick={deleteMe} icon="trash" style={{ float: "right" }}>
Delete
</TextButton>
<Accordion title={emailType}>
<InputSetter
label="Sendgrid Template ID"
description="Find this in the Sendgrid UI. Looks like d-ea13bf1f408947829fa19779eade8250"
keyOrPath={["email_types", emailType, "sendgrid_template"]}
/>
<FormField
label="Send to..."
description="Who should this email be sent to?"
>
<SelectButtonsSynced
globalConfigKey={["email_types", emailType, "audience"]}
options={audienceOptions}
width="320px"
/>
</FormField>
<FormField
label="Send when..."
description={`Email will be sent when ${triggerField.name} column is...`}
>
<SelectSynced
globalConfigKey={["email_types", emailType, "stage"]}
options={stageOptions}
width="320px"
/>
</FormField>
</Accordion>
</Box>
);
}
Example #16
Source File: refresh-queue.js From neighbor-express with MIT License | 5 votes |
export function RefreshQueueStep({ nextStep }) {
const [result, setResult] = useState(undefined);
const [warnings, setWarnings] = useState([]);
function addWarning(newWarning) {
if (newWarning) {
setWarnings(warnings.concat(newWarning));
}
}
async function refreshQueue() {
// Clear out the warnings from any previous run
setWarnings([]);
let { messagesToCreate, warnings } = await computeMessagesToCreate();
setWarnings(warnings);
if (messagesToCreate.length == 0) {
setResult(`No new messages to enqueue.`);
return;
}
await batchedCreate(messagesToCreate);
setResult(`Enqueued ${messagesToCreate.length} new messages`);
}
return (
<Box>
<h2> Step 1: Refresh Queue </h2>
<Text>
{" "}
Open the Messages tab to get started. Click below to refresh queue
(won't send emails yet).{" "}
</Text>
<Button variant="primary" margin={3} onClick={refreshQueue}>
Refresh queue
</Button>
{result && (
<Box>
<Button
style={{ float: "right" }}
marginX={3}
variant="primary"
onClick={nextStep}
>
Continue
</Button>
<Text> {result} </Text>
<WarningAccordion warnings={warnings} />
</Box>
)}
</Box>
);
}
Example #17
Source File: sync-data.js From neighbor-express with MIT License | 5 votes |
export function SyncData() {
const messagesTable = useBase().getTable(DESTINATION_TABLE);
const records = useRecords(messagesTable);
const [completed, setCompleted] = useState(false);
const [syncing, setSyncing] = useState(false);
const [succesfulCount, setSuccesfulCount] = useState(0);
const [syncError, setSyncError] = useState(null);
async function syncData() {
setCompleted(false);
setSyncing(true);
const { total, err } = await SyncVolunteerData(messagesTable, records);
setSuccesfulCount(total);
setSyncError(err);
setSyncing(false);
setCompleted(true);
}
return (
<Box>
<h2> Sync Galaxy Digital Data </h2>
{syncing && !completed && <Loader />}
{!syncing && (
<>
{completed && !syncError && (
<p>Successfully updated all volunteer data</p>
)}
{completed && syncError && (
<p>
`Sync completed for ${succesfulCount} with error ${syncError}`
</p>
)}
<Box
margin={2}
display="flex"
flexDirection="row"
justifyContent="flex-start"
alignItems="center"
>
<Button
marginX={2}
variant="primary"
onClick={syncData}
disabled={syncing}
>
Sync
</Button>
</Box>
</>
)}
</Box>
);
}
Example #18
Source File: index.js From apps-wikipedia-enrichment with MIT License | 5 votes |
function WikipediaEnrichmentApp() {
const base = useBase();
const table = base.getTableByName(TABLE_NAME);
const titleField = table.getFieldByName(TITLE_FIELD_NAME);
// load the records ready to be updated
// we only need to load the word field - the others don't get read, only written to.
const records = useRecords(table, {fields: [titleField]});
// keep track of whether we have up update currently in progress - if there is, we want to hide
// the update button so you can't have two updates running at once.
const [isUpdateInProgress, setIsUpdateInProgress] = useState(false);
// check whether we have permission to update our records or not. Any time we do a permissions
// check like this, we can pass in undefined for values we don't yet know. Here, as we want to
// make sure we can update the summary and image fields, we make sure to include them even
// though we don't know the values we want to use for them yet.
const permissionCheck = table.checkPermissionsForUpdateRecord(undefined, {
[EXTRACT_FIELD_NAME]: undefined,
[IMAGE_FIELD_NAME]: undefined,
});
async function onButtonClick() {
setIsUpdateInProgress(true);
const recordUpdates = await getExtractAndImageUpdatesAsync(table, titleField, records);
await updateRecordsInBatchesAsync(table, recordUpdates);
setIsUpdateInProgress(false);
}
return (
<Box
// center the button/loading spinner horizontally and vertically.
position="absolute"
top="0"
bottom="0"
left="0"
right="0"
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
>
{isUpdateInProgress ? (
<Loader />
) : (
<Fragment>
<Button
variant="primary"
onClick={onButtonClick}
disabled={!permissionCheck.hasPermission}
marginBottom={3}
>
Update summaries and images
</Button>
{!permissionCheck.hasPermission &&
// when we don't have permission to perform the update, we want to tell the
// user why. `reasonDisplayString` is a human-readable string that will
// explain why the button is disabled.
permissionCheck.reasonDisplayString}
</Fragment>
)}
</Box>
);
}
Example #19
Source File: index.js From apps-url-preview with MIT License | 5 votes |
// Shows a preview, or a dialog that displays information about what
// kind of services (URLs) are supported by this app.
function RecordPreviewWithDialog({
activeTable,
selectedRecordId,
selectedFieldId,
setIsSettingsOpen,
}) {
const [isDialogOpen, setIsDialogOpen] = useState(false);
// Close the dialog when the selected record is changed.
// The new record might have a preview, so we don't want to hide it behind this dialog.
useEffect(() => {
setIsDialogOpen(false);
}, [selectedRecordId]);
return (
<Fragment>
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
>
<RecordPreview
activeTable={activeTable}
selectedRecordId={selectedRecordId}
selectedFieldId={selectedFieldId}
setIsDialogOpen={setIsDialogOpen}
setIsSettingsOpen={setIsSettingsOpen}
/>
</Box>
{isDialogOpen && (
<Dialog onClose={() => setIsDialogOpen(false)} maxWidth={400}>
<Dialog.CloseButton />
<Heading size="small">Supported services</Heading>
<Text marginTop={2}>Previews are supported for these services:</Text>
<Text marginTop={2}>
<Link
href="https://support.airtable.com/hc/en-us/articles/205752117-Creating-a-base-share-link-or-a-view-share-link"
target="_blank"
>
Airtable share links
</Link>
, Figma, SoundCloud, Spotify, Vimeo, YouTube, Loom share links, Google Drive
share links, Google Docs, Google Sheets, Google Slides
</Text>
<Link
marginTop={2}
href="https://airtable.com/shrQSwIety6rqfJZX"
target="_blank"
>
Request a new service
</Link>
</Dialog>
)}
</Fragment>
);
}
Example #20
Source File: SettingsForm.js From apps-url-preview with MIT License | 5 votes |
function SettingsForm({setIsSettingsOpen}) {
const globalConfig = useGlobalConfig();
const {
isValid,
message,
settings: {isEnforced, urlTable},
} = useSettings();
const canUpdateSettings = globalConfig.hasPermissionToSet();
return (
<Box
position="absolute"
top={0}
bottom={0}
left={0}
right={0}
display="flex"
flexDirection="column"
>
<Box flex="auto" padding={4} paddingBottom={2}>
<Heading marginBottom={3}>Settings</Heading>
<FormField label="">
<Switch
aria-label="When enabled, the app will only show previews for the specified table and field, regardless of what field is selected."
value={isEnforced}
onChange={value => {
globalConfig.setAsync(ConfigKeys.IS_ENFORCED, value);
}}
disabled={!canUpdateSettings}
label="Use a specific field for previews"
/>
<Text paddingY={1} textColor="light">
{isEnforced
? 'The app will show previews for the selected record in grid view if the table has a supported URL in the specified field.'
: 'The app will show previews if the selected cell in grid view has a supported URL.'}
</Text>
</FormField>
{isEnforced && (
<FormField label="Preview table">
<TablePickerSynced globalConfigKey={ConfigKeys.URL_TABLE_ID} />
</FormField>
)}
{isEnforced && urlTable && (
<FormField label="Preview field">
<FieldPickerSynced
table={urlTable}
globalConfigKey={ConfigKeys.URL_FIELD_ID}
allowedTypes={allowedUrlFieldTypes}
/>
</FormField>
)}
</Box>
<Box display="flex" flex="none" padding={3} borderTop="thick">
<Box
flex="auto"
display="flex"
alignItems="center"
justifyContent="flex-end"
paddingRight={2}
>
<Text textColor="light">{message}</Text>
</Box>
<Button
disabled={!isValid}
size="large"
variant="primary"
onClick={() => setIsSettingsOpen(false)}
>
Done
</Button>
</Box>
</Box>
);
}
Example #21
Source File: todo-app.js From apps-todo-list with MIT License | 5 votes |
export default function TodoApp() {
const base = useBase();
// Read the user's choice for which table and view to use from globalConfig.
const globalConfig = useGlobalConfig();
const tableId = globalConfig.get('selectedTableId');
const viewId = globalConfig.get('selectedViewId');
const doneFieldId = globalConfig.get('selectedDoneFieldId');
const table = base.getTableByIdIfExists(tableId);
const view = table ? table.getViewByIdIfExists(viewId) : null;
const doneField = table ? table.getFieldByIdIfExists(doneFieldId) : null;
// Don't need to fetch records if doneField doesn't exist (the field or it's parent table may
// have been deleted, or may not have been selected yet.)
const records = useRecords(doneField ? view : null, {
fields: doneField ? [table.primaryField, doneField] : [],
});
const tasks = records
? records.map(record => {
return <Task key={record.id} record={record} table={table} doneField={doneField} />;
})
: null;
return (
<div>
<Box padding={3} borderBottom="thick">
<FormField label="Table">
<TablePickerSynced globalConfigKey="selectedTableId" />
</FormField>
<FormField label="View">
<ViewPickerSynced table={table} globalConfigKey="selectedViewId" />
</FormField>
<FormField label="Field" marginBottom={0}>
<FieldPickerSynced
table={table}
globalConfigKey="selectedDoneFieldId"
placeholder="Pick a 'done' field..."
allowedTypes={[FieldType.CHECKBOX]}
/>
</FormField>
</Box>
{tasks}
{table && doneField && <AddTaskForm table={table} />}
</div>
);
}
Example #22
Source File: index.js From apps-simple-chart with MIT License | 5 votes |
function SimpleChartApp() {
const base = useBase();
const globalConfig = useGlobalConfig();
const tableId = globalConfig.get(GlobalConfigKeys.TABLE_ID);
const table = base.getTableByIdIfExists(tableId);
const viewId = globalConfig.get(GlobalConfigKeys.VIEW_ID);
const view = table ? table.getViewByIdIfExists(viewId) : null;
const xFieldId = globalConfig.get(GlobalConfigKeys.X_FIELD_ID);
const xField = table ? table.getFieldByIdIfExists(xFieldId) : null;
const records = useRecords(view);
const data = records && xField ? getChartData({records, xField}) : null;
return (
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
display="flex"
flexDirection="column"
>
<Settings table={table} />
{data && (
<Box position="relative" flex="auto" padding={3}>
<Bar
data={data}
options={{
maintainAspectRatio: false,
scales: {
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
legend: {
display: false,
},
}}
/>
</Box>
)}
</Box>
);
}
Example #23
Source File: index.js From apps-flashcard with MIT License | 5 votes |
/**
* A simple flashcard app that displays records from a chosen view.
* Supports choosing a question field which is displayed by default and an optional answer field that
* is hidden until shown by the user.
*/
function FlashcardApp() {
const {isValid, message, settings} = useSettings();
const [isSettingsVisible, setIsSettingsVisible] = useState(false);
useSettingsButton(() => {
if (!isSettingsVisible) {
viewport.enterFullscreenIfPossible();
}
setIsSettingsVisible(!isSettingsVisible);
});
// Open the SettingsForm whenever the settings are not valid
useEffect(() => {
if (!isValid) {
setIsSettingsVisible(true);
}
}, [isValid]);
const records = useRecords(settings.view);
return (
<Box position="absolute" top="0" left="0" bottom="0" right="0" display="flex">
<Box display="flex" flexDirection="column" flex="auto">
{isValid ? (
<FlashcardContainer records={records} settings={settings} />
) : (
<Box display="flex" flex="auto" alignItems="center" justifyContent="center">
<Text textColor="light">{message}</Text>
</Box>
)}
</Box>
{isSettingsVisible && (
<SettingsForm setIsSettingsVisible={setIsSettingsVisible} settings={settings} />
)}
</Box>
);
}
Example #24
Source File: SettingsForm.js From apps-flashcard with MIT License | 5 votes |
export default function SettingsForm({setIsSettingsVisible, settings}) {
return (
<Box
flex="none"
display="flex"
flexDirection="column"
width="300px"
backgroundColor="white"
maxHeight="100vh"
borderLeft="thick"
>
<Box
flex="auto"
display="flex"
flexDirection="column"
minHeight="0"
padding={3}
overflowY="auto"
>
<Heading marginBottom={3}>Settings</Heading>
<FormField label="Table">
<TablePickerSynced globalConfigKey={ConfigKeys.TABLE_ID} />
</FormField>
{settings.table && (
<Fragment>
<FormField
label="View"
description="Only records in this view will be used"
>
<ViewPickerSynced
table={settings.table}
globalConfigKey={ConfigKeys.VIEW_ID}
/>
</FormField>
<FormField label="Question field">
<FieldPickerSynced
table={settings.table}
globalConfigKey={ConfigKeys.QUESTION_FIELD_ID}
/>
</FormField>
<FormField label="Answer field (optional)">
<FieldPickerSynced
table={settings.table}
shouldAllowPickingNone={true}
globalConfigKey={ConfigKeys.ANSWER_FIELD_ID}
/>
</FormField>
</Fragment>
)}
</Box>
<Box
flex="none"
display="flex"
justifyContent="flex-end"
paddingY={3}
marginX={3}
borderTop="thick"
>
<Button variant="primary" size="large" onClick={() => setIsSettingsVisible(false)}>
Done
</Button>
</Box>
</Box>
);
}
Example #25
Source File: SettingsForm.js From apps-base-schema with MIT License | 5 votes |
/**
* Settings form component.
* Allows the user to toggle link types.
*
* @param {Function} props.setShouldShowSettings Function to toggle settings visibility
*/
export default function SettingsForm({setShouldShowSettings}) {
return (
<FullscreenBox
left="initial" // show settings in right sidebar
width="360px"
backgroundColor="white"
display="flex"
flexDirection="column"
borderLeft="thick"
>
<Box flex="auto" display="flex" justifyContent="center" overflow="auto">
<Box paddingTop={4} paddingBottom={2} maxWidth={300} flex="auto">
<Heading marginBottom={2}>Settings</Heading>
<SwitchSynced
marginY={3}
label="Show linked record relationships"
globalConfigKey={[
ConfigKeys.ENABLED_LINKS_BY_TYPE,
FieldType.MULTIPLE_RECORD_LINKS,
]}
/>
<SwitchSynced
marginY={3}
label="Show formula relationships"
globalConfigKey={[ConfigKeys.ENABLED_LINKS_BY_TYPE, FieldType.FORMULA]}
/>
<SwitchSynced
marginY={3}
label="Show rollup relationships"
globalConfigKey={[ConfigKeys.ENABLED_LINKS_BY_TYPE, FieldType.ROLLUP]}
/>
<SwitchSynced
marginY={3}
label="Show lookup relationships"
globalConfigKey={[
ConfigKeys.ENABLED_LINKS_BY_TYPE,
FieldType.MULTIPLE_LOOKUP_VALUES,
]}
/>
<SwitchSynced
marginY={3}
label="Show count relationships"
globalConfigKey={[ConfigKeys.ENABLED_LINKS_BY_TYPE, FieldType.COUNT]}
/>
</Box>
</Box>
<Box
flex="none"
borderTop="thick"
display="flex"
justifyContent="flex-end"
alignItems="center"
>
<Button
margin={3}
variant="primary"
size="large"
onClick={() => setShouldShowSettings(false)}
>
Done
</Button>
</Box>
</FullscreenBox>
);
}
Example #26
Source File: FullscreenBox.js From apps-base-schema with MIT License | 5 votes |
FullscreenBox.propTypes = Box.propTypes;
Example #27
Source File: index.js From apps-url-preview with MIT License | 4 votes |
// How this app chooses a preview to show:
//
// Without a specified Table & Field:
//
// - When the user selects a cell in grid view and the field's content is
// a supported preview URL, the app uses this URL to construct an embed
// URL and inserts this URL into an iframe.
//
// To Specify a Table & Field:
//
// - The user may use "Settings" to toggle a specified table and specified
// field constraint. If the constraint switch is set to "Yes",he user must
// set a specified table and specified field for URL previews.
//
// With a specified table & specified field:
//
// - When the user selects a cell in grid view and the active table matches
// the specified table or when the user opens a record from a button field
// in the specified table:
// The app looks in the selected record for the
// specified field containing a supported URL (e.g. https://www.youtube.com/watch?v=KYz2wyBy3kc),
// and uses this URL to construct an embed URL and inserts this URL into
// an iframe.
//
function UrlPreviewApp() {
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
useSettingsButton(() => setIsSettingsOpen(!isSettingsOpen));
const {
isValid,
settings: {isEnforced, urlTable},
} = useSettings();
// Caches the currently selected record and field in state. If the user
// selects a record and a preview appears, and then the user de-selects the
// record (but does not select another), the preview will remain. This is
// useful when, for example, the user resizes the apps pane.
const [selectedRecordId, setSelectedRecordId] = useState(null);
const [selectedFieldId, setSelectedFieldId] = useState(null);
const [recordActionErrorMessage, setRecordActionErrorMessage] = useState('');
// cursor.selectedRecordIds and selectedFieldIds aren't loaded by default,
// so we need to load them explicitly with the useLoadable hook. The rest of
// the code in the component will not run until they are loaded.
useLoadable(cursor);
// Update the selectedRecordId and selectedFieldId state when the selected
// record or field change.
useWatchable(cursor, ['selectedRecordIds', 'selectedFieldIds'], () => {
// If the update was triggered by a record being de-selected,
// the current selectedRecordId will be retained. This is
// what enables the caching described above.
if (cursor.selectedRecordIds.length > 0) {
// There might be multiple selected records. We'll use the first
// one.
setSelectedRecordId(cursor.selectedRecordIds[0]);
}
if (cursor.selectedFieldIds.length > 0) {
// There might be multiple selected fields. We'll use the first
// one.
setSelectedFieldId(cursor.selectedFieldIds[0]);
}
});
// Close the record action error dialog whenever settings are opened or the selected record
// is updated. (This means you don't have to close the modal to see the settings, or when
// you've opened a different record.)
useEffect(() => {
setRecordActionErrorMessage('');
}, [isSettingsOpen, selectedRecordId]);
// Register a callback to be called whenever a record action occurs (via button field)
// useCallback is used to memoize the callback, to avoid having to register/unregister
// it unnecessarily.
const onRecordAction = useCallback(
data => {
// Ignore the event if settings are already open.
// This means we can assume settings are valid (since we force settings to be open if
// they are invalid).
if (!isSettingsOpen) {
if (isEnforced) {
if (data.tableId === urlTable.id) {
setSelectedRecordId(data.recordId);
} else {
// Record is from a mismatching table.
setRecordActionErrorMessage(
`This app is set up to preview URLs using records from the "${urlTable.name}" table, but was opened from a different table.`,
);
}
} else {
// Preview is not supported in this case, as we wouldn't know what field to preview.
// Show a dialog to the user instead.
setRecordActionErrorMessage(
'You must enable "Use a specific field for previews" to preview URLs with a button field.',
);
}
}
},
[isSettingsOpen, isEnforced, urlTable],
);
useEffect(() => {
// Return the unsubscribe function to ensure we clean up the handler.
return registerRecordActionDataCallback(onRecordAction);
}, [onRecordAction]);
// This watch deletes the cached selectedRecordId and selectedFieldId when
// the user moves to a new table or view. This prevents the following
// scenario: User selects a record that contains a preview url. The preview appears.
// User switches to a different table. The preview disappears. The user
// switches back to the original table. Weirdly, the previously viewed preview
// reappears, even though no record is selected.
useWatchable(cursor, ['activeTableId', 'activeViewId'], () => {
setSelectedRecordId(null);
setSelectedFieldId(null);
});
const base = useBase();
const activeTable = base.getTableByIdIfExists(cursor.activeTableId);
useEffect(() => {
// Display the settings form if the settings aren't valid.
if (!isValid && !isSettingsOpen) {
setIsSettingsOpen(true);
}
}, [isValid, isSettingsOpen]);
// activeTable is briefly null when switching to a newly created table.
if (!activeTable) {
return null;
}
return (
<Box>
{isSettingsOpen ? (
<SettingsForm setIsSettingsOpen={setIsSettingsOpen} />
) : (
<RecordPreviewWithDialog
activeTable={activeTable}
selectedRecordId={selectedRecordId}
selectedFieldId={selectedFieldId}
setIsSettingsOpen={setIsSettingsOpen}
/>
)}
{recordActionErrorMessage && (
<Dialog onClose={() => setRecordActionErrorMessage('')} maxWidth={400}>
<Dialog.CloseButton />
<Heading size="small">Can't preview URL</Heading>
<Text variant="paragraph" marginBottom={0}>
{recordActionErrorMessage}
</Text>
</Dialog>
)}
</Box>
);
}
Example #28
Source File: index.js From blocks-usa-map with MIT License | 4 votes |
function USAMapBlock() {
const [isShowingSettings, setIsShowingSettings] = useState(false);
const [selectedState, setSelectedState] = useState(null);
useSettingsButton(function() {
setIsShowingSettings(!isShowingSettings);
});
const viewport = useViewport();
const base = useBase();
const globalConfig = useGlobalConfig();
const tableId = globalConfig.get('selectedTableId');
const stateFieldId = globalConfig.get('selectedStateFieldId');
const colorFieldId = globalConfig.get('selectedColorFieldId');
const table = base.getTableByIdIfExists(tableId);
const stateField = table ? table.getFieldByIdIfExists(stateFieldId) : null;
const colorField = table ? table.getFieldByIdIfExists(colorFieldId) : null;
// if (table == null || stateField == null || colorField == null) {
// setIsShowingSettings(true);
// }
const records = useRecords(stateField ? table : null);
let mapData = null;
if (stateField !== null && colorField !== null) {
mapData = getMapData(records, stateField, colorField);
}
const mapHandler = (event) => {
setSelectedState(event.target.dataset.name);
};
// If settings is showing, draw settings only
if (isShowingSettings) {
return (
<Box padding={3} display="flex">
<FormField
label="Table"
description="Choose the table you want to your State data to come from."
padding={1}
marginBottom={0}
>
<TablePickerSynced globalConfigKey="selectedTableId" />
</FormField>
<FormField
label="State Field"
description='The State field will select a state by either abbreviation ("NJ") or name ("New Jersey")'
marginBottom={0}
padding={1}
>
<FieldPickerSynced
table={table}
globalConfigKey="selectedStateFieldId"
placeholder="Pick a 'state' field..."
allowedTypes={[FieldType.SINGLE_LINE_TEXT, FieldType.SINGLE_SELECT, FieldType.MULTIPLE_RECORD_LINKS, FieldType.MULTIPLE_LOOKUP_VALUES]}
/>
</FormField>
<FormField
label="Color Field"
marginBottom={0}
description="Choose the state color using either a text field which describes the color name, or a single select."
padding={1}
>
<FieldPickerSynced
table={table}
globalConfigKey="selectedColorFieldId"
placeholder="Pick a 'color' field..."
allowedTypes={[FieldType.SINGLE_LINE_TEXT, FieldType.SINGLE_SELECT, FieldType.MULTIPLE_LOOKUP_VALUES, FieldType.NUMBER]}
/>
{colorField != null && colorField.type === FieldType.NUMBER &&
<text>You have selected a numeric type; state colors will be normalized to the maximum value in your field.</text>
}
</FormField>
</Box>
)
}
// otherwise draw the map.
return (
<div>
<Box border="default"
backgroundColor="lightGray1"
// padding={}
>
{/* TODO Allow selected state to show a column of data. */}
{/* {selectedState
? <SelectedState selected={selectedState}/>
: <div>Click to select a state</div>
} */}
<USAMap
title="USA USA USA"
width={viewport.size.width}
height={viewport.size.height - 5}
customize={mapData}
onClick={mapHandler}
/>
</Box>
</div>
)
}
Example #29
Source File: send-messages.js From neighbor-express with MIT License | 4 votes |
export function SendMessagesStep({ previousStep }) {
const [completed, setCompleted] = useState(false);
const [sending, setSending] = useState(false);
const [progress, setProgress] = useState(0);
const messagesTable = useBase().getTable("Messages");
const statusField = messagesTable.getFieldByName("Status");
const queuedOption = statusField.options.choices.find(
(c) => c.name == "Queued"
);
const messagesToSend = useRecords(messagesTable).filter(
(m) => m.getCellValue("Status").name === "Queued"
);
async function sendMessages() {
setCompleted(false);
setSending(true);
const total = messagesToSend.length;
let i = 0;
for (const messageToSend of messagesToSend) {
const response = await sendMessage(messageToSend);
if (response.status === 202) {
messagesTable.updateRecordAsync(messageToSend.id, {
Status: { name: "Sent" },
"Sent Time": new Date(),
});
} else {
messagesTable.updateRecordAsync(messageToSend.id, {
Status: { name: "Errored" },
});
}
i += 1;
setProgress(i / total);
}
setSending(false);
setCompleted(true);
}
async function goBack() {
// Clear any non-persistent data before leaving
setCompleted(false);
setSending(false);
setProgress(0);
previousStep();
}
return (
<Box>
<h2> Step 2: Send emails </h2>
{(sending || completed) && <ProgressBar progress={progress} />}
{completed && <p> Successfully sent all messages </p>}
{messagesToSend.length === 0 ? (
<p>
{" "}
There are no messages with status{" "}
<ChoiceToken choice={queuedOption} marginRight={1} />{" "}
</p>
) : (
<>
<Box
margin={2}
display="flex"
flexDirection="row"
justifyContent="flex-start"
alignItems="center"
>
<Text>
{" "}
The following {messagesToSend.length} messages will be sent:
</Text>
<Button
marginX={2}
variant="primary"
onClick={sendMessages}
disabled={sending}
>
Send All Messages
</Button>
</Box>
<Box height="300px" border="thick" backgroundColor="lightGray1">
<RecordCardList
records={messagesToSend}
fields={["Email type", "Recipient", "Delivery"].map((f) =>
messagesTable.getFieldByName(f)
)}
/>
</Box>
</>
)}
<Button onClick={goBack}> Go Back </Button>
</Box>
);
}