mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +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:
parent
9d0e6694fc
commit
8a0bb4d4fe
@ -1,10 +1,13 @@
|
|||||||
import {setupAceEditorCompletions} from 'app/client/components/AceEditorCompletions';
|
import {setupAceEditorCompletions} from 'app/client/components/AceEditorCompletions';
|
||||||
import {colors} from 'app/client/ui2018/cssVars';
|
import {theme} from 'app/client/ui2018/cssVars';
|
||||||
|
import {Theme} from 'app/common/ThemePrefs';
|
||||||
|
import {getGristConfig} from 'app/common/urlUtils';
|
||||||
import * as ace from 'brace';
|
import * as ace from 'brace';
|
||||||
import {dom, DomArg, Observable, styled} from 'grainjs';
|
import {Computed, dom, DomArg, Listener, Observable, styled} from 'grainjs';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
export interface ACLFormulaOptions {
|
export interface ACLFormulaOptions {
|
||||||
|
gristTheme: Computed<Theme>;
|
||||||
initialValue: string;
|
initialValue: string;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
placeholder: DomArg;
|
placeholder: DomArg;
|
||||||
@ -19,7 +22,19 @@ export function aclFormulaEditor(options: ACLFormulaOptions) {
|
|||||||
const editor: ace.Editor = ace.edit(editorElem);
|
const editor: ace.Editor = ace.edit(editorElem);
|
||||||
|
|
||||||
// Set various editor options.
|
// Set various editor options.
|
||||||
editor.setTheme('ace/theme/chrome');
|
function setAceTheme(gristTheme: Theme) {
|
||||||
|
const {enableCustomCss} = getGristConfig();
|
||||||
|
const gristAppearance = gristTheme.appearance;
|
||||||
|
const aceTheme = gristAppearance === 'dark' && !enableCustomCss ? 'dracula' : 'chrome';
|
||||||
|
editor.setTheme(`ace/theme/${aceTheme}`);
|
||||||
|
}
|
||||||
|
setAceTheme(options.gristTheme.get());
|
||||||
|
let themeListener: Listener | undefined;
|
||||||
|
if (!getGristConfig().enableCustomCss) {
|
||||||
|
themeListener = options.gristTheme.addListener((gristTheme) => {
|
||||||
|
setAceTheme(gristTheme);
|
||||||
|
});
|
||||||
|
}
|
||||||
// ACE editor resizes automatically when maxLines is set.
|
// ACE editor resizes automatically when maxLines is set.
|
||||||
editor.setOptions({enableLiveAutocompletion: true, maxLines: 10});
|
editor.setOptions({enableLiveAutocompletion: true, maxLines: 10});
|
||||||
editor.renderer.setShowGutter(false); // Default line numbers to hidden
|
editor.renderer.setShowGutter(false); // Default line numbers to hidden
|
||||||
@ -80,6 +95,7 @@ export function aclFormulaEditor(options: ACLFormulaOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return cssConditionInputAce(
|
return cssConditionInputAce(
|
||||||
|
dom.autoDispose(themeListener ?? null),
|
||||||
cssConditionInputAce.cls('-disabled', options.readOnly),
|
cssConditionInputAce.cls('-disabled', options.readOnly),
|
||||||
// ACE editor calls preventDefault on clicks into the scrollbar area, which prevents focus
|
// ACE editor calls preventDefault on clicks into the scrollbar area, which prevents focus
|
||||||
// being set when the click happens to be into there. To ensure we can focus on such clicks
|
// being set when the click happens to be into there. To ensure we can focus on such clicks
|
||||||
@ -100,22 +116,25 @@ const cssConditionInputAce = styled('div', `
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid ${colors.darkGrey};
|
border: 1px solid ${theme.accessRulesFormulaEditorBorderHover};
|
||||||
}
|
}
|
||||||
&:not(&-disabled):focus-within {
|
&:not(&-disabled):focus-within {
|
||||||
box-shadow: inset 0 0 0 1px ${colors.cursor};
|
box-shadow: inset 0 0 0 1px ${theme.accessRulesFormulaEditorFocus};
|
||||||
border-color: ${colors.cursor};
|
border-color: ${theme.accessRulesFormulaEditorFocus};
|
||||||
}
|
}
|
||||||
&:not(:focus-within) .ace_scroller, &-disabled .ace_scroller {
|
&:not(:focus-within) .ace_scroller, &-disabled .ace_scroller {
|
||||||
cursor: unset;
|
cursor: unset;
|
||||||
}
|
}
|
||||||
&-disabled, &-disabled:hover {
|
&-disabled, &-disabled:hover {
|
||||||
background-color: ${colors.mediumGreyOpaque};
|
background-color: ${theme.accessRulesFormulaEditorBgDisabled};
|
||||||
box-shadow: unset;
|
box-shadow: unset;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
&-disabled .ace-chrome {
|
& .ace-chrome, & .ace-dracula {
|
||||||
background-color: ${colors.mediumGreyOpaque};
|
background-color: ${theme.accessRulesFormulaEditorBg};
|
||||||
|
}
|
||||||
|
&-disabled .ace-chrome, &-disabled .ace-dracula {
|
||||||
|
background-color: ${theme.accessRulesFormulaEditorBgDisabled};
|
||||||
}
|
}
|
||||||
& .ace_marker-layer, & .ace_cursor-layer {
|
& .ace_marker-layer, & .ace_cursor-layer {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -123,9 +123,9 @@ export class AccessRules extends Disposable {
|
|||||||
// Map of tableId to basic metadata for all tables in the document.
|
// Map of tableId to basic metadata for all tables in the document.
|
||||||
private _aclResources = new Map<string, AclTableDescription>();
|
private _aclResources = new Map<string, AclTableDescription>();
|
||||||
|
|
||||||
private _aclUsersPopup = ACLUsersPopup.create(this, this._gristDoc.docPageModel);
|
private _aclUsersPopup = ACLUsersPopup.create(this, this.gristDoc.docPageModel);
|
||||||
|
|
||||||
constructor(private _gristDoc: GristDoc) {
|
constructor(public gristDoc: GristDoc) {
|
||||||
super();
|
super();
|
||||||
this._ruleStatus = Computed.create(this, (use) => {
|
this._ruleStatus = Computed.create(this, (use) => {
|
||||||
const defRuleSet = use(this._docDefaultRuleSet);
|
const defRuleSet = use(this._docDefaultRuleSet);
|
||||||
@ -175,10 +175,10 @@ export class AccessRules extends Disposable {
|
|||||||
// changes). Instead, react deliberately if rules change. Note that table/column renames would
|
// changes). Instead, react deliberately if rules change. Note that table/column renames would
|
||||||
// trigger changes to rules, so we don't need to listen for those separately.
|
// trigger changes to rules, so we don't need to listen for those separately.
|
||||||
for (const tableId of ['_grist_ACLResources', '_grist_ACLRules']) {
|
for (const tableId of ['_grist_ACLResources', '_grist_ACLRules']) {
|
||||||
const tableData = this._gristDoc.docData.getTable(tableId)!;
|
const tableData = this.gristDoc.docData.getTable(tableId)!;
|
||||||
this.autoDispose(tableData.tableActionEmitter.addListener(this._onChange, this));
|
this.autoDispose(tableData.tableActionEmitter.addListener(this._onChange, this));
|
||||||
}
|
}
|
||||||
this.autoDispose(this._gristDoc.docPageModel.currentDoc.addListener(this._updateDocAccessData, this));
|
this.autoDispose(this.gristDoc.docPageModel.currentDoc.addListener(this._updateDocAccessData, this));
|
||||||
|
|
||||||
this.update().catch((e) => this._errorMessage.set(e.message));
|
this.update().catch((e) => this._errorMessage.set(e.message));
|
||||||
}
|
}
|
||||||
@ -202,9 +202,9 @@ export class AccessRules extends Disposable {
|
|||||||
const rules = this._ruleCollection;
|
const rules = this._ruleCollection;
|
||||||
|
|
||||||
const [ , , aclResources] = await Promise.all([
|
const [ , , aclResources] = await Promise.all([
|
||||||
rules.update(this._gristDoc.docData, {log: console, pullOutSchemaEdit: true}),
|
rules.update(this.gristDoc.docData, {log: console, pullOutSchemaEdit: true}),
|
||||||
this._updateDocAccessData(),
|
this._updateDocAccessData(),
|
||||||
this._gristDoc.docComm.getAclResources(),
|
this.gristDoc.docComm.getAclResources(),
|
||||||
]);
|
]);
|
||||||
this._aclResources = new Map(Object.entries(aclResources.tables));
|
this._aclResources = new Map(Object.entries(aclResources.tables));
|
||||||
this._ruleProblems.set(aclResources.problems);
|
this._ruleProblems.set(aclResources.problems);
|
||||||
@ -244,7 +244,7 @@ export class AccessRules extends Disposable {
|
|||||||
// Note that if anything has changed, we apply changes relative to the current state of the
|
// Note that if anything has changed, we apply changes relative to the current state of the
|
||||||
// ACL tables (they may have changed by other users). So our changes will win.
|
// ACL tables (they may have changed by other users). So our changes will win.
|
||||||
|
|
||||||
const docData = this._gristDoc.docData;
|
const docData = this.gristDoc.docData;
|
||||||
const resourcesTable = docData.getMetaTable('_grist_ACLResources');
|
const resourcesTable = docData.getMetaTable('_grist_ACLResources');
|
||||||
const rulesTable = docData.getMetaTable('_grist_ACLRules');
|
const rulesTable = docData.getMetaTable('_grist_ACLRules');
|
||||||
|
|
||||||
@ -346,7 +346,7 @@ export class AccessRules extends Disposable {
|
|||||||
|
|
||||||
public buildDom() {
|
public buildDom() {
|
||||||
return cssOuter(
|
return cssOuter(
|
||||||
dom('div', this._gristDoc.behavioralPromptsManager.attachTip('accessRules', {
|
dom('div', this.gristDoc.behavioralPromptsManager.attachTip('accessRules', {
|
||||||
hideArrow: true,
|
hideArrow: true,
|
||||||
})),
|
})),
|
||||||
cssAddTableRow(
|
cssAddTableRow(
|
||||||
@ -485,7 +485,7 @@ export class AccessRules extends Disposable {
|
|||||||
|
|
||||||
public async checkAclFormula(text: string): Promise<FormulaProperties> {
|
public async checkAclFormula(text: string): Promise<FormulaProperties> {
|
||||||
if (text) {
|
if (text) {
|
||||||
return this._gristDoc.docComm.checkAclFormula(text);
|
return this.gristDoc.docComm.checkAclFormula(text);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -1433,6 +1433,7 @@ class ObsUserAttributeRule extends Disposable {
|
|||||||
cssColumnGroup(
|
cssColumnGroup(
|
||||||
cssCell1(
|
cssCell1(
|
||||||
aclFormulaEditor({
|
aclFormulaEditor({
|
||||||
|
gristTheme: this._accessRules.gristDoc.currentTheme,
|
||||||
initialValue: this._charId.get(),
|
initialValue: this._charId.get(),
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
setValue: (text) => this._setUserAttr(text),
|
setValue: (text) => this._setUserAttr(text),
|
||||||
@ -1655,6 +1656,7 @@ class ObsRulePart extends Disposable {
|
|||||||
cssCell2(
|
cssCell2(
|
||||||
wide ? cssCell4.cls('') : null,
|
wide ? cssCell4.cls('') : null,
|
||||||
aclFormulaEditor({
|
aclFormulaEditor({
|
||||||
|
gristTheme: this._ruleSet.accessRules.gristDoc.currentTheme,
|
||||||
initialValue: this._aclFormula.get(),
|
initialValue: this._aclFormula.get(),
|
||||||
readOnly: this.isBuiltIn(),
|
readOnly: this.isBuiltIn(),
|
||||||
setValue: (value) => this._setAclFormula(value),
|
setValue: (value) => this._setAclFormula(value),
|
||||||
|
@ -3,17 +3,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ace_grist_link {
|
.ace_grist_link {
|
||||||
color: var(--grist-color-light-green);
|
color: var(--grist-theme-ace-autocomplete-link, var(--grist-color-light-green));
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ace_grist_example {
|
.ace_grist_example {
|
||||||
color: #8f8f8f;
|
color: var(--grist-theme-ace-autocomplete-secondary-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_editor.ace_autocomplete .ace_completion-highlight {
|
||||||
|
color: var(--grist-theme-ace-autocomplete-highlighted-fg, #000) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ace_editor.ace_autocomplete .ace_completion-highlight.ace_grist_link {
|
.ace_editor.ace_autocomplete .ace_completion-highlight.ace_grist_link {
|
||||||
color: var(--grist-color-dark-green);
|
color: var(--grist-theme-ace-autocomplete-link-highlighted, var(--grist-color-dark-green)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ace_editor.ace_autocomplete .ace_text-layer {
|
.ace_editor.ace_autocomplete .ace_text-layer {
|
||||||
@ -22,6 +26,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ace_editor.ace_autocomplete {
|
.ace_editor.ace_autocomplete {
|
||||||
|
color: var(--grist-theme-ace-autocomplete-primary-fg) !important;
|
||||||
|
background: var(--grist-theme-ace-autocomplete-bg, #fbfbfb) !important;
|
||||||
|
border: 1px solid var(--grist-theme-ace-autocomplete-border, lightgray) !important;
|
||||||
width: 500px !important; /* the default in language_tools.js is 280px */
|
width: 500px !important; /* the default in language_tools.js is 280px */
|
||||||
max-width: 80%; /* of the screen, for hypothetical mobile support */
|
max-width: 80%; /* of the screen, for hypothetical mobile support */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
|
||||||
|
background-color: var(--grist-theme-ace-autocomplete-line-bg-hover, rgba(233,233,253,0.4)) !important;
|
||||||
|
border: 1px solid var(--grist-theme-ace-autocomplete-line-border-hover, #abbffe) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
|
||||||
|
background-color: var(--grist-theme-ace-autocomplete-active-line-bg, #CAD6FA) !important;
|
||||||
|
}
|
||||||
|
@ -204,7 +204,7 @@ AceEditor.prototype._setup = function() {
|
|||||||
this.session = this.editor.getSession();
|
this.session = this.editor.getSession();
|
||||||
this.session.setMode('ace/mode/python');
|
this.session.setMode('ace/mode/python');
|
||||||
|
|
||||||
const gristTheme = this.gristDoc?.docPageModel.appModel.currentTheme;
|
const gristTheme = this.gristDoc?.currentTheme;
|
||||||
this._setAceTheme(gristTheme?.get());
|
this._setAceTheme(gristTheme?.get());
|
||||||
if (!getGristConfig().enableCustomCss && gristTheme) {
|
if (!getGristConfig().enableCustomCss && gristTheme) {
|
||||||
this.autoDispose(gristTheme.addListener((theme) => {
|
this.autoDispose(gristTheme.addListener((theme) => {
|
||||||
|
@ -229,7 +229,7 @@ export class ChartView extends Disposable {
|
|||||||
this.listenTo(this.sortedRows, 'rowNotify', this._update);
|
this.listenTo(this.sortedRows, 'rowNotify', this._update);
|
||||||
this.autoDispose(this.sortedRows.getKoArray().subscribe(this._update));
|
this.autoDispose(this.sortedRows.getKoArray().subscribe(this._update));
|
||||||
this.autoDispose(this._formatterComp.subscribe(this._update));
|
this.autoDispose(this._formatterComp.subscribe(this._update));
|
||||||
this.autoDispose(this.gristDoc.docPageModel.appModel.currentTheme.addListener(() => this._update()));
|
this.autoDispose(this.gristDoc.currentTheme.addListener(() => this._update()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public prepareToPrint(onOff: boolean) {
|
public prepareToPrint(onOff: boolean) {
|
||||||
|
@ -50,3 +50,11 @@
|
|||||||
.g-code-viewer .hljs-number {
|
.g-code-viewer .hljs-number {
|
||||||
color: var(--grist-theme-code-view-number, #880000);
|
color: var(--grist-theme-code-view-number, #880000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.g-code-viewer .hljs-built_in {
|
||||||
|
color: var(--grist-theme-code-view-builtin, #397300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-code-viewer .hljs-literal {
|
||||||
|
color: var(--grist-theme-code-view-literal, #78A960);
|
||||||
|
}
|
||||||
|
@ -182,6 +182,8 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
// Holder for the popped up formula editor.
|
// Holder for the popped up formula editor.
|
||||||
public readonly formulaPopup = Holder.create(this);
|
public readonly formulaPopup = Holder.create(this);
|
||||||
|
|
||||||
|
public readonly currentTheme = this.docPageModel.appModel.currentTheme;
|
||||||
|
|
||||||
private _actionLog: ActionLog;
|
private _actionLog: ActionLog;
|
||||||
private _undoStack: UndoStack;
|
private _undoStack: UndoStack;
|
||||||
private _lastOwnActionGroup: ActionGroupWithCursorPos|null = null;
|
private _lastOwnActionGroup: ActionGroupWithCursorPos|null = null;
|
||||||
|
@ -856,7 +856,8 @@ export class Importer extends DisposableWithEvents {
|
|||||||
return formula;
|
return formula;
|
||||||
};
|
};
|
||||||
|
|
||||||
return cssFieldFormula(use => formatFormula(use(column.formula)), {placeholder, maxLines: 1},
|
return cssFieldFormula(use => formatFormula(use(column.formula)),
|
||||||
|
{gristTheme: this._gristDoc.currentTheme, placeholder, maxLines: 1},
|
||||||
dom.cls('disabled'),
|
dom.cls('disabled'),
|
||||||
{tabIndex: '-1'},
|
{tabIndex: '-1'},
|
||||||
dom.on('focus', (_ev, elem) => buildEditor(elem)),
|
dom.on('focus', (_ev, elem) => buildEditor(elem)),
|
||||||
|
@ -2,6 +2,7 @@ import {CustomView} from 'app/client/components/CustomView';
|
|||||||
import {DataRowModel} from 'app/client/models/DataRowModel';
|
import {DataRowModel} from 'app/client/models/DataRowModel';
|
||||||
import DataTableModel from 'app/client/models/DataTableModel';
|
import DataTableModel from 'app/client/models/DataTableModel';
|
||||||
import {ViewSectionRec} from 'app/client/models/DocModel';
|
import {ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
|
import {prefersDarkMode, prefersDarkModeObs} from 'app/client/ui2018/cssVars';
|
||||||
import {dom} from 'grainjs';
|
import {dom} from 'grainjs';
|
||||||
|
|
||||||
type RowId = number|'new';
|
type RowId = number|'new';
|
||||||
@ -36,6 +37,16 @@ export async function printViewSection(layout: any, viewSection: ViewSectionRec)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prepareToPrint(onOff: boolean) {
|
function prepareToPrint(onOff: boolean) {
|
||||||
|
// window.print() is a blocking call, which means our listener for the
|
||||||
|
// `prefers-color-scheme: dark` media feature will not receive any updates for the
|
||||||
|
// duration that the print dialog is shown. This proves problematic since an event is
|
||||||
|
// sent just before the blocking call containing a value of false, regardless of the
|
||||||
|
// user agent's color scheme preference. It's not clear why this happens, but the result
|
||||||
|
// is Grist temporarily reverting to the light theme until the print dialog is dismissed.
|
||||||
|
// As a workaround, we'll temporarily pause our listener, and unpause after the print dialog
|
||||||
|
// is dismissed.
|
||||||
|
prefersDarkModeObs().pause();
|
||||||
|
|
||||||
// Hide all layout boxes that do NOT contain the section to be printed.
|
// Hide all layout boxes that do NOT contain the section to be printed.
|
||||||
layout?.forEachBox((box: any) => {
|
layout?.forEachBox((box: any) => {
|
||||||
if (!box.dom.contains(sectionElem)) {
|
if (!box.dom.contains(sectionElem)) {
|
||||||
@ -76,6 +87,10 @@ export async function printViewSection(layout: any, viewSection: ViewSectionRec)
|
|||||||
prepareToPrint(false);
|
prepareToPrint(false);
|
||||||
}
|
}
|
||||||
delete (window as any).afterPrintCallback;
|
delete (window as any).afterPrintCallback;
|
||||||
|
prefersDarkModeObs().pause(false);
|
||||||
|
|
||||||
|
// This may have changed while window.print() was blocking.
|
||||||
|
prefersDarkModeObs().set(prefersDarkMode());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Running print on a timeout makes it possible to test printing using selenium, and doesn't
|
// Running print on a timeout makes it possible to test printing using selenium, and doesn't
|
||||||
|
@ -108,7 +108,7 @@ div:hover > .kf_tooltip {
|
|||||||
box-shadow: 0 1px 1px 1px rgba(0,0,0,0.15);
|
box-shadow: 0 1px 1px 1px rgba(0,0,0,0.15);
|
||||||
line-height: 1.1rem;
|
line-height: 1.1rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #606060;
|
color: var(--grist-theme-prompt-fg, #606060);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
app/client/lib/pausableObs.ts
Normal file
41
app/client/lib/pausableObs.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {IDisposableOwner, Observable} from 'grainjs';
|
||||||
|
|
||||||
|
export interface PausableObservable<T> extends Observable<T> {
|
||||||
|
pause(shouldPause?: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns an `Observable` that can be paused, effectively causing all
|
||||||
|
* calls to `set` to become noops until unpaused, at which point the last value
|
||||||
|
* passed to set, if any, will be applied.
|
||||||
|
*
|
||||||
|
* NOTE: It's only advisable to use this when there are no other alternatives; pausing
|
||||||
|
* updates and notifications to subscribers increases the chances of introducing bugs.
|
||||||
|
*/
|
||||||
|
export function createPausableObs<T>(
|
||||||
|
owner: IDisposableOwner|null,
|
||||||
|
value: T,
|
||||||
|
): PausableObservable<T> {
|
||||||
|
let _isPaused = false;
|
||||||
|
let _lastValue: T | undefined = undefined;
|
||||||
|
const obs = Observable.create<T>(owner, value);
|
||||||
|
const set = Symbol('set');
|
||||||
|
return Object.assign(obs, {
|
||||||
|
pause(shouldPause: boolean = true) {
|
||||||
|
_isPaused = shouldPause;
|
||||||
|
if (shouldPause) {
|
||||||
|
_lastValue = undefined;
|
||||||
|
} else if (_lastValue) {
|
||||||
|
obs.set(_lastValue);
|
||||||
|
_lastValue = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[set]: obs.set,
|
||||||
|
set(val: T) {
|
||||||
|
_lastValue = val;
|
||||||
|
if (_isPaused) { return; }
|
||||||
|
|
||||||
|
this[set](val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -20,18 +20,24 @@ function makePrefFunctions<P extends keyof PrefsTypes>(prefsTypeName: P) {
|
|||||||
*/
|
*/
|
||||||
function getPrefsObs(appModel: AppModel): Observable<PrefsType> {
|
function getPrefsObs(appModel: AppModel): Observable<PrefsType> {
|
||||||
if (appModel.currentValidUser) {
|
if (appModel.currentValidUser) {
|
||||||
const prefsObs = Observable.create<PrefsType>(null, appModel.currentOrg?.[prefsTypeName] ?? {});
|
let prefs: PrefsType | undefined;
|
||||||
|
if (prefsTypeName === 'userPrefs') {
|
||||||
|
prefs = appModel.currentValidUser.prefs;
|
||||||
|
} else {
|
||||||
|
prefs = appModel.currentOrg?.[prefsTypeName];
|
||||||
|
}
|
||||||
|
const prefsObs = Observable.create<PrefsType>(null, prefs ?? {});
|
||||||
return Computed.create(null, (use) => use(prefsObs))
|
return Computed.create(null, (use) => use(prefsObs))
|
||||||
.onWrite(prefs => {
|
.onWrite(newPrefs => {
|
||||||
prefsObs.set(prefs);
|
prefsObs.set(newPrefs);
|
||||||
return appModel.api.updateOrg('current', {[prefsTypeName]: prefs});
|
return appModel.api.updateOrg('current', {[prefsTypeName]: newPrefs});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const userId = appModel.currentUser?.id || 0;
|
const userId = appModel.currentUser?.id || 0;
|
||||||
const jsonPrefsObs = localStorageObs(`${prefsTypeName}:u=${userId}`);
|
const jsonPrefsObs = localStorageObs(`${prefsTypeName}:u=${userId}`);
|
||||||
return Computed.create(null, jsonPrefsObs, (use, p) => (p && JSON.parse(p) || {}) as PrefsType)
|
return Computed.create(null, jsonPrefsObs, (use, p) => (p && JSON.parse(p) || {}) as PrefsType)
|
||||||
.onWrite(prefs => {
|
.onWrite(newPrefs => {
|
||||||
jsonPrefsObs.set(JSON.stringify(prefs));
|
jsonPrefsObs.set(JSON.stringify(newPrefs));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 * 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
|
// tslint:disable:no-var-requires
|
||||||
require('brace/ext/static_highlight');
|
require('brace/ext/static_highlight');
|
||||||
require("brace/mode/python");
|
require("brace/mode/python");
|
||||||
require("brace/theme/chrome");
|
require("brace/theme/chrome");
|
||||||
|
require('brace/theme/dracula');
|
||||||
|
|
||||||
export interface ICodeOptions {
|
export interface ICodeOptions {
|
||||||
|
gristTheme: Computed<Theme>;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
maxLines?: number;
|
maxLines?: number;
|
||||||
}
|
}
|
||||||
@ -15,29 +19,48 @@ export interface ICodeOptions {
|
|||||||
export function buildHighlightedCode(
|
export function buildHighlightedCode(
|
||||||
code: BindableValue<string>, options: ICodeOptions, ...args: DomElementArg[]
|
code: BindableValue<string>, options: ICodeOptions, ...args: DomElementArg[]
|
||||||
): HTMLElement {
|
): HTMLElement {
|
||||||
|
const {gristTheme, placeholder, maxLines} = options;
|
||||||
|
const {enableCustomCss} = getGristConfig();
|
||||||
|
|
||||||
const highlighter = ace.acequire('ace/ext/static_highlight');
|
const highlighter = ace.acequire('ace/ext/static_highlight');
|
||||||
const PythonMode = ace.acequire('ace/mode/python').Mode;
|
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();
|
const mode = new PythonMode();
|
||||||
|
|
||||||
return cssHighlightedCode(
|
const codeText = Observable.create(null, '');
|
||||||
dom('div',
|
const codeTheme = Observable.create(null, gristTheme.get());
|
||||||
elem => subscribeElem(elem, code, (codeText) => {
|
|
||||||
if (codeText) {
|
function updateHighlightedCode(elem: HTMLElement) {
|
||||||
if (options.maxLines) {
|
let text = codeText.get();
|
||||||
// If requested, trim to maxLines, and add an ellipsis at the end.
|
if (text) {
|
||||||
// (Long lines are also truncated with an ellpsis via text-overflow style.)
|
if (maxLines) {
|
||||||
const lines = codeText.split(/\n/);
|
// If requested, trim to maxLines, and add an ellipsis at the end.
|
||||||
if (lines.length > options.maxLines) {
|
// (Long lines are also truncated with an ellpsis via text-overflow style.)
|
||||||
codeText = lines.slice(0, options.maxLines).join("\n") + " \u2026"; // Ellipsis
|
const lines = text.split(/\n/);
|
||||||
}
|
if (lines.length > maxLines) {
|
||||||
}
|
text = lines.slice(0, maxLines).join("\n") + " \u2026"; // Ellipsis
|
||||||
elem.innerHTML = highlighter.render(codeText, mode, aceTheme, 1, true).html;
|
|
||||||
} else {
|
|
||||||
elem.textContent = options.placeholder || '';
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
...args,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -46,9 +69,9 @@ export function buildHighlightedCode(
|
|||||||
export const cssCodeBlock = styled('div', `
|
export const cssCodeBlock = styled('div', `
|
||||||
font-family: 'Monaco', 'Menlo', monospace;
|
font-family: 'Monaco', 'Menlo', monospace;
|
||||||
font-size: ${vars.smallFontSize};
|
font-size: ${vars.smallFontSize};
|
||||||
background-color: ${colors.light};
|
background-color: ${theme.highlightedCodeBlockBg};
|
||||||
&[disabled], &.disabled {
|
&[disabled], &.disabled {
|
||||||
background-color: ${colors.mediumGreyOpaque};
|
background-color: ${theme.highlightedCodeBlockBgDisabled};
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@ -57,14 +80,14 @@ const cssHighlightedCode = styled(cssCodeBlock, `
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
border: 1px solid ${colors.darkGrey};
|
border: 1px solid ${theme.highlightedCodeBorder};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
padding: 5px 6px;
|
padding: 5px 6px;
|
||||||
color: ${colors.slate};
|
color: ${theme.highlightedCodeFg};
|
||||||
|
|
||||||
&.disabled, &.disabled .ace-chrome {
|
&.disabled, &.disabled .ace-chrome, &.disabled .ace-dracula {
|
||||||
background-color: ${colors.mediumGreyOpaque};
|
background-color: ${theme.highlightedCodeBgDisabled};
|
||||||
}
|
}
|
||||||
& .ace_line {
|
& .ace_line {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -80,7 +103,7 @@ export const cssFieldFormula = styled(buildHighlightedCode, `
|
|||||||
--icon-color: ${theme.accentIcon};
|
--icon-color: ${theme.accentIcon};
|
||||||
|
|
||||||
&-disabled-icon.formula_field_sidepane::before {
|
&-disabled-icon.formula_field_sidepane::before {
|
||||||
--icon-color: ${theme.lightText};
|
--icon-color: ${theme.iconDisabled};
|
||||||
}
|
}
|
||||||
&-disabled {
|
&-disabled {
|
||||||
pointer-events: none;
|
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 {cssHelp, cssLabel, cssRow, cssSeparator} from 'app/client/ui/RightPanelStyles';
|
||||||
import {cssDragRow, cssFieldEntry, cssFieldLabel} from 'app/client/ui/VisibleFieldsConfig';
|
import {cssDragRow, cssFieldEntry, cssFieldLabel} from 'app/client/ui/VisibleFieldsConfig';
|
||||||
import {basicButton, primaryButton, textButton} from 'app/client/ui2018/buttons';
|
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 {cssDragger} from 'app/client/ui2018/draggableList';
|
||||||
import {textInput} from 'app/client/ui2018/editableLabel';
|
import {textInput} from 'app/client/ui2018/editableLabel';
|
||||||
import {IconName} from 'app/client/ui2018/IconList';
|
import {IconName} from 'app/client/ui2018/IconList';
|
||||||
@ -511,7 +511,7 @@ export class CustomSectionConfig extends Disposable {
|
|||||||
const cssWarningWrapper = styled('div', `
|
const cssWarningWrapper = styled('div', `
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
--icon-color: ${colors.error}
|
--icon-color: ${theme.iconError}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssColumns = styled('div', `
|
const cssColumns = styled('div', `
|
||||||
@ -534,7 +534,7 @@ const cssSection = styled('div', `
|
|||||||
|
|
||||||
const cssMenu = styled('div', `
|
const cssMenu = styled('div', `
|
||||||
& > li:first-child {
|
& > 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', `
|
const cssSubLabel = styled('span', `
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: ${vars.xsmallFontSize};
|
font-size: ${vars.xsmallFontSize};
|
||||||
color: ${colors.slate};
|
color: ${theme.lightText};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssAddMapping = styled('div', `
|
const cssAddMapping = styled('div', `
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${colors.lightGreen};
|
color: ${theme.controlFg};
|
||||||
--icon-color: ${colors.lightGreen};
|
--icon-color: ${theme.controlFg};
|
||||||
|
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
&:hover, &:focus, &:active {
|
&:hover, &:focus, &:active {
|
||||||
color: ${colors.darkGreen};
|
color: ${theme.controlHoverFg};
|
||||||
--icon-color: ${colors.darkGreen};
|
--icon-color: ${theme.controlHoverFg};
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssTextInput = styled(textInput, `
|
const cssTextInput = styled(textInput, `
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
|
|
||||||
|
color: ${theme.inputFg};
|
||||||
|
background-color: ${theme.inputBg};
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
color: ${colors.slate};
|
color: ${theme.inputDisabledFg};
|
||||||
background-color: ${colors.lightGrey};
|
background-color: ${theme.inputDisabledBg};
|
||||||
pointer-events: none;
|
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 {selectMenu, selectOption, selectTitle} from 'app/client/ui2018/menus';
|
||||||
import {createFormulaErrorObs, cssError} from 'app/client/widgets/FormulaEditor';
|
import {createFormulaErrorObs, cssError} from 'app/client/widgets/FormulaEditor';
|
||||||
import {sanitizeIdent} from 'app/common/gutil';
|
import {sanitizeIdent} from 'app/common/gutil';
|
||||||
|
import {Theme} from 'app/common/ThemePrefs';
|
||||||
import {bundleChanges, Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder,
|
import {bundleChanges, Computed, dom, DomContents, DomElementArg, fromKo, MultiHolder,
|
||||||
Observable, styled} from 'grainjs';
|
Observable, styled} from 'grainjs';
|
||||||
import * as ko from 'knockout';
|
import * as ko from 'knockout';
|
||||||
@ -316,10 +317,13 @@ export function buildFormulaConfig(
|
|||||||
cssRow(formulaField = buildFormula(
|
cssRow(formulaField = buildFormula(
|
||||||
origColumn,
|
origColumn,
|
||||||
buildEditor,
|
buildEditor,
|
||||||
t("Enter formula"),
|
{
|
||||||
disableOtherActions,
|
gristTheme: gristDoc.currentTheme,
|
||||||
onSave,
|
placeholder: t("Enter formula"),
|
||||||
clearState)),
|
disabled: disableOtherActions,
|
||||||
|
onSave,
|
||||||
|
onCancel: clearState,
|
||||||
|
})),
|
||||||
dom.maybe(errorMessage, errMsg => cssRow(cssError(errMsg), testId('field-error-count'))),
|
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(
|
function buildFormula(
|
||||||
column: ColumnRec,
|
column: ColumnRec,
|
||||||
buildEditor: BuildEditor,
|
buildEditor: BuildEditor,
|
||||||
placeholder: string,
|
options: BuildFormulaOptions
|
||||||
disabled: Observable<boolean>,
|
) {
|
||||||
onSave?: SaveHandler,
|
const {gristTheme, placeholder, disabled, onSave, onCancel} = options;
|
||||||
onCancel?: () => void) {
|
return cssFieldFormula(column.formula, {gristTheme, placeholder, maxLines: 2},
|
||||||
return cssFieldFormula(column.formula, {placeholder, maxLines: 2},
|
|
||||||
dom.cls('formula_field_sidepane'),
|
dom.cls('formula_field_sidepane'),
|
||||||
cssFieldFormula.cls('-disabled', disabled),
|
cssFieldFormula.cls('-disabled', disabled),
|
||||||
cssFieldFormula.cls('-disabled-icon', use => !use(column.formula)),
|
cssFieldFormula.cls('-disabled-icon', use => !use(column.formula)),
|
||||||
|
@ -236,7 +236,10 @@ function buildChat(owner: Disposable, context: Context & { formulaClicked: (form
|
|||||||
} else {
|
} else {
|
||||||
return cssAiMessage(
|
return cssAiMessage(
|
||||||
cssAvatar(cssAiImage()),
|
cssAvatar(cssAiImage()),
|
||||||
buildHighlightedCode(entry.message, { maxLines: 10 }, cssCodeStyles.cls('')),
|
buildHighlightedCode(entry.message, {
|
||||||
|
gristTheme: grist.currentTheme,
|
||||||
|
maxLines: 10,
|
||||||
|
}, cssCodeStyles.cls('')),
|
||||||
cssCopyIconWrapper(
|
cssCopyIconWrapper(
|
||||||
icon('Copy', dom.on('click', () => context.formulaClicked(entry.message))),
|
icon('Copy', dom.on('click', () => context.formulaClicked(entry.message))),
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
|
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
|
||||||
import {isLight, swatches} from 'app/client/ui2018/ColorPalette';
|
import {isLight, swatches} from 'app/client/ui2018/ColorPalette';
|
||||||
import {colors, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||||
import {textInput} from 'app/client/ui2018/editableLabel';
|
import {textInput} from 'app/client/ui2018/editableLabel';
|
||||||
import {IconName} from 'app/client/ui2018/IconList';
|
import {IconName} from 'app/client/ui2018/IconList';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
@ -366,24 +366,24 @@ class FontComponent extends Disposable {
|
|||||||
const cssFontOptions = styled('div', `
|
const cssFontOptions = styled('div', `
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
background: ${colors.darkGrey};
|
background: ${theme.colorSelectFontOptionsBorder};
|
||||||
border: 1px solid ${colors.darkGrey};
|
border: 1px solid ${theme.colorSelectFontOptionsBorder};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssFontOption = styled('div', `
|
const cssFontOption = styled('div', `
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
background: ${colors.light};
|
background: ${theme.colorSelectFontOptionBg};
|
||||||
--icon-color: ${colors.dark};
|
--icon-color: ${theme.colorSelectFontOptionFg};
|
||||||
height: 24px;
|
height: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover:not(&-selected) {
|
&:hover:not(&-selected) {
|
||||||
background: ${colors.lightGrey};
|
background: ${theme.colorSelectFontOptionBgHover};
|
||||||
}
|
}
|
||||||
&-selected {
|
&-selected {
|
||||||
background: ${colors.dark};
|
background: ${theme.colorSelectFontOptionBgSelected};
|
||||||
--icon-color: ${colors.light}
|
--icon-color: ${theme.colorSelectFontOptionFgSelected}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@ -409,6 +409,7 @@ const cssControlRow = styled('div', `
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const cssHeaderRow = styled('div', `
|
const cssHeaderRow = styled('div', `
|
||||||
|
color: ${theme.colorSelectFg};
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: ${vars.smallFontSize};
|
font-size: ${vars.smallFontSize};
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
@ -430,8 +431,8 @@ const cssVSpacer = styled('div', `
|
|||||||
|
|
||||||
const cssContainer = styled('div', `
|
const cssContainer = styled('div', `
|
||||||
padding: 18px 16px;
|
padding: 18px 16px;
|
||||||
background-color: white;
|
background-color: ${theme.colorSelectBg};
|
||||||
box-shadow: 0 2px 16px 0 rgba(38,38,51,0.6);
|
box-shadow: 0 2px 16px 0 ${theme.colorSelectShadow};
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
&:focus {
|
&:focus {
|
||||||
@ -445,13 +446,13 @@ const cssContent = styled('div', `
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const cssHexBox = styled(textInput, `
|
const cssHexBox = styled(textInput, `
|
||||||
border: 1px solid ${theme.inputBorder};
|
border: 1px solid ${theme.colorSelectInputBorder};
|
||||||
border-left: none;
|
border-left: none;
|
||||||
font-size: ${vars.smallFontSize};
|
font-size: ${vars.smallFontSize};
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${theme.lightText};
|
color: ${theme.colorSelectInputFg};
|
||||||
background-color: ${theme.inputBg};
|
background-color: ${theme.colorSelectInputBg};
|
||||||
width: 56px;
|
width: 56px;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
@ -460,7 +461,7 @@ const cssHexBox = styled(textInput, `
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const cssLightBorder = styled('div', `
|
const cssLightBorder = styled('div', `
|
||||||
border: 1px solid #D9D9D9;
|
border: 1px solid ${theme.colorSelectColorSquareBorder};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssColorSquare = styled('div', `
|
const cssColorSquare = styled('div', `
|
||||||
@ -471,16 +472,16 @@ const cssColorSquare = styled('div', `
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
&-selected {
|
&-selected {
|
||||||
outline: 1px solid #D9D9D9;
|
outline: 1px solid ${theme.colorSelectColorSquareBorder};
|
||||||
outline-offset: 1px;
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssEmptyBox = styled(cssColorSquare, `
|
const cssEmptyBox = styled(cssColorSquare, `
|
||||||
--icon-color: ${colors.error};
|
--icon-color: ${theme.iconError};
|
||||||
border: 1px solid #D9D9D9;
|
border: 1px solid #D9D9D9;
|
||||||
&-selected {
|
&-selected {
|
||||||
outline: 1px solid ${colors.dark};
|
outline: 1px solid ${theme.colorSelectColorSquareBorderEmpty};
|
||||||
outline-offset: 1px;
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
@ -493,7 +494,7 @@ const cssFontIcon = styled(icon, `
|
|||||||
const cssNoneIcon = styled(icon, `
|
const cssNoneIcon = styled(icon, `
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
--icon-color: ${colors.error}
|
--icon-color: ${theme.iconError}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssButtonIcon = styled(cssColorSquare, `
|
const cssButtonIcon = styled(cssColorSquare, `
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* https://css-tricks.com/snippets/css/system-font-stack/
|
* https://css-tricks.com/snippets/css/system-font-stack/
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
import {createPausableObs, PausableObservable} from 'app/client/lib/pausableObs';
|
||||||
import {getStorage} from 'app/client/lib/storage';
|
import {getStorage} from 'app/client/lib/storage';
|
||||||
import {urlState} from 'app/client/models/gristUrlState';
|
import {urlState} from 'app/client/models/gristUrlState';
|
||||||
import {getTheme, ProductFlavor} from 'app/client/ui/CustomThemes';
|
import {getTheme, ProductFlavor} from 'app/client/ui/CustomThemes';
|
||||||
@ -257,6 +258,9 @@ export const theme = {
|
|||||||
'rgba(76, 86, 103, 0.24)'),
|
'rgba(76, 86, 103, 0.24)'),
|
||||||
popupCloseButtonFg: new CustomProp('theme-popup-close-button-fg', undefined, colors.slate),
|
popupCloseButtonFg: new CustomProp('theme-popup-close-button-fg', undefined, colors.slate),
|
||||||
|
|
||||||
|
/* Prompts */
|
||||||
|
promptFg: new CustomProp('theme-prompt-fg', undefined, '#606060'),
|
||||||
|
|
||||||
/* Progress Bars */
|
/* Progress Bars */
|
||||||
progressBarFg: new CustomProp('theme-progress-bar-fg', undefined, colors.lightGreen),
|
progressBarFg: new CustomProp('theme-progress-bar-fg', undefined, colors.lightGreen),
|
||||||
progressBarErrorFg: new CustomProp('theme-progress-bar-error-fg', undefined, colors.error),
|
progressBarErrorFg: new CustomProp('theme-progress-bar-error-fg', undefined, colors.error),
|
||||||
@ -382,6 +386,10 @@ export const theme = {
|
|||||||
filterBarButtonSavedHoverBg: new CustomProp('theme-filter-bar-button-saved-hover-bg', undefined,
|
filterBarButtonSavedHoverBg: new CustomProp('theme-filter-bar-button-saved-hover-bg', undefined,
|
||||||
colors.darkGrey),
|
colors.darkGrey),
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
iconDisabled: new CustomProp('theme-icon-disabled', undefined, colors.slate),
|
||||||
|
iconError: new CustomProp('theme-icon-error', undefined, colors.error),
|
||||||
|
|
||||||
/* Icon Buttons */
|
/* Icon Buttons */
|
||||||
iconButtonFg: new CustomProp('theme-icon-button-fg', undefined, colors.light),
|
iconButtonFg: new CustomProp('theme-icon-button-fg', undefined, colors.light),
|
||||||
iconButtonPrimaryBg: new CustomProp('theme-icon-button-primary-bg', undefined,
|
iconButtonPrimaryBg: new CustomProp('theme-icon-button-primary-bg', undefined,
|
||||||
@ -589,6 +597,8 @@ export const theme = {
|
|||||||
codeViewParams: new CustomProp('theme-code-view-params', undefined, '#444'),
|
codeViewParams: new CustomProp('theme-code-view-params', undefined, '#444'),
|
||||||
codeViewString: new CustomProp('theme-code-view-string', undefined, '#880000'),
|
codeViewString: new CustomProp('theme-code-view-string', undefined, '#880000'),
|
||||||
codeViewNumber: new CustomProp('theme-code-view-number', undefined, '#880000'),
|
codeViewNumber: new CustomProp('theme-code-view-number', undefined, '#880000'),
|
||||||
|
codeViewBuiltin: new CustomProp('theme-code-view-builtin', undefined, '#397300'),
|
||||||
|
codeViewLiteral: new CustomProp('theme-code-view-literal', undefined, '#78A960'),
|
||||||
|
|
||||||
/* Importer */
|
/* Importer */
|
||||||
importerTableInfoBorder: new CustomProp('theme-importer-table-info-border', undefined, colors.darkGrey),
|
importerTableInfoBorder: new CustomProp('theme-importer-table-info-border', undefined, colors.darkGrey),
|
||||||
@ -646,6 +656,14 @@ export const theme = {
|
|||||||
undefined, colors.light),
|
undefined, colors.light),
|
||||||
accessRulesColumnItemIconHoverBg: new CustomProp('theme-access-rules-column-item-icon-hover-bg',
|
accessRulesColumnItemIconHoverBg: new CustomProp('theme-access-rules-column-item-icon-hover-bg',
|
||||||
undefined, colors.slate),
|
undefined, colors.slate),
|
||||||
|
accessRulesFormulaEditorBg: new CustomProp('theme-access-rules-formula-editor-bg', undefined,
|
||||||
|
'white'),
|
||||||
|
accessRulesFormulaEditorBorderHover: new CustomProp(
|
||||||
|
'theme-access-rules-formula-editor-border-hover', undefined, colors.darkGrey),
|
||||||
|
accessRulesFormulaEditorBgDisabled: new CustomProp(
|
||||||
|
'theme-access-rules-formula-editor-bg-disabled', undefined, colors.mediumGreyOpaque),
|
||||||
|
accessRulesFormulaEditorFocus: new CustomProp('theme-access-rules-formula-editor-focus',
|
||||||
|
undefined, colors.cursor),
|
||||||
|
|
||||||
/* Cells */
|
/* Cells */
|
||||||
cellFg: new CustomProp('theme-cell-fg', undefined, 'unset'),
|
cellFg: new CustomProp('theme-cell-fg', undefined, 'unset'),
|
||||||
@ -701,6 +719,63 @@ export const theme = {
|
|||||||
tutorialsPopupHeaderFg: new CustomProp('theme-tutorials-popup-header-fg', undefined,
|
tutorialsPopupHeaderFg: new CustomProp('theme-tutorials-popup-header-fg', undefined,
|
||||||
colors.lightGreen),
|
colors.lightGreen),
|
||||||
tutorialsPopupBoxBg: new CustomProp('theme-tutorials-popup-box-bg', undefined, '#F5F5F5'),
|
tutorialsPopupBoxBg: new CustomProp('theme-tutorials-popup-box-bg', undefined, '#F5F5F5'),
|
||||||
|
|
||||||
|
/* Ace Autocomplete */
|
||||||
|
aceAutocompletePrimaryFg: new CustomProp('theme-ace-autocomplete-primary-fg', undefined, '#444'),
|
||||||
|
aceAutocompleteSecondaryFg: new CustomProp('theme-ace-autocomplete-secondary-fg', undefined,
|
||||||
|
'#8f8f8f'),
|
||||||
|
aceAutocompleteHighlightedFg: new CustomProp('theme-ace-autocomplete-highlighted-fg', undefined, '#000'),
|
||||||
|
aceAutocompleteBg: new CustomProp('theme-ace-autocomplete-bg', undefined, '#FBFBFB'),
|
||||||
|
aceAutocompleteBorder: new CustomProp('theme-ace-autocomplete-border', undefined, 'lightgray'),
|
||||||
|
aceAutocompleteLink: new CustomProp('theme-ace-autocomplete-link', undefined, colors.lightGreen),
|
||||||
|
aceAutocompleteLinkHighlighted: new CustomProp('theme-ace-autocomplete-link-highlighted',
|
||||||
|
undefined, colors.darkGreen),
|
||||||
|
aceAutocompleteActiveLineBg: new CustomProp('theme-ace-autocomplete-active-line-bg',
|
||||||
|
undefined, '#CAD6FA'),
|
||||||
|
aceAutocompleteLineBorderHover: new CustomProp('theme-ace-autocomplete-line-border-hover',
|
||||||
|
undefined, '#abbffe'),
|
||||||
|
aceAutocompleteLineBgHover: new CustomProp('theme-ace-autocomplete-line-bg-hover',
|
||||||
|
undefined, 'rgba(233,233,253,0.4)'),
|
||||||
|
|
||||||
|
/* Color Select */
|
||||||
|
colorSelectFg: new CustomProp('theme-color-select-fg', undefined, colors.dark),
|
||||||
|
colorSelectBg: new CustomProp('theme-color-select-bg', undefined, 'white'),
|
||||||
|
colorSelectShadow: new CustomProp('theme-color-select-shadow', undefined,
|
||||||
|
'rgba(38,38,51,0.6)'),
|
||||||
|
colorSelectFontOptionsBorder: new CustomProp('theme-color-select-font-options-border',
|
||||||
|
undefined, colors.darkGrey),
|
||||||
|
colorSelectFontOptionFg: new CustomProp('theme-color-select-font-option-fg',
|
||||||
|
undefined, colors.dark),
|
||||||
|
colorSelectFontOptionBg: new CustomProp('theme-color-select-font-option-bg',
|
||||||
|
undefined, colors.light),
|
||||||
|
colorSelectFontOptionBgHover: new CustomProp('theme-color-select-font-option-bg-hover',
|
||||||
|
undefined, colors.lightGrey),
|
||||||
|
colorSelectFontOptionFgSelected: new CustomProp('theme-color-select-font-option-selected-fg',
|
||||||
|
undefined, colors.light),
|
||||||
|
colorSelectFontOptionBgSelected: new CustomProp('theme-color-select-font-option-selected-bg',
|
||||||
|
undefined, colors.dark),
|
||||||
|
colorSelectColorSquareBorder: new CustomProp('theme-color-select-color-square-border',
|
||||||
|
undefined, '#D9D9D9'),
|
||||||
|
colorSelectColorSquareBorderEmpty: new CustomProp('theme-color-select-color-square-border-empty',
|
||||||
|
undefined, colors.dark),
|
||||||
|
colorSelectInputFg: new CustomProp('theme-color-select-input-fg',
|
||||||
|
undefined, colors.slate),
|
||||||
|
colorSelectInputBg: new CustomProp('theme-color-select-input-bg',
|
||||||
|
undefined, 'white'),
|
||||||
|
colorSelectInputBorder: new CustomProp('theme-color-select-input-border',
|
||||||
|
undefined, colors.darkGrey),
|
||||||
|
|
||||||
|
/* Highlighted Code */
|
||||||
|
highlightedCodeBlockBg: new CustomProp('theme-highlighted-code-block-bg', undefined,
|
||||||
|
colors.light),
|
||||||
|
highlightedCodeBlockBgDisabled: new CustomProp('theme-highlighted-code-block-bg-disabled',
|
||||||
|
undefined, colors.mediumGreyOpaque),
|
||||||
|
highlightedCodeFg: new CustomProp('theme-highlighted-code-fg',
|
||||||
|
undefined, colors.slate),
|
||||||
|
highlightedCodeBorder: new CustomProp('theme-highlighted-code-border',
|
||||||
|
undefined, colors.darkGrey),
|
||||||
|
highlightedCodeBgDisabled: new CustomProp('theme-highlighted-code-bg-disabled',
|
||||||
|
undefined, colors.mediumGreyOpaque),
|
||||||
};
|
};
|
||||||
|
|
||||||
const cssColors = values(colors).map(v => v.decl()).join('\n');
|
const cssColors = values(colors).map(v => v.decl()).join('\n');
|
||||||
@ -823,15 +898,19 @@ export function isScreenResizing(): Observable<boolean> {
|
|||||||
return _isScreenResizingObs;
|
return _isScreenResizingObs;
|
||||||
}
|
}
|
||||||
|
|
||||||
let _prefersDarkModeObs: Observable<boolean>|undefined;
|
export function prefersDarkMode(): boolean {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _prefersDarkModeObs: PausableObservable<boolean>|undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a singleton observable for whether the user agent prefers dark mode.
|
* Returns a singleton observable for whether the user agent prefers dark mode.
|
||||||
*/
|
*/
|
||||||
export function prefersDarkModeObs(): Observable<boolean> {
|
export function prefersDarkModeObs(): PausableObservable<boolean> {
|
||||||
if (!_prefersDarkModeObs) {
|
if (!_prefersDarkModeObs) {
|
||||||
const query = window.matchMedia('(prefers-color-scheme: dark)');
|
const query = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
const obs = Observable.create<boolean>(null, query.matches);
|
const obs = createPausableObs<boolean>(null, query.matches);
|
||||||
query.addEventListener('change', event => obs.set(event.matches));
|
query.addEventListener('change', event => obs.set(event.matches));
|
||||||
_prefersDarkModeObs = obs;
|
_prefersDarkModeObs = obs;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ export class ConditionalStyle extends Disposable {
|
|||||||
) {
|
) {
|
||||||
return cssFieldFormula(
|
return cssFieldFormula(
|
||||||
formula,
|
formula,
|
||||||
{ maxLines: 1 },
|
{ gristTheme: this._gristDoc.currentTheme, maxLines: 1 },
|
||||||
dom.cls('formula_field_sidepane'),
|
dom.cls('formula_field_sidepane'),
|
||||||
dom.cls(cssErrorBorder.className, hasError),
|
dom.cls(cssErrorBorder.className, hasError),
|
||||||
{ tabIndex: '-1' },
|
{ tabIndex: '-1' },
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import {UserPrefs} from 'app/common/Prefs';
|
||||||
|
|
||||||
// User profile info for the user. When using Cognito, it is fetched during login.
|
// User profile info for the user. When using Cognito, it is fetched during login.
|
||||||
export interface UserProfile {
|
export interface UserProfile {
|
||||||
email: string;
|
email: string;
|
||||||
@ -16,6 +18,7 @@ export interface FullUser extends UserProfile {
|
|||||||
ref?: string|null; // Not filled for anonymous users.
|
ref?: string|null; // Not filled for anonymous users.
|
||||||
allowGoogleLogin?: boolean; // when present, specifies whether logging in via Google is possible.
|
allowGoogleLogin?: boolean; // when present, specifies whether logging in via Google is possible.
|
||||||
isSupport?: boolean; // set if user is a special support user.
|
isSupport?: boolean; // set if user is a special support user.
|
||||||
|
prefs?: UserPrefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginSessionAPI {
|
export interface LoginSessionAPI {
|
||||||
|
@ -93,6 +93,7 @@ export const ThemeColors = t.iface([], {
|
|||||||
"popup-shadow-inner": "string",
|
"popup-shadow-inner": "string",
|
||||||
"popup-shadow-outer": "string",
|
"popup-shadow-outer": "string",
|
||||||
"popup-close-button-fg": "string",
|
"popup-close-button-fg": "string",
|
||||||
|
"prompt-fg": "string",
|
||||||
"progress-bar-fg": "string",
|
"progress-bar-fg": "string",
|
||||||
"progress-bar-error-fg": "string",
|
"progress-bar-error-fg": "string",
|
||||||
"progress-bar-bg": "string",
|
"progress-bar-bg": "string",
|
||||||
@ -167,6 +168,8 @@ export const ThemeColors = t.iface([], {
|
|||||||
"filter-bar-button-saved-fg": "string",
|
"filter-bar-button-saved-fg": "string",
|
||||||
"filter-bar-button-saved-bg": "string",
|
"filter-bar-button-saved-bg": "string",
|
||||||
"filter-bar-button-saved-hover-bg": "string",
|
"filter-bar-button-saved-hover-bg": "string",
|
||||||
|
"icon-disabled": "string",
|
||||||
|
"icon-error": "string",
|
||||||
"icon-button-fg": "string",
|
"icon-button-fg": "string",
|
||||||
"icon-button-primary-bg": "string",
|
"icon-button-primary-bg": "string",
|
||||||
"icon-button-primary-hover-bg": "string",
|
"icon-button-primary-hover-bg": "string",
|
||||||
@ -287,6 +290,8 @@ export const ThemeColors = t.iface([], {
|
|||||||
"code-view-params": "string",
|
"code-view-params": "string",
|
||||||
"code-view-string": "string",
|
"code-view-string": "string",
|
||||||
"code-view-number": "string",
|
"code-view-number": "string",
|
||||||
|
"code-view-builtin": "string",
|
||||||
|
"code-view-literal": "string",
|
||||||
"importer-table-info-border": "string",
|
"importer-table-info-border": "string",
|
||||||
"importer-preview-border": "string",
|
"importer-preview-border": "string",
|
||||||
"importer-skipped-table-overlay": "string",
|
"importer-skipped-table-overlay": "string",
|
||||||
@ -319,6 +324,10 @@ export const ThemeColors = t.iface([], {
|
|||||||
"access-rules-column-item-icon-fg": "string",
|
"access-rules-column-item-icon-fg": "string",
|
||||||
"access-rules-column-item-icon-hover-fg": "string",
|
"access-rules-column-item-icon-hover-fg": "string",
|
||||||
"access-rules-column-item-icon-hover-bg": "string",
|
"access-rules-column-item-icon-hover-bg": "string",
|
||||||
|
"access-rules-formula-editor-bg": "string",
|
||||||
|
"access-rules-formula-editor-border-hover": "string",
|
||||||
|
"access-rules-formula-editor-bg-disabled": "string",
|
||||||
|
"access-rules-formula-editor-focus": "string",
|
||||||
"cell-fg": "string",
|
"cell-fg": "string",
|
||||||
"cell-bg": "string",
|
"cell-bg": "string",
|
||||||
"cell-zebra-bg": "string",
|
"cell-zebra-bg": "string",
|
||||||
@ -348,6 +357,35 @@ export const ThemeColors = t.iface([], {
|
|||||||
"tutorials-popup-border": "string",
|
"tutorials-popup-border": "string",
|
||||||
"tutorials-popup-header-fg": "string",
|
"tutorials-popup-header-fg": "string",
|
||||||
"tutorials-popup-box-bg": "string",
|
"tutorials-popup-box-bg": "string",
|
||||||
|
"ace-autocomplete-primary-fg": "string",
|
||||||
|
"ace-autocomplete-secondary-fg": "string",
|
||||||
|
"ace-autocomplete-highlighted-fg": "string",
|
||||||
|
"ace-autocomplete-bg": "string",
|
||||||
|
"ace-autocomplete-border": "string",
|
||||||
|
"ace-autocomplete-link": "string",
|
||||||
|
"ace-autocomplete-link-highlighted": "string",
|
||||||
|
"ace-autocomplete-active-line-bg": "string",
|
||||||
|
"ace-autocomplete-line-border-hover": "string",
|
||||||
|
"ace-autocomplete-line-bg-hover": "string",
|
||||||
|
"color-select-fg": "string",
|
||||||
|
"color-select-bg": "string",
|
||||||
|
"color-select-shadow": "string",
|
||||||
|
"color-select-font-options-border": "string",
|
||||||
|
"color-select-font-option-fg": "string",
|
||||||
|
"color-select-font-option-bg": "string",
|
||||||
|
"color-select-font-option-bg-hover": "string",
|
||||||
|
"color-select-font-option-fg-selected": "string",
|
||||||
|
"color-select-font-option-bg-selected": "string",
|
||||||
|
"color-select-color-square-border": "string",
|
||||||
|
"color-select-color-square-border-empty": "string",
|
||||||
|
"color-select-input-fg": "string",
|
||||||
|
"color-select-input-bg": "string",
|
||||||
|
"color-select-input-border": "string",
|
||||||
|
"highlighted-code-block-bg": "string",
|
||||||
|
"highlighted-code-block-bg-disabled": "string",
|
||||||
|
"highlighted-code-fg": "string",
|
||||||
|
"highlighted-code-border": "string",
|
||||||
|
"highlighted-code-bg-disabled": "string",
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportedTypeSuite: t.ITypeSuite = {
|
const exportedTypeSuite: t.ITypeSuite = {
|
||||||
|
@ -110,6 +110,9 @@ export interface ThemeColors {
|
|||||||
'popup-shadow-outer': string;
|
'popup-shadow-outer': string;
|
||||||
'popup-close-button-fg': string;
|
'popup-close-button-fg': string;
|
||||||
|
|
||||||
|
/* Prompts */
|
||||||
|
'prompt-fg': string;
|
||||||
|
|
||||||
/* Progress Bars */
|
/* Progress Bars */
|
||||||
'progress-bar-fg': string;
|
'progress-bar-fg': string;
|
||||||
'progress-bar-error-fg': string;
|
'progress-bar-error-fg': string;
|
||||||
@ -216,6 +219,10 @@ export interface ThemeColors {
|
|||||||
'filter-bar-button-saved-bg': string;
|
'filter-bar-button-saved-bg': string;
|
||||||
'filter-bar-button-saved-hover-bg': string;
|
'filter-bar-button-saved-hover-bg': string;
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
'icon-disabled': string;
|
||||||
|
'icon-error': string;
|
||||||
|
|
||||||
/* Icon Buttons */
|
/* Icon Buttons */
|
||||||
'icon-button-fg': string;
|
'icon-button-fg': string;
|
||||||
'icon-button-primary-bg': string;
|
'icon-button-primary-bg': string;
|
||||||
@ -373,6 +380,8 @@ export interface ThemeColors {
|
|||||||
'code-view-params': string;
|
'code-view-params': string;
|
||||||
'code-view-string': string;
|
'code-view-string': string;
|
||||||
'code-view-number': string;
|
'code-view-number': string;
|
||||||
|
'code-view-builtin': string;
|
||||||
|
'code-view-literal': string;
|
||||||
|
|
||||||
/* Importer */
|
/* Importer */
|
||||||
'importer-table-info-border': string;
|
'importer-table-info-border': string;
|
||||||
@ -415,6 +424,10 @@ export interface ThemeColors {
|
|||||||
'access-rules-column-item-icon-fg': string;
|
'access-rules-column-item-icon-fg': string;
|
||||||
'access-rules-column-item-icon-hover-fg': string;
|
'access-rules-column-item-icon-hover-fg': string;
|
||||||
'access-rules-column-item-icon-hover-bg': string;
|
'access-rules-column-item-icon-hover-bg': string;
|
||||||
|
'access-rules-formula-editor-bg': string;
|
||||||
|
'access-rules-formula-editor-border-hover': string;
|
||||||
|
'access-rules-formula-editor-bg-disabled': string;
|
||||||
|
'access-rules-formula-editor-focus': string;
|
||||||
|
|
||||||
/* Cells */
|
/* Cells */
|
||||||
'cell-fg': string;
|
'cell-fg': string;
|
||||||
@ -456,6 +469,41 @@ export interface ThemeColors {
|
|||||||
'tutorials-popup-border': string;
|
'tutorials-popup-border': string;
|
||||||
'tutorials-popup-header-fg': string;
|
'tutorials-popup-header-fg': string;
|
||||||
'tutorials-popup-box-bg': string;
|
'tutorials-popup-box-bg': string;
|
||||||
|
|
||||||
|
/* Ace Autocomplete */
|
||||||
|
'ace-autocomplete-primary-fg': string;
|
||||||
|
'ace-autocomplete-secondary-fg': string;
|
||||||
|
'ace-autocomplete-highlighted-fg': string;
|
||||||
|
'ace-autocomplete-bg': string;
|
||||||
|
'ace-autocomplete-border': string;
|
||||||
|
'ace-autocomplete-link': string;
|
||||||
|
'ace-autocomplete-link-highlighted': string;
|
||||||
|
'ace-autocomplete-active-line-bg': string;
|
||||||
|
'ace-autocomplete-line-border-hover': string;
|
||||||
|
'ace-autocomplete-line-bg-hover': string;
|
||||||
|
|
||||||
|
/* Color Select */
|
||||||
|
'color-select-fg': string;
|
||||||
|
'color-select-bg': string;
|
||||||
|
'color-select-shadow': string;
|
||||||
|
'color-select-font-options-border': string;
|
||||||
|
'color-select-font-option-fg': string;
|
||||||
|
'color-select-font-option-bg': string;
|
||||||
|
'color-select-font-option-bg-hover': string;
|
||||||
|
'color-select-font-option-fg-selected': string;
|
||||||
|
'color-select-font-option-bg-selected': string;
|
||||||
|
'color-select-color-square-border': string;
|
||||||
|
'color-select-color-square-border-empty': string;
|
||||||
|
'color-select-input-fg': string;
|
||||||
|
'color-select-input-bg': string;
|
||||||
|
'color-select-input-border': string;
|
||||||
|
|
||||||
|
/* Highlighted Code */
|
||||||
|
'highlighted-code-block-bg': string;
|
||||||
|
'highlighted-code-block-bg-disabled': string;
|
||||||
|
'highlighted-code-fg': string;
|
||||||
|
'highlighted-code-border': string;
|
||||||
|
'highlighted-code-bg-disabled': string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemePrefsChecker = createCheckers(ThemePrefsTI).ThemePrefs as CheckerT<ThemePrefs>;
|
export const ThemePrefsChecker = createCheckers(ThemePrefsTI).ThemePrefs as CheckerT<ThemePrefs>;
|
||||||
|
@ -89,6 +89,9 @@ export const GristDark: ThemeColors = {
|
|||||||
'popup-shadow-outer': '#000000',
|
'popup-shadow-outer': '#000000',
|
||||||
'popup-close-button-fg': '#A4A4A4',
|
'popup-close-button-fg': '#A4A4A4',
|
||||||
|
|
||||||
|
/* Prompts */
|
||||||
|
'prompt-fg': '#A4A4A4',
|
||||||
|
|
||||||
/* Progress Bars */
|
/* Progress Bars */
|
||||||
'progress-bar-fg': '#1DA270',
|
'progress-bar-fg': '#1DA270',
|
||||||
'progress-bar-error-fg': '#FF6666',
|
'progress-bar-error-fg': '#FF6666',
|
||||||
@ -195,6 +198,10 @@ export const GristDark: ThemeColors = {
|
|||||||
'filter-bar-button-saved-bg': '#555563',
|
'filter-bar-button-saved-bg': '#555563',
|
||||||
'filter-bar-button-saved-hover-bg': '#69697D',
|
'filter-bar-button-saved-hover-bg': '#69697D',
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
'icon-disabled': '#A4A4A4',
|
||||||
|
'icon-error': '#FFA500',
|
||||||
|
|
||||||
/* Icon Buttons */
|
/* Icon Buttons */
|
||||||
'icon-button-fg': '#FFFFFF',
|
'icon-button-fg': '#FFFFFF',
|
||||||
'icon-button-primary-bg': '#1DA270',
|
'icon-button-primary-bg': '#1DA270',
|
||||||
@ -352,6 +359,8 @@ export const GristDark: ThemeColors = {
|
|||||||
'code-view-params': '#D2D2D2',
|
'code-view-params': '#D2D2D2',
|
||||||
'code-view-string': '#ED7373',
|
'code-view-string': '#ED7373',
|
||||||
'code-view-number': '#ED7373',
|
'code-view-number': '#ED7373',
|
||||||
|
'code-view-builtin': '#BFE6D8',
|
||||||
|
'code-view-literal': '#9ED682',
|
||||||
|
|
||||||
/* Importer */
|
/* Importer */
|
||||||
'importer-table-info-border': '#69697D',
|
'importer-table-info-border': '#69697D',
|
||||||
@ -394,6 +403,10 @@ export const GristDark: ThemeColors = {
|
|||||||
'access-rules-column-item-icon-fg': '#A4A4A4',
|
'access-rules-column-item-icon-fg': '#A4A4A4',
|
||||||
'access-rules-column-item-icon-hover-fg': '#EFEFEF',
|
'access-rules-column-item-icon-hover-fg': '#EFEFEF',
|
||||||
'access-rules-column-item-icon-hover-bg': '#A4A4A4',
|
'access-rules-column-item-icon-hover-bg': '#A4A4A4',
|
||||||
|
'access-rules-formula-editor-bg': '#32323F',
|
||||||
|
'access-rules-formula-editor-border-hover': '#69697D',
|
||||||
|
'access-rules-formula-editor-bg-disabled': '#57575F',
|
||||||
|
'access-rules-formula-editor-focus': '#1DA270',
|
||||||
|
|
||||||
/* Cells */
|
/* Cells */
|
||||||
'cell-fg': '#FFFFFF',
|
'cell-fg': '#FFFFFF',
|
||||||
@ -435,4 +448,39 @@ export const GristDark: ThemeColors = {
|
|||||||
'tutorials-popup-border': '#69697D',
|
'tutorials-popup-border': '#69697D',
|
||||||
'tutorials-popup-header-fg': '#FFFFFF',
|
'tutorials-popup-header-fg': '#FFFFFF',
|
||||||
'tutorials-popup-box-bg': '#57575F',
|
'tutorials-popup-box-bg': '#57575F',
|
||||||
|
|
||||||
|
/* Ace Autocomplete */
|
||||||
|
'ace-autocomplete-primary-fg': '#EFEFEF',
|
||||||
|
'ace-autocomplete-secondary-fg': '#A4A4A4',
|
||||||
|
'ace-autocomplete-highlighted-fg': '#FFFFFF',
|
||||||
|
'ace-autocomplete-bg': '#32323F',
|
||||||
|
'ace-autocomplete-border': '#69697D',
|
||||||
|
'ace-autocomplete-link': '#28BE86',
|
||||||
|
'ace-autocomplete-link-highlighted': '#45D48B',
|
||||||
|
'ace-autocomplete-active-line-bg': '#555563',
|
||||||
|
'ace-autocomplete-line-border-hover': 'rgba(111,111,117,0.3)',
|
||||||
|
'ace-autocomplete-line-bg-hover': 'rgba(111,111,117,0.3)',
|
||||||
|
|
||||||
|
/* Color Select */
|
||||||
|
'color-select-fg': '#EFEFEF',
|
||||||
|
'color-select-bg': '#32323F',
|
||||||
|
'color-select-shadow': '#000000',
|
||||||
|
'color-select-font-options-border': '#69697D',
|
||||||
|
'color-select-font-option-fg': '#EFEFEF',
|
||||||
|
'color-select-font-option-bg': '#32323F',
|
||||||
|
'color-select-font-option-bg-hover': '#262633',
|
||||||
|
'color-select-font-option-fg-selected': '#EFEFEF',
|
||||||
|
'color-select-font-option-bg-selected': '#555563',
|
||||||
|
'color-select-color-square-border': '#A4A4A4',
|
||||||
|
'color-select-color-square-border-empty': '#EFEFEF',
|
||||||
|
'color-select-input-fg': '#EFEFEF',
|
||||||
|
'color-select-input-bg': '#32323F',
|
||||||
|
'color-select-input-border': '#69697D',
|
||||||
|
|
||||||
|
/* Highlighted Code */
|
||||||
|
'highlighted-code-block-bg': '#262633',
|
||||||
|
'highlighted-code-block-bg-disabled': '#555563',
|
||||||
|
'highlighted-code-fg': '#A4A4A4',
|
||||||
|
'highlighted-code-border': '#69697D',
|
||||||
|
'highlighted-code-bg-disabled': '#555563',
|
||||||
};
|
};
|
||||||
|
@ -89,6 +89,9 @@ export const GristLight: ThemeColors = {
|
|||||||
'popup-shadow-outer': 'rgba(76, 86, 103, 0.24)',
|
'popup-shadow-outer': 'rgba(76, 86, 103, 0.24)',
|
||||||
'popup-close-button-fg': '#929299',
|
'popup-close-button-fg': '#929299',
|
||||||
|
|
||||||
|
/* Prompts */
|
||||||
|
'prompt-fg': '#606060',
|
||||||
|
|
||||||
/* Progress Bars */
|
/* Progress Bars */
|
||||||
'progress-bar-fg': '#16B378',
|
'progress-bar-fg': '#16B378',
|
||||||
'progress-bar-error-fg': '#D0021B',
|
'progress-bar-error-fg': '#D0021B',
|
||||||
@ -195,6 +198,10 @@ export const GristLight: ThemeColors = {
|
|||||||
'filter-bar-button-saved-bg': '#929299',
|
'filter-bar-button-saved-bg': '#929299',
|
||||||
'filter-bar-button-saved-hover-bg': '#D9D9D9',
|
'filter-bar-button-saved-hover-bg': '#D9D9D9',
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
'icon-disabled': '#929299',
|
||||||
|
'icon-error': '#D0021B',
|
||||||
|
|
||||||
/* Icon Buttons */
|
/* Icon Buttons */
|
||||||
'icon-button-fg': '#FFFFFF',
|
'icon-button-fg': '#FFFFFF',
|
||||||
'icon-button-primary-bg': '#16B378',
|
'icon-button-primary-bg': '#16B378',
|
||||||
@ -352,6 +359,8 @@ export const GristLight: ThemeColors = {
|
|||||||
'code-view-params': '#444',
|
'code-view-params': '#444',
|
||||||
'code-view-string': '#880000',
|
'code-view-string': '#880000',
|
||||||
'code-view-number': '#880000',
|
'code-view-number': '#880000',
|
||||||
|
'code-view-builtin': '#397300',
|
||||||
|
'code-view-literal': '#78A960',
|
||||||
|
|
||||||
/* Importer */
|
/* Importer */
|
||||||
'importer-table-info-border': '#D9D9D9',
|
'importer-table-info-border': '#D9D9D9',
|
||||||
@ -394,6 +403,10 @@ export const GristLight: ThemeColors = {
|
|||||||
'access-rules-column-item-icon-fg': '#929299',
|
'access-rules-column-item-icon-fg': '#929299',
|
||||||
'access-rules-column-item-icon-hover-fg': '#FFFFFF',
|
'access-rules-column-item-icon-hover-fg': '#FFFFFF',
|
||||||
'access-rules-column-item-icon-hover-bg': '#929299',
|
'access-rules-column-item-icon-hover-bg': '#929299',
|
||||||
|
'access-rules-formula-editor-bg': 'white',
|
||||||
|
'access-rules-formula-editor-border-hover': '#D9D9D9',
|
||||||
|
'access-rules-formula-editor-bg-disabled': '#E8E8E8',
|
||||||
|
'access-rules-formula-editor-focus': '#16B378',
|
||||||
|
|
||||||
/* Cells */
|
/* Cells */
|
||||||
'cell-fg': 'black',
|
'cell-fg': 'black',
|
||||||
@ -435,4 +448,39 @@ export const GristLight: ThemeColors = {
|
|||||||
'tutorials-popup-border': '#D9D9D9',
|
'tutorials-popup-border': '#D9D9D9',
|
||||||
'tutorials-popup-header-fg': '#FFFFFF',
|
'tutorials-popup-header-fg': '#FFFFFF',
|
||||||
'tutorials-popup-box-bg': '#F5F5F5',
|
'tutorials-popup-box-bg': '#F5F5F5',
|
||||||
|
|
||||||
|
/* Ace Autocomplete */
|
||||||
|
'ace-autocomplete-primary-fg': '#444',
|
||||||
|
'ace-autocomplete-secondary-fg': '#8F8F8F',
|
||||||
|
'ace-autocomplete-highlighted-fg': '#000',
|
||||||
|
'ace-autocomplete-bg': '#FBFBFB',
|
||||||
|
'ace-autocomplete-border': 'lightgray',
|
||||||
|
'ace-autocomplete-link': '#16B378',
|
||||||
|
'ace-autocomplete-link-highlighted': '#009058',
|
||||||
|
'ace-autocomplete-active-line-bg': '#CAD6FA',
|
||||||
|
'ace-autocomplete-line-border-hover': '#ABBFFE',
|
||||||
|
'ace-autocomplete-line-bg-hover': 'rgba(233,233,253,0.4)',
|
||||||
|
|
||||||
|
/* Color Select */
|
||||||
|
'color-select-fg': '#262633',
|
||||||
|
'color-select-bg': 'white',
|
||||||
|
'color-select-shadow': 'rgba(38,38,51,0.6)',
|
||||||
|
'color-select-font-options-border': '#D9D9D9',
|
||||||
|
'color-select-font-option-fg': '#262633',
|
||||||
|
'color-select-font-option-bg': '#FFFFFF',
|
||||||
|
'color-select-font-option-bg-hover': '#F7F7F7',
|
||||||
|
'color-select-font-option-fg-selected': '#FFFFFF',
|
||||||
|
'color-select-font-option-bg-selected': '#262633',
|
||||||
|
'color-select-color-square-border': '#D9D9D9',
|
||||||
|
'color-select-color-square-border-empty': '#262633',
|
||||||
|
'color-select-input-fg': '#929299',
|
||||||
|
'color-select-input-bg': 'white',
|
||||||
|
'color-select-input-border': '#D9D9D9',
|
||||||
|
|
||||||
|
/* Highlighted Code */
|
||||||
|
'highlighted-code-block-bg': '#FFFFFF',
|
||||||
|
'highlighted-code-block-bg-disabled': '#E8E8E8',
|
||||||
|
'highlighted-code-fg': '#929299',
|
||||||
|
'highlighted-code-border': '#D9D9D9',
|
||||||
|
'highlighted-code-bg-disabled': '#E8E8E8',
|
||||||
};
|
};
|
||||||
|
@ -472,7 +472,7 @@ export class ApiServer {
|
|||||||
// GET /api/session/access/active
|
// GET /api/session/access/active
|
||||||
// Returns active user and active org (if any)
|
// Returns active user and active org (if any)
|
||||||
this._app.get('/api/session/access/active', expressWrap(async (req, res) => {
|
this._app.get('/api/session/access/active', expressWrap(async (req, res) => {
|
||||||
const fullUser = await this._getFullUser(req);
|
const fullUser = await this._getFullUser(req, {includePrefs: true});
|
||||||
const domain = getOrgFromRequest(req);
|
const domain = getOrgFromRequest(req);
|
||||||
const org = domain ? (await this._withSupportUserAllowedToView(
|
const org = domain ? (await this._withSupportUserAllowedToView(
|
||||||
domain, req, (scope) => this._dbManager.getOrg(scope, domain)
|
domain, req, (scope) => this._dbManager.getOrg(scope, domain)
|
||||||
@ -534,10 +534,10 @@ export class ApiServer {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getFullUser(req: Request): Promise<FullUser> {
|
private async _getFullUser(req: Request, options: {includePrefs?: boolean} = {}): Promise<FullUser> {
|
||||||
const mreq = req as RequestWithLogin;
|
const mreq = req as RequestWithLogin;
|
||||||
const userId = getUserId(mreq);
|
const userId = getUserId(mreq);
|
||||||
const user = await this._dbManager.getUser(userId);
|
const user = await this._dbManager.getUser(userId, options);
|
||||||
if (!user) { throw new ApiError("unable to find user", 400); }
|
if (!user) { throw new ApiError("unable to find user", 400); }
|
||||||
|
|
||||||
const fullUser = this._dbManager.makeFullUser(user);
|
const fullUser = this._dbManager.makeFullUser(user);
|
||||||
|
@ -7,6 +7,7 @@ import {BaseEntity, BeforeInsert, Column, Entity, JoinTable, ManyToMany, OneToMa
|
|||||||
import {Group} from "./Group";
|
import {Group} from "./Group";
|
||||||
import {Login} from "./Login";
|
import {Login} from "./Login";
|
||||||
import {Organization} from "./Organization";
|
import {Organization} from "./Organization";
|
||||||
|
import {Pref} from './Pref';
|
||||||
|
|
||||||
@Entity({name: 'users'})
|
@Entity({name: 'users'})
|
||||||
export class User extends BaseEntity {
|
export class User extends BaseEntity {
|
||||||
@ -34,6 +35,9 @@ export class User extends BaseEntity {
|
|||||||
@OneToMany(type => Login, login => login.user)
|
@OneToMany(type => Login, login => login.user)
|
||||||
public logins: Login[];
|
public logins: Login[];
|
||||||
|
|
||||||
|
@OneToMany(type => Pref, pref => pref.user)
|
||||||
|
public prefs: Pref[];
|
||||||
|
|
||||||
@ManyToMany(type => Group)
|
@ManyToMany(type => Group)
|
||||||
@JoinTable({
|
@JoinTable({
|
||||||
name: 'group_users',
|
name: 'group_users',
|
||||||
|
@ -482,8 +482,14 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
return await User.findOne({where: {ref}, relations: ["logins"]}) || undefined;
|
return await User.findOne({where: {ref}, relations: ["logins"]}) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUser(userId: number): Promise<User|undefined> {
|
public async getUser(
|
||||||
return await User.findOne({where: {id: userId}, relations: ["logins"]}) || undefined;
|
userId: number,
|
||||||
|
options: {includePrefs?: boolean} = {}
|
||||||
|
): Promise<User|undefined> {
|
||||||
|
const {includePrefs} = options;
|
||||||
|
const relations = ["logins"];
|
||||||
|
if (includePrefs) { relations.push("prefs"); }
|
||||||
|
return await User.findOne({where: {id: userId}, relations}) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getFullUser(userId: number): Promise<FullUser> {
|
public async getFullUser(userId: number): Promise<FullUser> {
|
||||||
@ -505,7 +511,8 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
name: user.name,
|
name: user.name,
|
||||||
picture: user.picture,
|
picture: user.picture,
|
||||||
ref: user.ref,
|
ref: user.ref,
|
||||||
locale: user.options?.locale
|
locale: user.options?.locale,
|
||||||
|
prefs: user.prefs?.find((p)=> p.orgId === null)?.prefs,
|
||||||
};
|
};
|
||||||
if (this.getAnonymousUserId() === user.id) {
|
if (this.getAnonymousUserId() === user.id) {
|
||||||
result.anonymous = true;
|
result.anonymous = true;
|
||||||
|
Loading…
Reference in New Issue
Block a user