Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/mocks/browser-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { handlers as stylesheetHandlers } from './handlers/stylesheet.handlers.j
import { handlers as partialViewsHandlers } from './handlers/partial-views.handlers.js';
import { handlers as tagHandlers } from './handlers/tag-handlers.js';
import { handlers as configHandlers } from './handlers/config.handlers.js';
import { handlers as scriptHandlers } from './handlers/scripts.handlers.js';

const handlers = [
serverHandlers.serverVersionHandler,
Expand Down Expand Up @@ -65,6 +66,7 @@ const handlers = [
...partialViewsHandlers,
...tagHandlers,
...configHandlers,
...scriptHandlers,
];

switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) {
Expand Down
238 changes: 238 additions & 0 deletions src/mocks/data/scripts.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import { UmbData } from './data.js';
import { UmbEntityData } from './entity.data.js';
import { createFileItemResponseModelBaseModel, createFileSystemTreeItem, createTextFileItem } from './utils.js';
import {
CreatePathFolderRequestModel,
CreateTextFileViewModelBaseModel,
FileSystemTreeItemPresentationModel,
PagedFileSystemTreeItemPresentationModel,
ScriptItemResponseModel,
ScriptResponseModel,
UpdateScriptRequestModel,
} from '@umbraco-cms/backoffice/backend-api';

type ScriptsDataItem = ScriptResponseModel & FileSystemTreeItemPresentationModel;

export const data: Array<ScriptsDataItem> = [
{
path: 'some-folder',
isFolder: true,
name: 'some-folder',
type: 'script',
hasChildren: true,
},
{
path: 'another-folder',
isFolder: true,
name: 'another-folder',
type: 'script',
hasChildren: true,
},
{
path: 'very important folder',
isFolder: true,
name: 'very important folder',
type: 'script',
hasChildren: true,
},
{
path: 'some-folder/ugly script.js',
isFolder: false,
name: 'ugly script.js',
type: 'script',
hasChildren: false,
content: `function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}

console.log(makeid(5));`,
},
{
path: 'some-folder/nice script.js',
isFolder: false,
name: 'nice script.js',
type: 'script',
hasChildren: false,
content: `var items = {
"item_1": "1",
"item_2": "2",
"item_3": "3"
}
for (var item in items) {
console.log(items[item]);
}`,
},
{
path: 'another-folder/only bugs.js',
isFolder: false,
name: 'only bugs.js',
type: 'script',
hasChildren: false,
content: `var my_arr = [4, '', 0, 10, 7, '', false, 10];

my_arr = my_arr.filter(Boolean);

console.log(my_arr);`,
},
{
path: 'very important folder/no bugs at all.js',
isFolder: false,
name: 'no bugs at all.js',
type: 'script',
hasChildren: false,
content: `const date_str = "07/20/2021";
const date = new Date(date_str);
const full_day_name = date.toLocaleDateString('default', { weekday: 'long' });
// -> to get full day name e.g. Tuesday

const short_day_name = date.toLocaleDateString('default', { weekday: 'short' });
console.log(short_day_name);
// -> TO get the short day name e.g. Tue`,
},
{
path: 'very important folder/nope.js',
isFolder: false,
name: 'nope.js',
type: 'script',
hasChildren: false,
content: `// Define an object
const employee = {
"name": "John Deo",
"department": "IT",
"project": "Inventory Manager"
};

// Remove a property
delete employee["project"];

console.log(employee);`,
},
];

class UmbScriptsData extends UmbData<ScriptsDataItem> {
constructor() {
super(data);
}

getTreeRoot(): PagedFileSystemTreeItemPresentationModel {
const items = this.data.filter((item) => item.path?.includes('/') === false);
const treeItems = items.map((item) => createFileSystemTreeItem(item));
const total = items.length;
return { items: treeItems, total };
}

getTreeItemChildren(parentPath: string): PagedFileSystemTreeItemPresentationModel {
const items = this.data.filter((item) => item.path?.startsWith(parentPath));
const treeItems = items.map((item) => createFileSystemTreeItem(item));
const total = items.length;
return { items: treeItems, total };
}

getTreeItem(paths: Array<string>): Array<FileSystemTreeItemPresentationModel> {
const items = this.data.filter((item) => paths.includes(item.path ?? ''));
return items.map((item) => createFileSystemTreeItem(item));
}

getItem(paths: Array<string>): Array<ScriptItemResponseModel> {
const items = this.data.filter((item) => paths.includes(item.path ?? ''));
return items.map((item) => createFileItemResponseModelBaseModel(item));
}

getFolder(path: string): FileSystemTreeItemPresentationModel {
const items = data.filter((item) => item.isFolder && item.path === path);
return items as FileSystemTreeItemPresentationModel;
}

postFolder(payload: CreatePathFolderRequestModel) {
const newFolder = {
path: `${payload.parentPath ?? ''}/${payload.name}`,
isFolder: true,
name: payload.name,
type: 'script',
hasChildren: false,
};
return this.insert(newFolder);
}

deleteFolder(path: string) {
return this.delete([path]);
}

getScript(path: string): ScriptResponseModel | undefined {
return createTextFileItem(this.data.find((item) => item.path === path));
}

insertScript(item: CreateTextFileViewModelBaseModel) {
const newItem: ScriptsDataItem = {
...item,
path: `${item.parentPath}/${item.name}.js}`,
isFolder: false,
hasChildren: false,
type: 'script',
};

this.insert(newItem);
return newItem;
}

insert(item: ScriptsDataItem) {
const exits = this.data.find((i) => i.path === item.path);

if (exits) {
throw new Error(`Item with path ${item.path} already exists`);
}

this.data.push(item);

return item;
}

updateData(updateItem: UpdateScriptRequestModel) {
const itemIndex = this.data.findIndex((item) => item.path === updateItem.existingPath);
const item = this.data[itemIndex];
if (!item) return;

// TODO: revisit this code, seems like something we can solve smarter/type safer now:
const itemKeys = Object.keys(item);
const newItem = { ...item };

for (const [key] of Object.entries(updateItem)) {
if (itemKeys.indexOf(key) !== -1) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
newItem[key] = updateItem[key];
}
}
// Specific to fileSystem, we need to update path based on name:
const dirName = updateItem.existingPath?.substring(0, updateItem.existingPath.lastIndexOf('/'));
newItem.path = `${dirName}${dirName ? '/' : ''}${updateItem.name}`;

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.data[itemIndex] = newItem;
}

delete(paths: Array<string>) {
const pathsOfItemsToDelete = this.data
.filter((item) => {
if (!item.path) throw new Error('Item has no path');
return paths.includes(item.path);
})
.map((item) => item.path);

this.data = this.data.filter((item) => {
if (!item.path) throw new Error('Item has no path');
return paths.indexOf(item.path) === -1;
});

return pathsOfItemsToDelete;
}
}

