mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Improve dark mode
Summary: Enhances dark mode support for the formula editor, and adds support to the color select popup. Also fixes some bugs with dark mode. Test Plan: Tested manually. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3847
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
import {colors, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {Theme} from 'app/common/ThemePrefs';
|
||||
import {getGristConfig} from 'app/common/urlUtils';
|
||||
import * as ace from 'brace';
|
||||
import {BindableValue, dom, DomElementArg, styled, subscribeElem} from 'grainjs';
|
||||
import {BindableValue, Computed, dom, DomElementArg, Observable, styled, subscribeElem} from 'grainjs';
|
||||
|
||||
// tslint:disable:no-var-requires
|
||||
require('brace/ext/static_highlight');
|
||||
require("brace/mode/python");
|
||||
require("brace/theme/chrome");
|
||||
require('brace/theme/dracula');
|
||||
|
||||
export interface ICodeOptions {
|
||||
gristTheme: Computed<Theme>;
|
||||
placeholder?: string;
|
||||
maxLines?: number;
|
||||
}
|
||||
@@ -15,29 +19,48 @@ export interface ICodeOptions {
|
||||
export function buildHighlightedCode(
|
||||
code: BindableValue<string>, options: ICodeOptions, ...args: DomElementArg[]
|
||||
): HTMLElement {
|
||||
const {gristTheme, placeholder, maxLines} = options;
|
||||
const {enableCustomCss} = getGristConfig();
|
||||
|
||||
const highlighter = ace.acequire('ace/ext/static_highlight');
|
||||
const PythonMode = ace.acequire('ace/mode/python').Mode;
|
||||
const aceTheme = ace.acequire('ace/theme/chrome');
|
||||
const chrome = ace.acequire('ace/theme/chrome');
|
||||
const dracula = ace.acequire('ace/theme/dracula');
|
||||
const mode = new PythonMode();
|
||||
|
||||
return cssHighlightedCode(
|
||||
dom('div',
|
||||
elem => subscribeElem(elem, code, (codeText) => {
|
||||
if (codeText) {
|
||||
if (options.maxLines) {
|
||||
// If requested, trim to maxLines, and add an ellipsis at the end.
|
||||
// (Long lines are also truncated with an ellpsis via text-overflow style.)
|
||||
const lines = codeText.split(/\n/);
|
||||
if (lines.length > options.maxLines) {
|
||||
codeText = lines.slice(0, options.maxLines).join("\n") + " \u2026"; // Ellipsis
|
||||
}
|
||||
}
|
||||
elem.innerHTML = highlighter.render(codeText, mode, aceTheme, 1, true).html;
|
||||
} else {
|
||||
elem.textContent = options.placeholder || '';
|
||||
const codeText = Observable.create(null, '');
|
||||
const codeTheme = Observable.create(null, gristTheme.get());
|
||||
|
||||
function updateHighlightedCode(elem: HTMLElement) {
|
||||
let text = codeText.get();
|
||||
if (text) {
|
||||
if (maxLines) {
|
||||
// If requested, trim to maxLines, and add an ellipsis at the end.
|
||||
// (Long lines are also truncated with an ellpsis via text-overflow style.)
|
||||
const lines = text.split(/\n/);
|
||||
if (lines.length > maxLines) {
|
||||
text = lines.slice(0, maxLines).join("\n") + " \u2026"; // Ellipsis
|
||||
}
|
||||
}
|
||||
|
||||
const aceTheme = codeTheme.get().appearance === 'dark' && !enableCustomCss ? dracula : chrome;
|
||||
elem.innerHTML = highlighter.render(text, mode, aceTheme, 1, true).html;
|
||||
} else {
|
||||
elem.textContent = placeholder || '';
|
||||
}
|
||||
}
|
||||
|
||||
return cssHighlightedCode(
|
||||
dom.autoDispose(codeText),
|
||||
dom.autoDispose(codeTheme),
|
||||
elem => subscribeElem(elem, code, (newCodeText) => {
|
||||
codeText.set(newCodeText);
|
||||
updateHighlightedCode(elem);
|
||||
}),
|
||||
),
|
||||
elem => subscribeElem(elem, gristTheme, (newCodeTheme) => {
|
||||
codeTheme.set(newCodeTheme);
|
||||
updateHighlightedCode(elem);
|
||||
}),
|
||||
...args,
|
||||
);
|
||||
}
|
||||
@@ -46,9 +69,9 @@ export function buildHighlightedCode(
|
||||
export const cssCodeBlock = styled('div', `
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: ${vars.smallFontSize};
|
||||
background-color: ${colors.light};
|
||||
background-color: ${theme.highlightedCodeBlockBg};
|
||||
&[disabled], &.disabled {
|
||||
background-color: ${colors.mediumGreyOpaque};
|
||||
background-color: ${theme.highlightedCodeBlockBgDisabled};
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -57,14 +80,14 @@ const cssHighlightedCode = styled(cssCodeBlock, `
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 1px solid ${colors.darkGrey};
|
||||
border: 1px solid ${theme.highlightedCodeBorder};
|
||||
border-radius: 3px;
|
||||
min-height: 28px;
|
||||
padding: 5px 6px;
|
||||
color: ${colors.slate};
|
||||
color: ${theme.highlightedCodeFg};
|
||||
|
||||
&.disabled, &.disabled .ace-chrome {
|
||||
background-color: ${colors.mediumGreyOpaque};
|
||||
&.disabled, &.disabled .ace-chrome, &.disabled .ace-dracula {
|
||||
background-color: ${theme.highlightedCodeBgDisabled};
|
||||
}
|
||||
& .ace_line {
|
||||
overflow: hidden;
|
||||
@@ -80,7 +103,7 @@ export const cssFieldFormula = styled(buildHighlightedCode, `
|
||||
--icon-color: ${theme.accentIcon};
|
||||
|
||||
&-disabled-icon.formula_field_sidepane::before {
|
||||
--icon-color: ${theme.lightText};
|
||||
--icon-color: ${theme.iconDisabled};
|
||||
}
|
||||
&-disabled {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {reportError} from 'app/client/models/errors';
|
||||
import {cssHelp, cssLabel, cssRow, cssSeparator} from 'app/client/ui/RightPanelStyles';
|
||||
import {cssDragRow, cssFieldEntry, cssFieldLabel} from 'app/client/ui/VisibleFieldsConfig';
|
||||
import {basicButton, primaryButton, textButton} from 'app/client/ui2018/buttons';
|
||||
import {colors, vars} from 'app/client/ui2018/cssVars';
|
||||
import {theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {cssDragger} from 'app/client/ui2018/draggableList';
|
||||
import {textInput} from 'app/client/ui2018/editableLabel';
|
||||
import {IconName} from 'app/client/ui2018/IconList';
|
||||
@@ -511,7 +511,7 @@ export class CustomSectionConfig extends Disposable {
|
||||
const cssWarningWrapper = styled('div', `
|
||||
padding-left: 8px;
|
||||
padding-top: 6px;
|
||||
--icon-color: ${colors.error}
|
||||
--icon-color: ${theme.iconError}
|
||||
`);
|
||||
|
||||
const cssColumns = styled('div', `
|
||||
@@ -534,7 +534,7 @@ const cssSection = styled('div', `
|
||||
|
||||
const cssMenu = styled('div', `
|
||||
& > li:first-child {
|
||||
border-bottom: 1px solid ${colors.mediumGrey};
|
||||
border-bottom: 1px solid ${theme.menuBorder};
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -556,30 +556,37 @@ const cssRemoveIcon = styled(icon, `
|
||||
const cssSubLabel = styled('span', `
|
||||
text-transform: none;
|
||||
font-size: ${vars.xsmallFontSize};
|
||||
color: ${colors.slate};
|
||||
color: ${theme.lightText};
|
||||
`);
|
||||
|
||||
const cssAddMapping = styled('div', `
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
color: ${colors.lightGreen};
|
||||
--icon-color: ${colors.lightGreen};
|
||||
color: ${theme.controlFg};
|
||||
--icon-color: ${theme.controlFg};
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
&:hover, &:focus, &:active {
|
||||
color: ${colors.darkGreen};
|
||||
--icon-color: ${colors.darkGreen};
|
||||
color: ${theme.controlHoverFg};
|
||||
--icon-color: ${theme.controlHoverFg};
|
||||
}
|
||||
`);
|
||||
|
||||
const cssTextInput = styled(textInput, `
|
||||
flex: 1 0 auto;
|
||||
|
||||
color: ${theme.inputFg};
|
||||
background-color: ${theme.inputBg};
|
||||
|
||||
&:disabled {
|
||||
color: ${colors.slate};
|
||||
background-color: ${colors.lightGrey};
|
||||
color: ${theme.inputDisabledFg};
|
||||
background-color: ${theme.inputDisabledBg};
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: ${theme.inputPlaceholderFg};
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {GRIST_FORMULA_ASSISTANT} from 'app/client/models/features';
|
||||
import {selectMenu, selectOption, selectTitle} from 'app/client/ui2018/menus';
|
||||
import {createFormulaErrorObs, cssError} from 'app/client/widgets/FormulaEditor';
|
||||
import {sanitizeIdent} from 'app/common/gutil';
|
||||
import {Theme} from 'app/common/ThemePrefs';
|
||||
import {bundleChanges, Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder,
|
||||
Observable, styled} from 'grainjs';
|
||||
import * as ko from 'knockout';
|
||||
@@ -316,10 +317,13 @@ export function buildFormulaConfig(
|
||||
cssRow(formulaField = buildFormula(
|
||||
origColumn,
|
||||
buildEditor,
|
||||
t("Enter formula"),
|
||||
disableOtherActions,
|
||||
onSave,
|
||||
clearState)),
|
||||
{
|
||||
gristTheme: gristDoc.currentTheme,
|
||||
placeholder: t("Enter formula"),
|
||||
disabled: disableOtherActions,
|
||||
onSave,
|
||||
onCancel: clearState,
|
||||
})),
|
||||
dom.maybe(errorMessage, errMsg => cssRow(cssError(errMsg), testId('field-error-count'))),
|
||||
];
|
||||
|
||||
@@ -404,14 +408,21 @@ export function buildFormulaConfig(
|
||||
]);
|
||||
}
|
||||
|
||||
interface BuildFormulaOptions {
|
||||
gristTheme: Computed<Theme>;
|
||||
placeholder: string;
|
||||
disabled: Observable<boolean>;
|
||||
onSave?: SaveHandler;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
function buildFormula(
|
||||
column: ColumnRec,
|
||||
buildEditor: BuildEditor,
|
||||
placeholder: string,
|
||||
disabled: Observable<boolean>,
|
||||
onSave?: SaveHandler,
|
||||
onCancel?: () => void) {
|
||||
return cssFieldFormula(column.formula, {placeholder, maxLines: 2},
|
||||
column: ColumnRec,
|
||||
buildEditor: BuildEditor,
|
||||
options: BuildFormulaOptions
|
||||
) {
|
||||
const {gristTheme, placeholder, disabled, onSave, onCancel} = options;
|
||||
return cssFieldFormula(column.formula, {gristTheme, placeholder, maxLines: 2},
|
||||
dom.cls('formula_field_sidepane'),
|
||||
cssFieldFormula.cls('-disabled', disabled),
|
||||
cssFieldFormula.cls('-disabled-icon', use => !use(column.formula)),
|
||||
|
||||
@@ -236,7 +236,10 @@ function buildChat(owner: Disposable, context: Context & { formulaClicked: (form
|
||||
} else {
|
||||
return cssAiMessage(
|
||||
cssAvatar(cssAiImage()),
|
||||
buildHighlightedCode(entry.message, { maxLines: 10 }, cssCodeStyles.cls('')),
|
||||
buildHighlightedCode(entry.message, {
|
||||
gristTheme: grist.currentTheme,
|
||||
maxLines: 10,
|
||||
}, cssCodeStyles.cls('')),
|
||||
cssCopyIconWrapper(
|
||||
icon('Copy', dom.on('click', () => context.formulaClicked(entry.message))),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user