Skip to content

Commit 90ce75e

Browse files
committed
Add copy from seed realm checkbox
1 parent e064ab8 commit 90ce75e

File tree

8 files changed

+165
-11
lines changed

8 files changed

+165
-11
lines changed

packages/boxel-ui/addon/src/components/input/index.gts

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import cn from '../../helpers/cn.ts';
77
import element from '../../helpers/element.ts';
88
import optional from '../../helpers/optional.ts';
99
import pick from '../../helpers/pick.ts';
10-
import { and, eq, not } from '../../helpers/truth-helpers.ts';
10+
import { and, bool, eq, not } from '../../helpers/truth-helpers.ts';
1111
import FailureBordered from '../../icons/failure-bordered.gts';
1212
import IconSearch from '../../icons/icon-search.gts';
1313
import LoadingIndicator from '../../icons/loading-indicator.gts';
@@ -62,6 +62,7 @@ export interface Signature {
6262
id?: string;
6363
max?: string | number;
6464
onBlur?: (ev: Event) => void;
65+
onChange?: (ev: Event) => void;
6566
onFocus?: (ev: Event) => void;
6667
onInput?: (val: string) => void;
6768
onKeyPress?: (ev: KeyboardEvent) => void;
@@ -155,6 +156,7 @@ export default class BoxelInput extends Component<Signature> {
155156
id={{this.id}}
156157
type={{this.type}}
157158
value={{@value}}
159+
checked={{if (and (eq @type 'checkbox') (bool @value)) @value}}
158160
placeholder={{@placeholder}}
159161
max={{@max}}
160162
required={{@required}}
@@ -177,6 +179,7 @@ export default class BoxelInput extends Component<Signature> {
177179
{{on 'blur' (optional @onBlur)}}
178180
{{on 'keypress' (optional @onKeyPress)}}
179181
{{on 'focus' (optional @onFocus)}}
182+
{{on 'change' (optional @onChange)}}
180183
...attributes
181184
/>
182185
{{#if this.isSearch}}

packages/host/app/components/operator-mode/workspace-chooser/add-workspace.gts

+43
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default class AddWorkspace extends Component<Signature> {
4040
@service private declare matrixService: MatrixService;
4141
@tracked private isModalOpen = false;
4242
@tracked private endpoint = '';
43+
@tracked private copyFromSeedRealm = true;
4344
@tracked private displayName = '';
4445
@tracked private hasUserEditedEndpoint = false;
4546
@tracked private error: string | null = null;
@@ -54,6 +55,9 @@ export default class AddWorkspace extends Component<Signature> {
5455
this.endpoint = cleanseString(value);
5556
}
5657
};
58+
private toggleCopyFromSeedRealm = () => {
59+
this.copyFromSeedRealm = !this.copyFromSeedRealm;
60+
};
5761
private closeModal = () => {
5862
this.isModalOpen = false;
5963
};
@@ -65,6 +69,7 @@ export default class AddWorkspace extends Component<Signature> {
6569
name: this.displayName,
6670
iconURL: iconURLFor(this.displayName),
6771
backgroundURL: getRandomBackgroundURL(),
72+
copyFromSeedRealm: this.copyFromSeedRealm,
6873
});
6974
this.closeModal();
7075
} catch (e: any) {
@@ -135,6 +140,23 @@ export default class AddWorkspace extends Component<Signature> {
135140
@helperText='The endpoint is the unique identifier for your workspace. Use letters, numbers, and hyphens only.'
136141
/>
137142
</FieldContainer>
143+
<FieldContainer
144+
@label='Initialize workspace with'
145+
@tag='label'
146+
class='field'
147+
>
148+
<label class='copy-from-seed-label'>
149+
<BoxelInput
150+
data-test-copy-from-seed-field
151+
class='copy-from-seed-checkbox'
152+
placeholder='Workspace Endpoint'
153+
@type='checkbox'
154+
@value={{this.copyFromSeedRealm}}
155+
@onChange={{this.toggleCopyFromSeedRealm}}
156+
/>
157+
<span>Cards and Fields from Seed Workspace</span>
158+
</label>
159+
</FieldContainer>
138160
{{/if}}
139161
{{/if}}
140162
{{#if this.error}}
@@ -232,6 +254,27 @@ export default class AddWorkspace extends Component<Signature> {
232254
.spinner {
233255
--boxel-loading-indicator-size: 2.5rem;
234256
}
257+
.copy-from-seed-label {
258+
display: flex;
259+
align-items: center;
260+
gap: calc(var(--boxel-sp-sm) + 1px);
261+
}
262+
.copy-from-seed-label span {
263+
color: var(--boxel-label-color);
264+
font: var(--boxel-font-sm);
265+
letter-spacing: var(--boxel-lsp-xs);
266+
}
267+
.copy-from-seed-label :deep(.input-container) {
268+
grid-template-columns: 1fr;
269+
width: auto;
270+
}
271+
.copy-from-seed-checkbox {
272+
--boxel-input-height: 17px;
273+
grid-area: pre-icon;
274+
justify-self: start;
275+
margin: 0;
276+
width: auto;
277+
}
235278
</style>
236279
</template>
237280
}

packages/host/app/services/matrix-service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -337,17 +337,20 @@ export default class MatrixService extends Service {
337337
name,
338338
iconURL,
339339
backgroundURL,
340+
copyFromSeedRealm,
340341
}: {
341342
endpoint: string;
342343
name: string;
343344
iconURL?: string;
344345
backgroundURL?: string;
346+
copyFromSeedRealm?: boolean;
345347
}) {
346348
let personalRealmURL = await this.realmServer.createRealm({
347349
endpoint,
348350
name,
349351
iconURL,
350352
backgroundURL,
353+
copyFromSeedRealm,
351354
});
352355
let { realms = [] } =
353356
(await this.client.getAccountDataFromServer<{ realms: string[] }>(

packages/host/app/services/realm-server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export default class RealmServerService extends Service {
115115
name: string;
116116
iconURL?: string;
117117
backgroundURL?: string;
118+
copyFromSeedRealm?: boolean;
118119
}) {
119120
await this.login();
120121

packages/realm-server/handlers/handle-create-realm.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface RealmCreationJSON {
2424
name: string;
2525
backgroundURL?: string;
2626
iconURL?: string;
27+
copyFromSeedRealm?: boolean;
2728
};
2829
};
2930
}

packages/realm-server/routes.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ export type CreateRoutesArgs = {
2222
name,
2323
backgroundURL,
2424
iconURL,
25+
copyFromSeedRealm,
2526
}: {
2627
ownerUserId: string;
2728
endpoint: string;
2829
name: string;
2930
backgroundURL?: string;
3031
iconURL?: string;
32+
copyFromSeedRealm?: boolean,
3133
}) => Promise<Realm>;
3234
serveIndex: (ctxt: Koa.Context, next: Koa.Next) => Promise<any>;
3335
serveFromRealm: (ctxt: Koa.Context, next: Koa.Next) => Promise<any>;

packages/realm-server/server.ts

+16-9
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,14 @@ export class RealmServer {
270270
name,
271271
backgroundURL,
272272
iconURL,
273+
copyFromSeedRealm = true
273274
}: {
274275
ownerUserId: string; // note matrix userIDs look like "@mango:boxel.ai"
275276
endpoint: string;
276277
name: string;
277278
backgroundURL?: string;
278279
iconURL?: string;
280+
copyFromSeedRealm?: boolean,
279281
}): Promise<Realm> => {
280282
if (
281283
this.realms.find(
@@ -336,15 +338,20 @@ export class RealmServer {
336338
...(backgroundURL ? { backgroundURL } : {}),
337339
});
338340
if (this.seedPath) {
339-
let ignoreList = IGNORE_SEED_FILES.map((file) =>
340-
join(this.seedPath!.replace(/\/$/, ''), file),
341-
);
342-
copySync(this.seedPath, realmPath, {
343-
filter: (src, _dest) => {
344-
return !ignoreList.includes(src);
345-
},
346-
});
347-
this.log.debug(`seed files for new realm ${url} copied to ${realmPath}`);
341+
if (copyFromSeedRealm) {
342+
let ignoreList = IGNORE_SEED_FILES.map((file) =>
343+
join(this.seedPath!.replace(/\/$/, ''), file),
344+
);
345+
346+
copySync(this.seedPath, realmPath, {
347+
filter: (src, _dest) => {
348+
return !ignoreList.includes(src);
349+
},
350+
});
351+
this.log.debug(`seed files for new realm ${url} copied to ${realmPath}`);
352+
} else {
353+
copySync(join(this.seedPath!.replace(/\/$/, ''), 'index.json'), join(realmPath!.replace(/\/$/, ''), 'index.json'));
354+
}
348355
}
349356

350357
let realm = new Realm(

packages/realm-server/tests/realm-server-test.ts

+95-1
Original file line numberDiff line numberDiff line change
@@ -3049,7 +3049,7 @@ module(basename(__filename), function () {
30493049
});
30503050
});
30513051

3052-
module('various other realm tests', function (hooks) {
3052+
module.only('various other realm tests', function (hooks) {
30533053
let testRealmHttpServer2: Server;
30543054
let testRealmServer2: RealmServer;
30553055
let testRealm2: Realm;
@@ -3301,6 +3301,100 @@ module(basename(__filename), function () {
33013301
}
33023302
});
33033303

3304+
test('POST /_create-realm without copying seed realm', async function (assert) {
3305+
// we randomize the realm and owner names so that we can isolate matrix
3306+
// test state--there is no "delete user" matrix API
3307+
let endpoint = `test-realm-${uuidv4()}`;
3308+
let owner = 'mango';
3309+
let ownerUserId = '@mango:boxel.ai';
3310+
let response = await request2
3311+
.post('/_create-realm')
3312+
.set('Accept', 'application/vnd.api+json')
3313+
.set('Content-Type', 'application/json')
3314+
.set(
3315+
'Authorization',
3316+
`Bearer ${createRealmServerJWT(
3317+
{ user: ownerUserId, sessionRoom: 'session-room-test' },
3318+
secretSeed,
3319+
)}`,
3320+
)
3321+
.send(
3322+
JSON.stringify({
3323+
data: {
3324+
type: 'realm',
3325+
attributes: {
3326+
...testRealmInfo,
3327+
endpoint,
3328+
backgroundURL: 'http://example.com/background.jpg',
3329+
iconURL: 'http://example.com/icon.jpg',
3330+
copyFromSeedRealm: false,
3331+
},
3332+
},
3333+
}),
3334+
);
3335+
3336+
assert.strictEqual(response.status, 201, 'HTTP 201 status');
3337+
let json = response.body;
3338+
assert.deepEqual(
3339+
json,
3340+
{
3341+
data: {
3342+
type: 'realm',
3343+
id: `${testRealm2URL.origin}/${owner}/${endpoint}/`,
3344+
attributes: {
3345+
...testRealmInfo,
3346+
endpoint,
3347+
backgroundURL: 'http://example.com/background.jpg',
3348+
iconURL: 'http://example.com/icon.jpg',
3349+
copyFromSeedRealm: false,
3350+
},
3351+
},
3352+
},
3353+
'realm creation JSON is correct',
3354+
);
3355+
3356+
let realmPath = join(dir.name, 'realm_server_2', owner, endpoint);
3357+
let realmJSON = readJSONSync(join(realmPath, '.realm.json'));
3358+
assert.deepEqual(
3359+
realmJSON,
3360+
{
3361+
name: 'Test Realm',
3362+
backgroundURL: 'http://example.com/background.jpg',
3363+
iconURL: 'http://example.com/icon.jpg',
3364+
},
3365+
'.realm.json is correct',
3366+
);
3367+
assert.ok(
3368+
existsSync(join(realmPath, 'index.json')),
3369+
'seed file index.json exists',
3370+
);
3371+
assert.notOk(
3372+
existsSync(
3373+
join(
3374+
realmPath,
3375+
'HelloWorld/47c0fc54-5099-4e9c-ad0d-8a58572d05c0.json',
3376+
),
3377+
),
3378+
'seed file HelloWorld/47c0fc54-5099-4e9c-ad0d-8a58572d05c0.json exists',
3379+
);
3380+
assert.notOk(
3381+
existsSync(join(realmPath, 'package.json')),
3382+
'ignored seed file package.json does not exist',
3383+
);
3384+
assert.notOk(
3385+
existsSync(join(realmPath, 'node_modules')),
3386+
'ignored seed file node_modules/ does not exist',
3387+
);
3388+
assert.notOk(
3389+
existsSync(join(realmPath, '.gitignore')),
3390+
'ignored seed file .gitignore does not exist',
3391+
);
3392+
assert.notOk(
3393+
existsSync(join(realmPath, 'tsconfig.json')),
3394+
'ignored seed file tsconfig.json does not exist',
3395+
);
3396+
});
3397+
33043398
test('dynamically created realms are not publicly readable or writable', async function (assert) {
33053399
let endpoint = `test-realm-${uuidv4()}`;
33063400
let owner = 'mango';

0 commit comments

Comments
 (0)