Skip to content

Commit

Permalink
Merge pull request k-pet-group#357 from neilccbrown/canvas-graphics-m…
Browse files Browse the repository at this point in the history
…erged

Canvas graphics merge from main
  • Loading branch information
neilccbrown authored Dec 9, 2024
2 parents eee0051 + 141a4fe commit 0484ef3
Show file tree
Hide file tree
Showing 28 changed files with 424 additions and 520 deletions.
63 changes: 51 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 } 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 @@ -340,12 +349,12 @@ export default Vue.extend({
}
});
/* IFTRUE_isPurePython */
/* 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 +410,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 +851,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 +871,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 +888,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 @@ -1081,6 +1119,7 @@ $divider-grey: darken($background-grey, 15%);
z-index: 20;
border-radius: 8px;
border: 1px solid #8e8e8e;
background-color: #BBB;
}
/*
Expand Down
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
3 changes: 1 addition & 2 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
25 changes: 12 additions & 13 deletions src/components/Commands.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
: addFrameCommand[0].shortcuts[0]
"
:description="addFrameCommand[0].description"
:tooltip="addFrameCommand[0].tooltip"
:index="
addFrameCommand[0].index!==undefined
? addFrameCommand[0].index
Expand All @@ -51,10 +50,10 @@
<text id="userCode"></text>
<span id="keystrokeSpan"></span>
</div>
/* IFTRUE_isPurePython
/* IFTRUE_isPython
<div class="flex-padding"/>
<python-execution-area class="python-exec-area-container" :ref="peaComponentRefId"/>
FITRUE_isPurePython */
FITRUE_isPython */
/* IFTRUE_isMicrobit
<div class="python-exec-area-container">
<div v-if="showProgress" class="progress cmd-progress-container">
Expand Down Expand Up @@ -87,10 +86,10 @@ import Vue from "vue";
import browserDetect from "vue-browser-detect-plugin";
import { mapStores } from "pinia";
import { getFrameSectionIdFromFrameId } from "@/helpers/storeMethods";
/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
import PythonExecutionArea from "@/components/PythonExecutionArea.vue";
import { isMacOSPlatform } from "@/helpers/common";
/* FITRUE_isPurePython */
/* FITRUE_isPython */
/* IFTRUE_isMicrobit */
import APIDiscovery from "@/components/APIDiscovery.vue";
import { flash } from "@/helpers/webUSB";
Expand All @@ -105,9 +104,9 @@ export default Vue.extend({
/* IFTRUE_isMicrobit */
APIDiscovery,
/* FITRUE_isMicrobit */
/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
PythonExecutionArea,
/* FITRUE_isPurePython */
/* FITRUE_isPython */
},
data: function () {
Expand Down Expand Up @@ -148,11 +147,11 @@ export default Vue.extend({
return this.$i18n.t("appMessage.autoSaveGDriveTooltip", {freq: autoSaveFreqMins}) as string;
},
/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
peaComponentRefId(): string {
return getStrypePEAComponentRefId();
},
/* FITRUE_isPurePython */
/* FITRUE_isPython */
/* IFTRUE_isMicrobit */
tabIndex: {
get(): number{
Expand Down Expand Up @@ -527,7 +526,7 @@ export default Vue.extend({
this.frameCommandsReactiveFlag = !this.frameCommandsReactiveFlag;
});
/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
// Listen to the Python execution area size change events (as the other commands max height need to be ammended)
document.addEventListener(CustomEventTypes.pythonExecAreaExpandCollapseChanged, (event) => {
this.isExpandedPEA = (event as CustomEvent).detail;
Expand All @@ -544,7 +543,7 @@ export default Vue.extend({
}
});
});
/* FITRUE_isPurePython */
/* FITRUE_isPython */
},
mounted() {
Expand Down Expand Up @@ -688,9 +687,9 @@ export default Vue.extend({
}
.python-exec-area-container {
/* IFTRUE_isPurePython */
/* IFTRUE_isPython */
margin: 0px 5px 5px 5px;
/* FITRUE_isPurePython */
/* FITRUE_isPython */
/* IFTRUE_isMicrobit */
margin-bottom: 90px;
overflow: hidden; // that is used to keep the margin https://stackoverflow.com/questions/44165725/flexbox-preventing-margins-from-being-respected
Expand Down
6 changes: 5 additions & 1 deletion src/components/Frame.vue
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,11 @@ export default Vue.extend({
document.getElementById(this.frameHeaderId)?.removeEventListener(CustomEventTypes.frameContentEdited, this.onFrameContentEdited);
// Remove the registration of the caret container component at the upmost level for drag and drop
delete this.$root.$refs[getCaretUID(this.caretPosition.below, this.frameId)];
// ONLY if the frame is really removed from the state (because for a very strange reason, when reloading
// a page and overwriting the frames with a state, the initial state's frame are destroyed after registered).
if(this.appStore.frameObjects[this.frameId] == undefined){
delete this.$root.$refs[getCaretUID(this.caretPosition.below, this.frameId)];
}
},
methods: {
Expand Down
Loading

0 comments on commit 0484ef3

Please sign in to comment.