import _ from 'lodash'
import app from './app'
import events from './events'
import config from './config'
import remoteCommands from './remote-commands'
import { createFile } from './ide/lib/file-helper'
import tools from './tools'
const emit = events.create({
addMenuItem: 'menu:add',
removeMenuItem: 'menu:remove',
updateMenuItem: 'menu:update'
})
const callbacks = {
onErrorState: {},
files: {
onChange: {}
}
}
window.codioIDE = window.codioIDE || {}
/**
* IDE files API methods
* @memberof codioIDE
* @type {object}
* @namespace codioIDE.files
*/
window.codioIDE.files = {
/**
* Adds a file
* @memberof codioIDE.files
* @async
* @param {string} path - file path
* @param {string} content - file content
* @return {Promise<void>}
*/
add: async (path, content) => {
if (!app.currentProject) {
throw new Error('project not found')
}
if (app.currentProject.exists(path)) {
throw new Error('file already exists')
}
const paths = path.split('/')
const isPathInvalid = _.some(paths, (path) => {
return !tools.isItemNameValid(path)
})
if (isPathInvalid) {
throw new Error(
'Path must only contain alphanumeric characters, slashes, hyphens and underscores.'
)
}
await createFile(path, content)
},
/**
* Returns binary file content as base64 encoded string
* @memberof codioIDE.files
* @async
* @method getFileBase64
* @param path
* @return {Promise<string>} - base64 encoded string
*/
getFileBase64: async (path) => {
if (!app.currentProject) {
throw new Error('project not found')
}
if (!app.currentProject.exists(path)) {
throw new Error('file not found')
}
const buffer = await app.currentProject.getBinaryFileContent(path)
return tools.arrayBufferToBase64(buffer)
},
/**
* Returns file content
* @memberof codioIDE.files
* @async
* @method getContent
* @param {string} path - file path
*/
getContent: async (path) => {
if (!app.currentProject) {
throw new Error('project not found')
}
if (!app.currentProject.exists(path)) {
throw new Error('file not found')
}
const file = await app.currentProject.unixFile(path)
return file.content
},
/**
* Deletes the files
* @memberof codioIDE.files
* @async
* @method deleteFiles
* @param {string[]} paths - file paths to delete
*/
deleteFiles: async (paths) => {
if (!app.currentProject) {
throw new Error('project not found')
}
return app.currentProject.deleteItems(paths)
},
/**
* Returns file tree structure
* @memberof codioIDE.files
* @method getStructure
*/
getStructure: () => {
if (!app.currentProject) {
throw new Error('project not found')
}
return app.currentProject.children
},
/**
* File change callback
* @memberof codioIDE.files
* @callback FileChangeCallback
* @param {string} path - file path
* @param {string} content - file content
*/
/**
* Register an events listener
* An event emitted when a file has changed
* @param {FileChangeCallback} callback - file change callback
* @throws Will throw an error if the callback is not a function.
* @return {callback} - call it to deregister the event
*/
onChange: (callback) => {
if (!_.isFunction(callback)) {
throw new Error('Callback should be a function')
}
if (!window.codioIDE.files.onChange._handler) {
window.codioIDE.files.onChange._handler = events.subscribe(
'file:change:#path',
(topic, content) => {
_.forEach(callbacks.files.onChange, (callback) =>
callback(topic.path, content)
)
}
)
}
const id = crypto.randomUUID()
callbacks.files.onChange[id] = callback
return () => {
delete callbacks.files.onChange[id]
if (
window.codioIDE.files.onChange._handler &&
_.isEmpty(callbacks.files.onChange)
) {
events.unsubscribe(window.codioIDE.files.onChange._handler)
}
}
}
}
/**
* Check is author assignment
* @memberof codioIDE
* @method isAuthorAssignment
*/
window.codioIDE.isAuthorAssignment = () => {
if (!app.currentProject) {
throw new Error('project not found')
}
return !!app.currentProject.authorAssignment
}
/**
* Error state callback
* @memberof codioIDE
* @callback ErrorStateCallback
* @param {boolean} isError - error state
* @param {string} [error] - error
* @param {number} [format] - output format(1 - text, 2 - markdown, 3 - html)
*/
/**
* Register an events listener
* An event emitted when an error state has changed
* @param {ErrorStateCallback} callback - error state change callback
* @throws Will throw an error if the callback is not a function.
* @return {callback} - call it to deregister the event
*/
window.codioIDE.onErrorState = (callback) => {
if (!_.isFunction(callback)) {
throw new Error('Callback should be a function')
}
if (!window.codioIDE.onErrorState._handler) {
window.codioIDE.onErrorState._handler = events.subscribe(
'ide:onErrorState',
(topic, { isError, output, format }) => {
_.forEach(callbacks.onErrorState, (callback) =>
callback(isError, output, format)
)
}
)
}
const id = crypto.randomUUID()
callbacks.onErrorState[id] = callback
return () => {
delete callbacks.onErrorState[id]
if (
window.codioIDE.onErrorState._handler &&
_.isEmpty(callbacks.onErrorState)
) {
events.unsubscribe(window.codioIDE.onErrorState._handler)
}
}
}
/**
* IDE Menu API methods
* @memberof codioIDE
* @type {object}
* @namespace codioIDE.menu
*/
window.codioIDE.menu = {
/**
* @typedef codioIDE.menu.MenuItem
* @memberof codioIDE.menu
* @interface
* @type {Object}
* @property {string} title - The title of the item. If equals `divider`, then a menu divider will be rendered, and all other options will be ignored.
* @property {string} [id] - A unique ID.
* @property {boolean} [group] - When true, will be rendered as group menu item
* @property {string} [url] - The URL that the user will be redirected to when this item is clicked.
* @property {boolean} [disabled = false] - When true, will disable this item (default: false).
* @property {boolean|codioIDE.menu.MenuOnOff} [onOff = false] When true, will treat this item as an on/off checkmark.
* @property {Object} [params] - Command params as a plain Object, which will be passed to the callback.
* @property {Object} [context] - Plain Object of contexts to determine if this item will be visible (default: all)
* @property {string|string[]} [context.session] - Allowed: `all`, `registered` and/or `anonymous`.
* @property {string|string[]} [context.project] - Allowed: `read`, `write` and/or `admin`.
* @property {function} [callback] - Function that will be called when this item is clicked.
*/
/**
* @typedef codioIDE.menu.MenuItemUpdate
* @extends codioIDE.menu.MenuItem
* @memberof codioIDE.menu
* @interface
* @property {string} [title] - The title of the item. If equals `divider`, then a menu divider will be rendered, and all other options will be ignored.
*/
/**
* @typedef codioIDE.menu.MenuOnOff
* @memberof codioIDE.menu
* @interface
* @type {Object}
* @property {boolean} [defaultValue = false] - The default value.
* @property {boolean} [toggle = false] - If true clicking the item will toggle the checkmark.
*/
/**
* @typedef codioIDE.menu.MenuItemDescriptor
* @memberof codioIDE.menu
* @interface
* @type {Object}
* @property {string} [id] - Item id.
* @property {string} [title] - Item title.
*/
/**
* Item added callback function
*
* @callback itemAddedCallback
* @return {string} - item id
*/
/**
* Add menu item
* @memberof codioIDE.menu
* @method addItem
* @param {codioIDE.menu.MenuItemDescriptor} parentDescriptor - item descriptor
* @param {codioIDE.menu.MenuItem} item - menu item
* @param {itemAddedCallback} [itemAddedCallback] - will be called when on item add with item id
*/
addItem: (parentDescriptor, item, itemAddedCallback) => {
emit.addMenuItem({ parentDescriptor, item, itemAddedCallback })
},
/**
* Remove menu item
* @memberof codioIDE.menu
* @method removeItem
* @param {codioIDE.menu.MenuItemDescriptor} itemDescriptor - item descriptor
*/
removeItem: (itemDescriptor) => {
emit.removeMenuItem(itemDescriptor)
},
/**
* Update menu item
* @memberof codioIDE.menu
* @method updateItem
* @param {codioIDE.menu.MenuItemDescriptor} itemDescriptor - item descriptor
* @param {codioIDE.menu.MenuItemUpdate} item - menu item
*/
updateItem: (itemDescriptor, item) => {
emit.updateMenuItem({ itemDescriptor, item })
}
}
/**
* Returns box url
* @memberof codioIDE
* @method getBoxUrl
* @returns {string}
*/
window.codioIDE.getBoxUrl = () => {
if (!app.currentProject) {
throw new Error('project not found')
}
return `//${app.currentProject.backendSubDomain}.${config.get('boxDomain')}`
}
/**
* IDE Remote commands API methods
* @memberof codioIDE
* @type {object}
* @namespace codioIDE.remoteCommand
*/
window.codioIDE.remoteCommand = {
/**
* Executes remote command and returns a result as a promise
* @memberof codioIDE.remoteCommand
* @async
* @method run
* @param {string|string[]} command - command to execute
* @returns {Promise}
*/
run: async (command) => {
if (!app.currentProject) {
throw new Error('project not found')
}
return remoteCommands.run(command)
}
}