export const umbScriptsData = new UmbScriptsData();
86 changes: 86 additions & 0 deletions src/mocks/handlers/scripts.handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const { rest } = window.MockServiceWorker;
import { RestHandler, MockedRequest, DefaultBodyType } from 'msw';
import { umbScriptsData } from '../data/scripts.data.js';
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
import { CreatePathFolderRequestModel, CreateTextFileViewModelBaseModel } from '@umbraco-cms/backoffice/backend-api';

const treeHandlers = [
rest.get(umbracoPath('/tree/script/root'), (req, res, ctx) => {
const response = umbScriptsData.getTreeRoot();
return res(ctx.status(200), ctx.json(response));
}),

rest.get(umbracoPath('/tree/script/children'), (req, res, ctx) => {
const path = req.url.searchParams.get('path');
if (!path) return;

const response = umbScriptsData.getTreeItemChildren(path);
return res(ctx.status(200), ctx.json(response));
}),

rest.get(umbracoPath('/tree/script/item'), (req, res, ctx) => {
const paths = req.url.searchParams.getAll('paths');
if (!paths) return;

const items = umbScriptsData.getTreeItem(paths);
return res(ctx.status(200), ctx.json(items));
}),
];

const detailHandlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [
rest.get(umbracoPath('/script'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.getScript(path);
return res(ctx.status(200), ctx.json(response));
}),

rest.get(umbracoPath('/script/item'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400, 'no body found'));
const response = umbScriptsData.getItem([path]);
return res(ctx.status(200), ctx.json(response));
}),

