Skip to content

Commit

Permalink
Merge pull request #376 from neilccbrown/oop
Browse files Browse the repository at this point in the history
Bring OOP branch up to date with main branch
  • Loading branch information
neilccbrown authored Jan 20, 2025
2 parents 60dc71c + 2d15b5f commit c4d47fb
Show file tree
Hide file tree
Showing 38 changed files with 936 additions and 777 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,23 @@ Current team:
- Pierre Weill-Tessier (King's College London)

Other contributors:
- Tobias Kohn (<a href="https://github.com/Tobias-Kohn/TigerPython-Parser" target="_blank">TigerPython parser<a>)
- Babis Kyfonidis
- Zak Singh
- Aleksandr Voronstov

Translators:
- Greek: Babis Kyfonidis
- Chinese: Rongkun Liu
- French: Pierre Weill-Tessier
- German: Michael Kölling
- Greek: Babis Kyfonidis
- Spanish: Emma Dodoo and Nolgie Oquendo-Colon

Roadmap
---

Strype is currently under very active development. A rough roadmap of some major planned features (beyond the general bugfixing and polishing):

- Improved editor features (including copy-as-image): Q4 2024
- Add class frames (for object-oriented programming): Q1 2025
- Add a graphics and sound API: Q1 2025
- Add support for third-party library import: Q2 2025
Expand Down
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "strype-editor",
"version": "1.0.0",
"version": "2024.12.13",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --port 8081",
Expand Down
90 changes: 78 additions & 12 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
<ModalDlg :dlgId="importDiffVersionModalDlgId" :useYesNo="true">
<span v-t="'appMessage.editorFileUploadWrongVersion'" />
</ModalDlg>
<ModalDlg :dlgId="resyncGDAtStartupModalDlgId" :useYesNo="true" :okCustomTitle="$t('buttonLabel.yesSign')" :cancelCustomTitle="$t('buttonLabel.noContinueWithout')">
<span style="white-space:pre-wrap">{{ $t('appMessage.resyncToGDAtStartup') }}</span>
</ModalDlg>
<div :id="getSkulptBackendTurtleDivId" class="hidden"></div>
<canvas v-show="appStore.isDraggingFrame" :id="getCompanionDndCanvasId" class="companion-canvas-dnd"/>
</div>
Expand All @@ -92,18 +95,20 @@ import SimpleMsgModalDlg from "@/components/SimpleMsgModalDlg.vue";
import {Splitpanes, Pane} from "splitpanes";
import { useStore } from "@/store/store";
import { AppEvent, AutoSaveFunction, BaseSlot, CaretPosition, FormattedMessage, FormattedMessageArgKeyValuePlaceholders, FrameObject, MessageDefinitions, MessageTypes, ModifierKeyCode, Position, PythonExecRunningState, SaveRequestReason, SlotCursorInfos, SlotsStructure, SlotType, StringSlot } from "@/types/types";
import { getFrameContainerUID, getMenuLeftPaneUID, getEditorMiddleUID, getCommandsRightPaneContainerId, isElementLabelSlotInput, CustomEventTypes, getFrameUID, parseLabelSlotUID, getLabelSlotUID, getFrameLabelSlotsStructureUID, getSelectionCursorsComparisonValue, setDocumentSelection, getSameLevelAncestorIndex, autoSaveFreqMins, getImportDiffVersionModalDlgId, getAppSimpleMsgDlgId, getFrameContextMenuUID, getFrameBodyRef, getJointFramesRef, getCaretContainerRef, getActiveContextMenu, actOnTurtleImport, setPythonExecutionAreaTabsContentMaxHeight, setManuallyResizedEditorHeightFlag, setPythonExecAreaExpandButtonPos, isContextMenuItemSelected, getStrypeCommandComponentRefId, frameContextMenuShortcuts, getCompanionDndCanvasId } from "./helpers/editor";
import { getFrameContainerUID, getMenuLeftPaneUID, getEditorMiddleUID, getCommandsRightPaneContainerId, isElementLabelSlotInput, CustomEventTypes, getFrameUID, parseLabelSlotUID, getLabelSlotUID, getFrameLabelSlotsStructureUID, getSelectionCursorsComparisonValue, setDocumentSelection, getSameLevelAncestorIndex, autoSaveFreqMins, getImportDiffVersionModalDlgId, getAppSimpleMsgDlgId, getFrameContextMenuUID, getFrameBodyRef, getJointFramesRef, getCaretContainerRef, getActiveContextMenu, actOnTurtleImport, setPythonExecutionAreaTabsContentMaxHeight, setManuallyResizedEditorHeightFlag, setPythonExecAreaExpandButtonPos, isContextMenuItemSelected, getStrypeCommandComponentRefId, frameContextMenuShortcuts, getCompanionDndCanvasId, getStrypePEAComponentRefId, getGoogleDriveComponentRefId, addDuplicateActionOnFramesDnD, removeDuplicateActionOnFramesDnD } from "./helpers/editor";
/* IFTRUE_isMicrobit */
import { getAPIItemTextualDescriptions } from "./helpers/microbitAPIDiscovery";
import { DAPWrapper } from "./helpers/partial-flashing";
/* FITRUE_isMicrobit */
import { mapStores } from "pinia";
import { getFrameContainer, getSlotIdFromParentIdAndIndexSplit, getSlotParentIdAndIndexSplit, retrieveParentSlotFromSlotInfos, retrieveSlotFromSlotInfos } from "./helpers/storeMethods";
import { cloneDeep } from "lodash";
import CaretContainer from "./components/CaretContainer.vue";
import CaretContainer from "@/components/CaretContainer.vue";
import { VueContextConstructor } from "vue-context";
import { BACKEND_SKULPT_DIV_ID } from "./autocompletion/ac-skulpt";
import { BACKEND_SKULPT_DIV_ID } from "@/autocompletion/ac-skulpt";
import {copyFramesFromParsedPython, splitLinesToSections, STRYPE_LOCATION} from "@/helpers/pythonToFrames";
import GoogleDrive from "@/components/GoogleDrive.vue";
import { BvModalEvent } from "bootstrap-vue";
let autoSaveTimerId = -1;
let autoSaveState : AutoSaveFunction[] = [];
Expand Down Expand Up @@ -188,6 +193,10 @@ export default Vue.extend({
return getImportDiffVersionModalDlgId();
},
resyncGDAtStartupModalDlgId(): string {
return "resyncGDAtStartupModalDlg";
},
getSkulptBackendTurtleDivId(): string {
return BACKEND_SKULPT_DIV_ID;
},
Expand Down Expand Up @@ -338,14 +347,41 @@ export default Vue.extend({
if(event.key.toLowerCase() == "contextmenu"){
this.appStore.isContextMenuKeyboardShortcutUsed = true;
}
// Handling the notification for doing duplication with drag and drop.
// We don't really care if another key is hit along ctrl/option, we only look that
// we are currently in a drag and drop action, and notify the current caret candidate for drop that
// the action requires frame duplication.
if(this.appStore.isDraggingFrame && (event.ctrlKey || event.altKey)){
addDuplicateActionOnFramesDnD();
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
return;
}
});
/* IFTRUE_isPurePython */
// There are only a few cases when we need to handle key up events
window.addEventListener("keyup", (event) => {
// Handling the notification for not doing duplication anymore with drag and drop.
// We don't really care if another key is hit along ctrl/option, we only look that
// we are currently in a drag and drop action, and notify the current caret candidate for drop that
// the action doesn't require frame duplication.
if(this.appStore.isDraggingFrame && (event.key == "Control" || event.key == "Alt")){
removeDuplicateActionOnFramesDnD();
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
return;
}
});
/* IFTRUE_isPython */
// Listen to the Python execution area size change events (as the editor needs to be resized too)
document.addEventListener(CustomEventTypes.pythonExecAreaExpandCollapseChanged, (event) => {
this.isExpandedPythonExecArea = (event as CustomEvent).detail;
});
/* FITRUE_isPurePython */
/* FITRUE_isPython */
/* IFTRUE_isMicrobit */
// Register an event for WebUSB to detect when the micro:bit has been disconnected. We only do that once, and if WebUSB is available...
Expand Down Expand Up @@ -401,11 +437,27 @@ export default Vue.extend({
this.appStore.setStateFromJSONStr(
{
stateJSONStr: savedState,
callBack: () => {},
showMessage: false,
readCompressed: true,
}
);
).then(() => {
// When a file had been reloaded and it was previously synced with Google Drive, we want to ask the user
// about reloading the project from Google Drive again
if(this.appStore.currentGoogleDriveSaveFileId) {
const execGetGDFileFunction = (event: BvModalEvent, dlgId: string) => {
if((event.trigger == "ok" || event.trigger=="event") && dlgId == this.resyncGDAtStartupModalDlgId){
// Fetch the Google Drive component
const gdVueComponent = ((this.$refs[this.menuUID] as InstanceType<typeof Menu>).$refs[getGoogleDriveComponentRefId()] as InstanceType<typeof GoogleDrive>);
// Initiate a connection to Google Drive via loading mechanisms (for resync at startup)
gdVueComponent.loadFile(true);
this.$root.$off("bv::modal::hide", execGetGDFileFunction);
}
};
this.$root.$on("bv::modal::hide", execGetGDFileFunction);
this.$root.$emit("bv::show::modal", this.resyncGDAtStartupModalDlgId);
}
}, () => {});
}
}
Expand Down Expand Up @@ -826,7 +878,7 @@ export default Vue.extend({
document.getElementById("tabContentContainerDiv")?.dispatchEvent(new CustomEvent(CustomEventTypes.pythonExecAreaSizeChanged));
},
setStateFromPythonFile(completeSource: string) : void {
setStateFromPythonFile(completeSource: string, fileName: string, fileLocation?: FileSystemFileHandle) : void {
const allLines = completeSource.split(/\r?\n/);
// Split can make an extra blank line at the end which we don't want:
if (allLines.length > 0 && allLines[allLines.length - 1] === "") {
Expand All @@ -846,6 +898,9 @@ export default Vue.extend({
useStore().showMessage(msg, 10000);
}
else {
// Clear the current existing code (i.e. frames) of the editor
this.appStore.clearAllFrames();
copyFramesFromParsedPython(s.imports.join("\n"), STRYPE_LOCATION.IMPORTS_SECTION);
if (useStore().copiedSelectionFrameIds.length > 0) {
this.getCaretContainerComponent(this.getFrameComponent(-1) as InstanceType<typeof FrameContainer>).doPaste(true);
Expand All @@ -860,11 +915,21 @@ export default Vue.extend({
this.getCaretContainerComponent(this.getFrameComponent(-3) as InstanceType<typeof FrameContainer>).doPaste(true);
}
}
// Now we can clear other non-frame related elements
this.appStore.clearNoneFrameRelatedState();
/* IFTRUE_isPython */
// We check about turtle being imported as at loading a state we should reflect if turtle was added in that state.
actOnTurtleImport();
// Clear the Python Execution Area as it could have be run before.
((this.$root.$children[0].$refs[getStrypeCommandComponentRefId()] as Vue).$refs[getStrypePEAComponentRefId()] as any).clear();
/* FITRUE_isPython */
// Finally, we can trigger the notifcation a file has been loaded.
(this.$refs[this.menuUID] as InstanceType<typeof Menu>).onFileLoaded(fileName, fileLocation);
}
// Must take ourselves off the clipboard after:
useStore().copiedFrames = {};
useStore().copiedSelectionFrameIds = [];
},
},
});
Expand Down Expand Up @@ -1080,6 +1145,7 @@ $divider-grey: darken($background-grey, 15%);
z-index: 20;
border-radius: 8px;
border: 1px solid #8e8e8e;
background-color: #BBB;
}
/*
Expand Down
33 changes: 33 additions & 0 deletions src/assets/images/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 10 additions & 10 deletions src/autocompletion/acManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,12 @@ function doGetAllExplicitlyImportedItems(frame: FrameObject, module: string, isS
}
/* FITRUE_isMicrobit */

/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
const allSkulptItems : AcResultType[] = skulptPythonAPI[module as keyof typeof skulptPythonAPI] as AcResultType[];
if (allSkulptItems) {
soFar[module] = [...allSkulptItems.filter((x) => !x.acResult.startsWith("_"))];
}
/* FITRUE_isPurePython */
/* FITRUE_isPython */
}
else {
// The module name might be an alias: we need to get the right module to retrieve the data.
Expand Down Expand Up @@ -277,12 +277,12 @@ function doGetAllExplicitlyImportedItems(frame: FrameObject, module: string, isS
}
/* FITRUE_isMicrobit */

/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
const allSkulptItems : AcResultType[] = skulptPythonAPI[realModule as keyof typeof skulptPythonAPI] as AcResultType[];
if (allSkulptItems) {
allItems = [...allSkulptItems.filter((x) => !x.acResult.startsWith("_"))];
}
/* FITRUE_isPurePython */
/* FITRUE_isPython */

// Find the relevant item from allItems (if it exists):
if (isSimpleImport) {
Expand All @@ -304,12 +304,12 @@ export function getAvailableModulesForImport() : AcResultsWithCategory {
/* IFTRUE_isMicrobit */
return {[""]: microbitModuleDescription.modules.map((m) => ({acResult: m, documentation: m in microbitPythonAPI ? (microbitPythonAPI[m as keyof typeof microbitPythonAPI].find((ac) => ac.acResult === "__doc__")?.documentation || "") : "", type: ["module"], version: 0}))};
/* FITRUE_isMicrobit */
/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
return {[""] : Object.keys(pythonBuiltins)
.filter((k) => pythonBuiltins[k]?.type === "module")
.map((k) => ({acResult: k, documentation: pythonBuiltins[k].documentation||"", type: [pythonBuiltins[k].type], version: 0}))
.concat(OUR_PUBLIC_LIBRARY_MODULES.map((m) => ({acResult: m, documentation: "", type: ["module"], version: 0})))};
/* FITRUE_isPurePython */
/* FITRUE_isPython */
}
export function getAvailableItemsForImportFromModule(module: string) : AcResultType[] {
const star : AcResultType = {"acResult": "*", "documentation": "All items from module", "version": 0, "type": []};
Expand All @@ -320,20 +320,20 @@ export function getAvailableItemsForImportFromModule(module: string) : AcResultT
}
/* FITRUE_isMicrobit */

/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
const allSkulptItems: AcResultType[] = skulptPythonAPI[module as keyof typeof skulptPythonAPI] as AcResultType[];
if (allSkulptItems) {
return [...allSkulptItems, star];
}
/* FITRUE_isPurePython */
/* FITRUE_isPython */
return [star];
}

export function getBuiltins() : AcResultType[] {
/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
// Must return a clone as caller may later modify the list:
return [...skulptPythonAPI[""] as AcResultType[]];
/* FITRUE_isPurePython */
/* FITRUE_isPython */
/* IFTRUE_isMicrobit */
// Must return a clone as caller may later modify the list:
return [...microbitPythonAPI[""] as AcResultType[]];
Expand Down
5 changes: 2 additions & 3 deletions src/components/AddFrameCommand.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div :class="{'frame-cmd-container': true, disabled: isPythonExecuting}" @click="onClick" :title=tooltip>
<div :class="{'frame-cmd-container': true, disabled: isPythonExecuting}" @click="onClick">
<button :class="{'frame-cmd-btn': true, 'frame-cmd-btn-large': isLargerShorcutSymbol}" :disabled=isPythonExecuting>{{ symbol }}</button>
<span>{{ description }}</span>
</div>
Expand All @@ -26,7 +26,6 @@ export default Vue.extend({
shortcut: String, //the keyboard shortcut to add the frame
symbol: String, //the displayed shortcut in the UI, it can be a symbolic representation
description: String, //the description of the frame
tooltip:String, //the tooltip showing details of the frame
index: Number, //when more than 1 frame is assigned to a shortcut, the index tells which frame definition should be used
},
Expand Down Expand Up @@ -67,7 +66,6 @@ export default Vue.extend({
color: rgb(180, 180, 180);
}
.frame-cmd-btn {
margin-right: 5px;
cursor: pointer;
Expand All @@ -85,5 +83,6 @@ export default Vue.extend({
.frame-cmd-btn:disabled {
cursor: default;
color: rgb(180, 180, 180);
}
</style>
Loading

0 comments on commit c4d47fb

Please sign in to comment.