diff --git a/app/client/aclui/ACLFormulaEditor.ts b/app/client/aclui/ACLFormulaEditor.ts index b000c2ea..02c299cc 100644 --- a/app/client/aclui/ACLFormulaEditor.ts +++ b/app/client/aclui/ACLFormulaEditor.ts @@ -18,9 +18,11 @@ export function aclFormulaEditor(options: ACLFormulaOptions) { // Set various editor options. editor.setTheme('ace/theme/chrome'); - editor.setOptions({enableLiveAutocompletion: true}); + // ACE editor resizes automatically when maxLines is set. + editor.setOptions({enableLiveAutocompletion: true, maxLines: 10}); editor.renderer.setShowGutter(false); // Default line numbers to hidden - editor.renderer.setPadding(0); + editor.renderer.setPadding(5); + editor.renderer.setScrollMargin(4, 4, 0, 0); editor.$blockScrolling = Infinity; editor.setReadOnly(options.readOnly); editor.setFontSize('12'); @@ -64,23 +66,9 @@ export function aclFormulaEditor(options: ACLFormulaOptions) { // Disable Tab/Shift+Tab commands to restore their regular behavior. (editor.commands as any).removeCommands(['indent', 'outdent']); - function resize() { - if (editor.renderer.lineHeight === 0) { - // Reschedule the resize, since it's not ready yet. Seems to happen occasionally. - setTimeout(resize, 50); - } - editorElem.style.width = 'auto'; - editorElem.style.height = (Math.max(1, session.getScreenLength()) * editor.renderer.lineHeight) + 'px'; - editor.resize(); - } - // Set the editor's initial value. editor.setValue(options.initialValue); - // Resize the editor on change, and initially once it's attached to the page. - editor.on('change', resize); - setTimeout(resize, 0); - return cssConditionInputAce( cssConditionInputAce.cls('-disabled', options.readOnly), dom.onDispose(() => editor.destroy()), @@ -91,7 +79,7 @@ export function aclFormulaEditor(options: ACLFormulaOptions) { const cssConditionInputAce = styled('div', ` width: 100%; min-height: 28px; - padding: 5px 6px 5px 6px; + padding: 1px; border-radius: 3px; border: 1px solid transparent; cursor: pointer; @@ -123,5 +111,6 @@ const cssConditionInputAce = styled('div', ` `); const cssAcePlaceholder = styled('div', ` + padding: 4px 5px; opacity: 0.5; `); diff --git a/app/client/aclui/AccessRules.ts b/app/client/aclui/AccessRules.ts index 04513705..a3647fc6 100644 --- a/app/client/aclui/AccessRules.ts +++ b/app/client/aclui/AccessRules.ts @@ -726,6 +726,18 @@ abstract class ObsRuleSet extends Disposable { ), cssCell4(cssRuleBody.cls(''), dom.forEach(this._body, part => part.buildRulePartDom()), + dom.maybe(use => !this.hasDefaultCondition(use), () => + cssColumnGroup( + {style: 'min-height: 28px'}, + cssCellIcon( + cssIconButton(icon('Plus'), + dom.on('click', () => this.addRulePart(null)), + testId('rule-add'), + ) + ), + testId('rule-extra-add'), + ) + ), ), testId('rule-set'), ); @@ -738,8 +750,9 @@ abstract class ObsRuleSet extends Disposable { } } - public addRulePart(beforeRule: ObsRulePart) { - const i = this._body.get().indexOf(beforeRule); + public addRulePart(beforeRule: ObsRulePart|null) { + const body = this._body.get(); + const i = beforeRule ? body.indexOf(beforeRule) : body.length; this._body.splice(i, 0, ObsRulePart.create(this._body, this, undefined)); } @@ -768,6 +781,11 @@ abstract class ObsRuleSet extends Disposable { return body[body.length - 1] === part; } + public hasDefaultCondition(use: UseCB): boolean { + const body = use(this._body); + return body.length > 0 && body[body.length - 1].hasEmptyCondition(use); + } + /** * Which permission bits to allow the user to set. */ @@ -1127,6 +1145,10 @@ class ObsRulePart extends Disposable { }; } + public hasEmptyCondition(use: UseCB): boolean { + return use(this._aclFormula) === ''; + } + public matches(use: UseCB, aclFormula: string, permissionsText: string): boolean { return (use(this._aclFormula) === aclFormula && permissionSetToText(use(this._permissions)) === permissionsText);