rest.post(umbracoPath('/script'), async (req, res, ctx) => {
const requestBody = (await req.json()) as CreateTextFileViewModelBaseModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
const response = umbScriptsData.insertScript(requestBody);
return res(ctx.status(200), ctx.json(response));
}),

rest.delete(umbracoPath('/script'), (req, res, ctx) => {
const path = req.url.searchParams.get('path');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.delete([path]);
return res(ctx.status(200), ctx.json(response));
}),
rest.put(umbracoPath('/script'), async (req, res, ctx) => {
const requestBody = (await req.json()) as CreateTextFileViewModelBaseModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
const response = umbScriptsData.updateData(requestBody);
return res(ctx.status(200));
}),
];

const folderHandlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [
rest.get(umbracoPath('script/folder'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.getFolder(path);
return res(ctx.status(200), ctx.json(response));
}),
rest.post(umbracoPath('script/folder'), (req, res, ctx) => {
const requestBody = req.json() as CreatePathFolderRequestModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
return res(ctx.status(200));
}),
rest.delete(umbracoPath('script/folder'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.deleteFolder(path);
return res(ctx.status(200), ctx.json(response));
}),
];

export const handlers = [...treeHandlers, ...detailHandlers, ...folderHandlers];
2 changes: 2 additions & 0 deletions src/packages/templating/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { manifests as menuManifests } from './menu.manifests.js';
import { manifests as templateManifests } from './templates/manifests.js';
import { manifests as stylesheetManifests } from './stylesheets/manifests.js';
import { manifests as partialManifests } from './partial-views/manifests.js';
import { manifests as scriptsManifest } from './scripts/manifests.js';
import { manifests as modalManifests } from './modals/manifests.js';

export const manifests = [
Expand All @@ -10,4 +11,5 @@ export const manifests = [
...stylesheetManifests,
...partialManifests,
...modalManifests,
...scriptsManifest,
];
34 changes: 34 additions & 0 deletions src/packages/templating/scripts/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ScriptResponseModel } from '@umbraco-cms/backoffice/backend-api';

export type ScriptDetails = ScriptResponseModel;

//ENTITY TYPES
export const SCRIPTS_ENTITY_TYPE = 'script';
export const SCRIPTS_ROOT_ENTITY_TYPE = 'script-root';
export const SCRIPTS_FOLDER_ENTITY_TYPE = 'script-folder';
export const SCRIPTS_FOLDER_EMPTY_ENTITY_TYPE = 'script-folder-empty';


export const SCRIPTS_STORE_ALIAS = 'Umb.Store.Scripts';
export const UMB_SCRIPTS_STORE_CONTEXT_TOKEN_ALIAS = 'Umb.Store.Scripts.Context.Token';

export const SCRIPTS_REPOSITORY_ALIAS = 'Umb.Repository.Scripts';

export const SCRIPTS_MENU_ITEM_ALIAS = 'Umb.MenuItem.Scripts';

//TREE
export const SCRIPTS_TREE_ALIAS = 'Umb.Tree.Scripts';
export const SCRIPTS_TREE_ITEM_ALIAS = 'Umb.TreeItem.Scripts';
export const SCRIPTS_TREE_STORE_ALIAS = 'Umb.Store.Scripts.Tree';
export const UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN_ALIAS = 'Umb.Store.Scripts.Tree.Context.Token';

//ENTITY (tree) ACTIONS
export const SCRIPTS_ENTITY_ACTION_DELETE_ALIAS = 'Umb.EntityAction.Scripts.Delete';
export const SCRIPTS_ENTITY_ACTION_CREATE_NEW_ALIAS = 'Umb.EntityAction.ScriptsFolder.Create.New';
export const SCRIPTS_ENTITY_ACTION_DELETE_FOLDER_ALIAS = 'Umb.EntityAction.ScriptsFolder.DeleteFolder';
export const SCRIPTS_ENTITY_ACTION_CREATE_FOLDER_NEW_ALIAS = 'Umb.EntityAction.ScriptsFolder.CreateFolder';

//WORKSPACE
export const SCRIPTS_WORKSPACE_ALIAS = 'Umb.Workspace.Scripts';
export const SCRIPTS_WORKSPACE_ACTION_SAVE_ALIAS = 'Umb.WorkspaceAction.Scripts.Save';

Loading