Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into fix-component-cache-f…
Browse files Browse the repository at this point in the history
…or-field
  • Loading branch information
tintinthong committed Mar 5, 2025
2 parents ddd5427 + 17d0250 commit a894b3d
Show file tree
Hide file tree
Showing 36 changed files with 839 additions and 345 deletions.
2 changes: 1 addition & 1 deletion packages/base/SkillCard/code-module-editing.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"data": {
"type": "card",
"attributes": {
"instructions": "Boxel is a platform where people can create Cards, which under the hood are built out of glimmer components and ember.\n\nCards are independent linkable items that get an ID. Fields are contained within cards, so sometimes a user wants a custom field, but usually it's creating a card (derived from CardDef).\n\nUse glimmer templating and typescript for the code. Remember the limitations of logic within glimmer templating code. Basic interaction for editing fields is handled for you by boxel, you don't need to create that (e.g. StringField has an edit template that allows a user to edit the data). Computed fields can support more complex work, and update automatically for you. Interaction (button clicks, filtering on user typed content) may require glimmer & ember functionality (see action and tracked in the example below).\n\nCards you create have three templates. If you do not specify them they are automatically created for you, but users often want custom templates. Each template is a glimmer template and can use ember functionality. These are specified as static in the card definition:\n\nimport { contains, containsMany, linksToMany, field, CardDef, Component, } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import NumberField from 'https://cardstack.com/base/number'; import BooleanField from 'https://cardstack.com/base/boolean'; // Important, this is the tracked decorator import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn } from '@ember/helper'; import { on } from '@ember/modifier';\n\nexport class MyCustomCard extends CardDef {\n\nstatic displayName = 'BoxelBuddyGuestList';\n\n// linksTo and linksToMany @field linkedData = linksToMany(() => AnotherCard);\n\n// A field that is computed from other data in the card @field computedData = contains(NumberField, { computeVia: function (this: MyCustomCard) { // implementation logic here return 1; }, });\n\n// Isolated templates are used when items are viewed on their own. Default to the isolated template static isolated = class Isolated extends Component { // Use tracked and action decorators to be able to use interactivity in the templates @tracked trackedValue = []; @action interactivity(event: InputEvent) {}\n\n// Glimmer template goes here, make sure the style tag is at the top level inside the template tag };\n\n// Embedded is when they appear in other cards static embedded = class Embedded extends Component { };\n\n// Fitted templates should be responsive to the size of the container they appear in static fitted = class Fitted extends Component { };\n\n// Edit is for the user editing the data. Use @fields let the field render itself static edit = class Edit extends Component { }; }\n\[email protected] lets the field render itself, very useful for editable fields. @model.fieldName gets the value out of the field.\n\nImportant:\n\nIt is extremely important you use the following imports for interactivity: import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn, get } from '@ember/helper'; import { on } from '@ember/modifier';\n\nRemember to define a field the following syntax is used:\n\n@field fieldname = contains(FieldType); @field fieldname = containsMany(FieldType);\n\nIf user asks you to make something editable, use contains or containsMany syntax for adding a field.\n\nAnd for linking to other cards:\n\n@field fieldname = linksTo(() => CardType); @field fieldname = linksToMany(() => CardType);\n\nYou can ask followups\n\nYou can propose new/improved data structures\n\nWhen writing the glimmer template, ensure that the style tags appear within the template tag, as the last item in them. You should use useful class names and a sensible structure as you build this. Use single quotes for the class names.\n\nWhen writing this, take care to remember ember and glimmer oddities. Accessing a list by index should use this format:\n\n{{(get this.args.model.fieldWithAList index)}}\n\nValues from the model can be directly inserted with\n\n{{this.args.model.fieldName}}\n\nand you can delegate rendering to the field with\n\n<@fields.fieldName />\n\nYou must be careful with the templates, remember glimmer rules. Do not put a dollar sign ($) directly in front of the brackets.\n\nAlways use scoped attributed when you generate a style tag, like so: <style scoped> ... CSS code ... </style>.\n\n<style> must be inside <template>, and the style tag must be the first child of <template>, not nested. So when you use <template>, the anatomy of should look like this:\n<template>\n... any html content...\n<style scoped>\n</style>\n</template>\n\nUnless otherwise instructed, use a modern but stylish theme. \n\nIn responses regarding to attached files, respond with a series of code patches where you output gts code and mark it whether it's for adding or deleting, in a clear succession, so that user can quickly just copy paste and put it in the code file, or delete code. \n\nUse multiple code snippets for every code change.",
"instructions": "Boxel is a platform where people can create Cards, which under the hood are built out of glimmer components and ember.\n\nCards are independent linkable items that get an ID. Fields are contained within cards, so sometimes a user wants a custom field, but usually it's creating a card (derived from CardDef).\n\nUse glimmer templating and typescript for the code. Remember the limitations of logic within glimmer templating code. Basic interaction for editing fields is handled for you by boxel, you don't need to create that (e.g. StringField has an edit template that allows a user to edit the data). Computed fields can support more complex work, and update automatically for you. Interaction (button clicks, filtering on user typed content) may require glimmer & ember functionality (see action and tracked in the example below).\n\nCards you create have three templates. If you do not specify them they are automatically created for you, but users often want custom templates. Each template is a glimmer template and can use ember functionality. These are specified as static in the card definition:\n\nimport { contains, containsMany, linksToMany, field, CardDef, Component, } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import NumberField from 'https://cardstack.com/base/number'; import BooleanField from 'https://cardstack.com/base/boolean'; // Important, this is the tracked decorator import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn } from '@ember/helper'; import { on } from '@ember/modifier';\n\nexport class MyCustomCard extends CardDef {\n\nstatic displayName = 'BoxelBuddyGuestList';\n\n// linksTo and linksToMany @field linkedData = linksToMany(() => AnotherCard);\n\n// A field that is computed from other data in the card @field computedData = contains(NumberField, { computeVia: function (this: MyCustomCard) { // implementation logic here return 1; }, });\n\n// Isolated templates are used when items are viewed on their own. Default to the isolated template static isolated = class Isolated extends Component { // Use tracked and action decorators to be able to use interactivity in the templates @tracked trackedValue = []; @action interactivity(event: InputEvent) {}\n\n// Glimmer template goes here, make sure the style tag is at the top level inside the template tag };\n\n// Embedded is when they appear in other cards static embedded = class Embedded extends Component { };\n\n// Fitted templates should be responsive to the size of the container they appear in static fitted = class Fitted extends Component { };\n\n// Edit is for the user editing the data. Use @fields let the field render itself static edit = class Edit extends Component { }; }\n\[email protected] lets the field render itself, very useful for editable fields. @model.fieldName gets the value out of the field.\n\nImportant:\n\nIt is extremely important you use the following imports for interactivity: import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn, get } from '@ember/helper'; import { on } from '@ember/modifier';\n\nRemember to define a field the following syntax is used:\n\n@field fieldname = contains(FieldType); @field fieldname = containsMany(FieldType);\n\nIf user asks you to make something editable, use contains or containsMany syntax for adding a field.\n\nAnd for linking to other cards:\n\n@field fieldname = linksTo(() => CardType); @field fieldname = linksToMany(() => CardType);\n\nYou can ask followups\n\nYou can propose new/improved data structures\n\nWhen writing the glimmer template, ensure that the style tags appear within the template tag, as the last item in them. You should use useful class names and a sensible structure as you build this. Use single quotes for the class names.\n\nWhen writing this, take care to remember ember and glimmer oddities. Accessing a list by index should use this format:\n\n{{(get this.args.model.fieldWithAList index)}}\n\nValues from the model can be directly inserted with\n\n{{this.args.model.fieldName}}\n\nand you can delegate rendering to the field with\n\n<@fields.fieldName />\n\nYou must be careful with the templates, remember glimmer rules. Do not put a dollar sign ($) directly in front of the brackets.\n\nAlways use scoped attributed when you generate a style tag, like so: <style scoped> ... CSS code ... </style>.\n\n<style> must be inside <template>, and the style tag must be the first child of <template>, not nested. So when you use <template>, the anatomy of should look like this:\n<template>\n... any html content...\n<style scoped>\n</style>\n</template>\n\nUnless otherwise instructed, use a modern but stylish theme. \n\nIn responses regarding to attached files, respond with a series of code patches where you output gts code and mark it whether it's for adding or deleting, in a clear succession, so that user can quickly just copy paste and put it in the code file, or delete code. \n\nUse multiple code snippets for every code change. Use gts language for markdown in your code snippets, like so: ```gts ...code... ```.",
"commands": [],
"title": "Code Module Editing",
"description": null,
Expand Down
3 changes: 2 additions & 1 deletion packages/boxel-ui/addon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"dayjs": "^1.11.7",
"ember-basic-dropdown": "^8.0.0",
"ember-css-url": "^1.0.0",
"ember-concurrency": "^4.0.1",
"ember-concurrency-ts": "^0.3.1",
"ember-draggable-modifiers": "^1.0.0",
"ember-focus-trap": "^1.0.1",
"ember-freestyle": "^0.20.0",
Expand Down Expand Up @@ -83,7 +85,6 @@
"@typescript-eslint/parser": "^7.16.1",
"babel-plugin-ember-template-compilation": "^2.2.1",
"concurrently": "^8.0.1",
"ember-concurrency": "^4.0.1",
"ember-template-imports": "^4.1.1",
"ember-template-lint": "^5.11.2",
"ember-template-lint-plugin-prettier": "^5.0.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/boxel-ui/addon/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CardHeader from './components/card-header/index.gts';
import CircleSpinner from './components/circle-spinner/index.gts';
import ColorPalette from './components/color-palette/index.gts';
import ColorPicker from './components/color-picker/index.gts';
import CopyButton from './components/copy-button/index.gts';
import DateRangePicker from './components/date-range-picker/index.gts';
import DndKanbanBoard, {
type DndItem,
Expand Down Expand Up @@ -83,6 +84,7 @@ export {
CircleSpinner,
ColorPalette,
ColorPicker,
CopyButton,
DateRangePicker,
DndColumn,
DndItem,
Expand Down
52 changes: 52 additions & 0 deletions packages/boxel-ui/addon/src/components/copy-button/index.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { on } from '@ember/modifier';
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
import { taskFor } from 'ember-concurrency-ts';

import Copy from '../../icons/copy.gts';
import IconButton from '../icon-button/index.gts';
import Tooltip from '../tooltip/index.gts';

interface Signature {
Args: {
textToCopy: string;
};
Element: HTMLElement;
}

export default class CopyButton extends Component<Signature> {
@tracked recentlyCopied = false;

@task
private *copyToClipboardTask(this: CopyButton) {
yield navigator.clipboard.writeText(this.args.textToCopy);
this.recentlyCopied = true;

setTimeout(() => (this.recentlyCopied = false), 2000);
}

@action
private copyToClipboard() {
taskFor(this.copyToClipboardTask).perform();
}

<template>
<Tooltip @placement='top' class='editability-icon'>
<:trigger>
<IconButton
@icon={{Copy}}
@width='18px'
@height='18px'
{{on 'click' this.copyToClipboard}}
aria-label='Copy'
data-test-boxel-copy-button
/>
</:trigger>
<:content>
{{if this.recentlyCopied 'Copied!' 'Copy to clipboard'}}
</:content>
</Tooltip>
</template>
}
25 changes: 25 additions & 0 deletions packages/boxel-ui/addon/src/components/copy-button/usage.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { fn } from '@ember/helper';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import FreestyleUsage from 'ember-freestyle/components/freestyle/usage';

import CopyButton from './index.gts';

export default class CopyButtonUsage extends Component {
@tracked textToCopy: string = 'Text to copy';

<template>
<FreestyleUsage @name='CopyButton'>
<:example>
<CopyButton @textToCopy={{this.textToCopy}} />
</:example>
<:api as |Args|>
<Args.String
@name='text'
@onInput={{fn (mut this.textToCopy)}}
@value={{this.textToCopy}}
/>
</:api>
</FreestyleUsage>
</template>
}
2 changes: 2 additions & 0 deletions packages/boxel-ui/addon/src/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CardContainerUsage from './components/card-container/usage.gts';
import CardContentContainerUsage from './components/card-content-container/usage.gts';
import CardHeaderUsage from './components/card-header/usage.gts';
import CircleSpinnerUsage from './components/circle-spinner/usage.gts';
import CopyButtonUsage from './components/copy-button/usage.gts';
import DateRangePickerUsage from './components/date-range-picker/usage.gts';
import DragAndDropUsage from './components/drag-and-drop/usage.gts';
import DropdownTriggerUsage from './components/dropdown/trigger/usage.gts';
Expand Down Expand Up @@ -83,4 +84,5 @@ export const ALL_USAGE_COMPONENTS = [
['ViewSelector', ViewSelectorUsage],
['ColorPicker', ColorPickerUsage],
['ColorPalette', ColorPaletteUsage],
['CopyButtonUsage', CopyButtonUsage],
];
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,6 @@ export default class FormattedMessage extends Component<FormattedMessageSignatur
scrollbar: {
alwaysConsumeMouseWheel: false,
},
lineNumbers: 'off',
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default class RoomMessageCommand extends Component<Signature> {
scrollbar: {
alwaysConsumeMouseWheel: false,
},
lineNumbers: 'off',
};

private get previewCommandCode() {
Expand Down
33 changes: 32 additions & 1 deletion packages/host/app/components/operator-mode/code-editor.gts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import type { MonacoSDK } from '@cardstack/host/services/monaco-service';

import type OperatorModeStateService from '@cardstack/host/services/operator-mode-state-service';

import RecentFilesService from '@cardstack/host/services/recent-files-service';

import BinaryFileInfo from './binary-file-info';

interface Signature {
Expand All @@ -54,6 +56,7 @@ export default class CodeEditor extends Component<Signature> {
@service private declare operatorModeStateService: OperatorModeStateService;
@service private declare cardService: CardService;
@service private declare environmentService: EnvironmentService;
@service private declare recentFilesService: RecentFilesService;

@tracked private maybeMonacoSDK: MonacoSDK | undefined;

Expand Down Expand Up @@ -122,6 +125,18 @@ export default class CodeEditor extends Component<Signature> {

@cached
private get initialMonacoCursorPosition() {
if (this.codePath) {
let recentFile = this.recentFilesService.findRecentFileByURL(
this.codePath.toString(),
);
if (recentFile?.cursorPosition) {
return new Position(
recentFile.cursorPosition.line,
recentFile.cursorPosition.column,
);
}
}

let loc =
this.args.selectedDeclaration?.path?.node &&
'body' in this.args.selectedDeclaration.path.node &&
Expand Down Expand Up @@ -195,6 +210,22 @@ export default class CodeEditor extends Component<Signature> {
}
}

@action
private onCursorPositionChange(position: Position) {
this.selectDeclarationByMonacoCursorPosition(position);

if (!this.codePath) {
return;
}
this.recentFilesService.updateCursorPositionByURL(
this.codePath.toString(),
{
line: position.lineNumber,
column: position.column,
},
);
}

@action
private selectDeclarationByMonacoCursorPosition(position: Position) {
let declarationCursorOn = this.declarations.find(
Expand Down Expand Up @@ -303,7 +334,7 @@ export default class CodeEditor extends Component<Signature> {
monacoSDK=this.monacoSDK
language=this.language
initialCursorPosition=this.initialMonacoCursorPosition
onCursorPositionChange=this.selectDeclarationByMonacoCursorPosition
onCursorPositionChange=this.onCursorPositionChange
readOnly=@isReadOnly
editorDisplayOptions=(hash lineNumbersMinChars=3 fontSize=13)
}}
Expand Down
Loading

0 comments on commit a894b3d

Please sign in to comment.