generated from wandyezj/website-react
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Action Outline
- Loading branch information
Showing
9 changed files
with
766 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Actions | ||
|
||
Associate a trigger with an action. | ||
|
||
- Trigger | ||
- office.js event | ||
- shortcut | ||
- Action | ||
- executable JavaScript | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,211 @@ | ||
// cspell:ignore SHOWTASKPANE HIDETASKPANE addin | ||
import { ActionType, TriggerAction, TriggerType } from "./core/actions/TriggerAction"; | ||
import { registerTriggerActionsInitial, setTriggerActions } from "./core/actions/triggerActionHandlers"; | ||
import { getSourceEmbed } from "./core/embed/getSourceEmbed"; | ||
import { setHost } from "./core/globals"; | ||
|
||
console.log("actions load"); | ||
|
||
Office.actions.associate("SHOWTASKPANE", function () { | ||
console.log("shortcut - Show"); | ||
return Office.addin | ||
.showAsTaskpane() | ||
.then(function () { | ||
return; | ||
}) | ||
.catch(function (error) { | ||
return error.code; | ||
}); | ||
const triggerActionLoadLogTest: TriggerAction = { | ||
id: "1", | ||
name: "LoadLogTest", | ||
trigger: { | ||
type: TriggerType.Load, | ||
}, | ||
action: { | ||
type: ActionType.LogId, | ||
}, | ||
}; | ||
|
||
const triggerActionExcelNamedRangeTest: TriggerAction = { | ||
id: "2", | ||
name: "NamedRangeTest", | ||
trigger: { | ||
type: TriggerType.ExcelNamedRangeEdit, | ||
parameters: { | ||
namedRangeName: "test", | ||
}, | ||
}, | ||
action: { | ||
type: ActionType.LogId, | ||
}, | ||
}; | ||
|
||
const triggerActionExcelWorksheetNameRangeAddressTest: TriggerAction = { | ||
id: "3", | ||
name: "Sheet1A1Edit", | ||
trigger: { | ||
type: TriggerType.ExcelWorksheetNameRangeAddressEdit, | ||
parameters: { | ||
worksheetName: "Sheet1", | ||
rangeAddress: "A1", | ||
}, | ||
}, | ||
action: { | ||
type: ActionType.EvalCallback, | ||
parameters: { | ||
callback: ` | ||
async function main(triggerAction) { | ||
console.log(triggerAction.id) | ||
await Excel.run(async (context) => { | ||
const sheet = context.workbook.worksheets.getItemOrNullObject("Sheet1"); | ||
const range = sheet.getRange("B1"); | ||
range.load("values"); | ||
await context.sync(); | ||
if (range.isNullObject) { | ||
return; | ||
} | ||
const value = range.values[0][0]; | ||
let newValue = 0; | ||
if (typeof value === "number") { | ||
newValue = value + 1; | ||
} | ||
range.values = [[newValue]]; | ||
await context.sync(); | ||
console.log("Incremented Value"); | ||
}); | ||
} | ||
`, | ||
}, | ||
|
||
// type: ActionType.Callback, | ||
// parameters: { | ||
// callback: async (triggerAction: Readonly<TriggerAction>) => { | ||
// // Log that this was called. | ||
// logTriggerId(triggerAction); | ||
|
||
// await Excel.run(async (context) => { | ||
// const sheet = context.workbook.worksheets.getItemOrNullObject("Sheet1"); | ||
// const range = sheet.getRange("B1"); | ||
// range.load("values"); | ||
// await context.sync(); | ||
// if (range.isNullObject) { | ||
// return; | ||
// } | ||
|
||
// const value = range.values[0][0]; | ||
// let newValue = 0; | ||
// if (typeof value === "number") { | ||
// newValue = value + 1; | ||
// } | ||
|
||
// range.values = [[newValue]]; | ||
// await context.sync(); | ||
// console.log("Incremented Value"); | ||
// }); | ||
// }, | ||
// }, | ||
}, | ||
}; | ||
|
||
/** | ||
* action registry | ||
*/ | ||
export const triggerActionsDefault: TriggerAction[] = [ | ||
triggerActionLoadLogTest, | ||
triggerActionExcelNamedRangeTest, | ||
triggerActionExcelWorksheetNameRangeAddressTest, | ||
]; | ||
|
||
// Store triggers in custom XML | ||
|
||
type TriggerActionMetadata = Pick<TriggerAction, "id">; | ||
|
||
function pruneTriggerActionToTriggerActionMetadata(triggerAction: TriggerAction): TriggerActionMetadata { | ||
const { id } = triggerAction; | ||
return { | ||
id, | ||
}; | ||
} | ||
|
||
function getTriggerActionJson(triggerAction: TriggerAction): string { | ||
// TODO: prune if needed before storage | ||
const s = JSON.stringify(triggerAction, undefined, 4); | ||
return s; | ||
} | ||
|
||
function getTriggerActionFromJson(json: string): TriggerAction { | ||
// TODO: validate the thing read | ||
const triggerAction = JSON.parse(json) as TriggerAction; | ||
return triggerAction; | ||
} | ||
|
||
const embedNamespace = "build-add-in-trigger-action"; | ||
const embedTag = "TriggerAction"; | ||
|
||
const storage = getSourceEmbed({ | ||
embedNamespace, | ||
embedTag, | ||
pruneItemToItemMetadata: pruneTriggerActionToTriggerActionMetadata, | ||
getItemJson: getTriggerActionJson, | ||
getItemFromJson: getTriggerActionFromJson, | ||
}); | ||
|
||
Office.actions.associate("HIDETASKPANE", async function () { | ||
console.log("shortcut - Hide"); | ||
try { | ||
await Office.addin.hide(); | ||
} catch (e) { | ||
console.log(e); | ||
function removeUndefined<T>(array: (T | undefined)[]): T[] { | ||
return array.filter((x) => x !== undefined) as T[]; | ||
} | ||
|
||
import { embedDeleteById, embedReadAllId } from "./core/embed/embed"; | ||
async function removeAllEmbedTriggers() { | ||
const ids = await embedReadAllId({ embedNamespace }); | ||
await Promise.all(ids.map((id) => embedDeleteById({ id, embedNamespace }))); | ||
} | ||
|
||
class StopWatch { | ||
constructor(private name: string) {} | ||
lastStart = 0; | ||
start() { | ||
this.lastStart = Date.now(); | ||
} | ||
}); | ||
|
||
Office.actions.associate("SETCOLOR", function () { | ||
console.log("shortcut - Set Color"); | ||
Excel.run(async (context) => { | ||
const range = context.workbook.getSelectedRange(); | ||
range.format.fill.load("color"); | ||
|
||
await context.sync(); | ||
const colors = ["#FFFFFF", "#C7CC7A", "#7560BA", "#9DD9D2", "#FFE1A8", "#E26D5C"]; | ||
const colorIndex = (colors.indexOf(range.format.fill.color) + 1) % colors.length; | ||
range.format.fill.color = colors[colorIndex]; | ||
await context.sync(); | ||
}); | ||
}); | ||
read(header: string = "") { | ||
const current = Date.now(); | ||
const delta = current - this.lastStart; | ||
const milliseconds = delta; | ||
console.log(`[${this.name}] ${header} = ${milliseconds} ms`); | ||
} | ||
} | ||
|
||
function getWatch(name: string) { | ||
return new StopWatch(name); | ||
} | ||
|
||
Office.onReady(() => { | ||
Office.onReady(async ({ host }) => { | ||
console.log("ready"); | ||
setHost(host); | ||
|
||
const watch = getWatch("boot"); | ||
watch.start(); | ||
|
||
removeAllEmbedTriggers(); | ||
|
||
// read triggers | ||
let metadata = await storage.getAllItemMetadata(); | ||
watch.read("read trigger metadata"); | ||
|
||
if (metadata.length === 0) { | ||
console.log("save default triggers - start"); | ||
// embed some triggers | ||
watch.start(); | ||
await Promise.all(triggerActionsDefault.map((item) => storage.saveItem(item))); | ||
metadata = await storage.getAllItemMetadata(); | ||
watch.read("save triggers"); | ||
console.log("save default triggers - complete"); | ||
} | ||
|
||
watch.start(); | ||
const triggerActions = removeUndefined( | ||
await Promise.all( | ||
metadata.map(({ id }) => { | ||
return storage.getItemById(id); | ||
}) | ||
) | ||
); | ||
watch.read("read triggers"); | ||
|
||
watch.start(); | ||
setTriggerActions(triggerActions); | ||
watch.read("setTriggerActions"); | ||
registerTriggerActionsInitial(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
export enum TriggerType { | ||
/** | ||
* When the shared runtime is loaded | ||
*/ | ||
Load = "Load", | ||
/** | ||
* When an excel named range is edited | ||
*/ | ||
ExcelNamedRangeEdit = "ExcelNamedRangeEdit", | ||
|
||
/** | ||
* When an excel worksheet (by name) range (by address) is edited | ||
*/ | ||
ExcelWorksheetNameRangeAddressEdit = "ExcelWorksheetNameRangeAddressEdit", | ||
} | ||
|
||
export interface TriggerLoad { | ||
type: TriggerType.Load; | ||
} | ||
|
||
export interface TriggerExcelNamedRangeEdit { | ||
type: TriggerType.ExcelNamedRangeEdit; | ||
parameters: { | ||
/** | ||
* global named range | ||
*/ | ||
namedRangeName: string; | ||
}; | ||
} | ||
|
||
export interface TriggerExcelWorksheetNameRangeAddressEdit { | ||
type: TriggerType.ExcelWorksheetNameRangeAddressEdit; | ||
parameters: { | ||
/** | ||
* name of the worksheet. | ||
*/ | ||
worksheetName: string; | ||
|
||
/** | ||
* address of the range. | ||
*/ | ||
rangeAddress: string; | ||
}; | ||
} | ||
|
||
export type Trigger = TriggerLoad | TriggerExcelNamedRangeEdit | TriggerExcelWorksheetNameRangeAddressEdit; | ||
|
||
export enum ActionType { | ||
/** | ||
* Log the id of the trigger; | ||
*/ | ||
LogId = "LogId", | ||
|
||
/** | ||
* Run a callback. | ||
* note: this is an incredibly powerful action and shouldn't be serialized, but ok to use internally. | ||
*/ | ||
Callback = "Callback", | ||
|
||
/** | ||
* eval the code and execute main function | ||
* !!!WARNING!!! incredibly not secure only for proof of concept | ||
*/ | ||
EvalCallback = "EvalCallback", | ||
} | ||
|
||
export interface ActionLogId { | ||
type: ActionType.LogId; | ||
} | ||
|
||
export interface ActionCallback { | ||
type: ActionType.Callback; | ||
parameters: { | ||
/** | ||
* Hardcoded callback to call. | ||
* @param triggerAction the trigger action that triggered the callback. | ||
* @returns | ||
*/ | ||
callback: (triggerAction: Readonly<TriggerAction>) => Promise<void>; | ||
}; | ||
} | ||
|
||
export interface ActionEvalCallback { | ||
type: ActionType.EvalCallback; | ||
parameters: { | ||
/** | ||
* Hardcoded string callback to call. eval and call main function with TriggerAction/ | ||
* @returns | ||
*/ | ||
callback: string; | ||
}; | ||
} | ||
|
||
export type Action = ActionLogId | ActionCallback | ActionEvalCallback; | ||
|
||
export interface TriggerAction { | ||
/** | ||
* Unique id to easily identify the Trigger Action | ||
*/ | ||
id: string; | ||
|
||
/** | ||
* Easily readable identifier. | ||
*/ | ||
name: string; | ||
|
||
trigger: Trigger; | ||
action: Action; | ||
} |
Oops, something went wrong.