-
Notifications
You must be signed in to change notification settings - Fork 0
Conversation
I'm having some problems with traversing the top-level events list. Because I default to iterating Jumping directly to The problem then will be remembering where the events in this list originally came from - after all, if I save this object directly, it will be saved to a single JSON file and the original locations of each event will be destroyed and forgotten. That is to say that this solution would work but it would only work once. I should find some way of having the separate events files pretend that they're in a single events list from the perspective of the |
Editing files directly from the browser is not going to work because of sandboxing and shit. I have two options for alternatives:
Agh. I'm going to need to compile to Electron eventually anyway - I guess it's just come up sooner than I thought. There's no point going to the effort of setting up a backend when it's not going to be used again. |
The browser cannot actually access these files. A new system (either a backend or a desktop app) is needed.
There are a lot of changes here and it's all too messy to split into separate commits. Honestly a lot of it is over my head. The important bit is that file reading is performed on a separate process to the renderer, and that that's set up in a secure way. I think I got it right but that bit's not actually working right now so who knows. The importanter bit is that I get something that's not just error messages in the Electron pane: in this case, it's the skeleton of the Editor component. So, effectively, strip away all the Electron stuff and this commit just introduces a dirty hotfix to the identifier function.
The contextBridge strips everything but data from objects (including fs.Dirent objects, including the isDirectory method). This information needs to be communicated with a primitive value.
It was introducing another layer of complexity that I simply do not need or want right now.
It's because the new events were being bound after the asynchronous binders had been called. Moving the binding into a callback and passing it to the binder was the fix.
Build events editor with Electron
I'm now at the point where the data is loaded correctly. The next step is being able to make edits to the data, and then I can implement saving. Turns out making edits to the data is pretty hard. The way I'm attempting to implement it currently has each component keeping track of an object (e.g. a text field component keeps track of just a string like the event summary; an event editor keeps track of an entire event object). Components in the tree pass the changes they want to make up the tree, eventually reaching the component that keeps track of the event object, which passes a final request to the root object to update the event in its internal store. Theoretically, this solves the problem of each component needing to know what data it cares about, because each component only needs to handle a single chunk of data and pass any changes to its parent - and data-only components that don't contain any data fields themselves but contain components that do only need to transmit the events and props correctly. That's as opposed to a solution like Vuex, where each component would need to know the exact location of their concerns so they know where to update the data in the store. The more I'm thinking about it, the more convinced I am that this is the right solution. I think the complexity is actually in my events system, with stuff like One problem with this approach is that it relies on a consistent parent-child component relationship existing for everything, however, that's not always the case - events in different files have arbitrary nesting corresponding to their file structure. In these cases, the EventEditor component picks one of the events to edit and records the Identifier pointing to it, which means that I can't totally abolish the Identifier system, because that incredibly crucial part of editing events requires it. However, that situation isn't exactly necessary. Events don't need to be nested at all - in fact they could be a totally flat structure. Instead of an event looking like I have a few choices for wrangling the root events object:
I pick 3.i. In the future when I've got lots of events, I should come back to 3bc33ac and see what the old event selector UI would've looked like. |
Noting that both of the sample events, despite having differences between them, appear as identical. I have done a little investigation and found that:
|
I noticed that, when reading the contents of files transmitted over the IPC from the file read/write channel in the Electron preload script, even though I had two different events defined, they were both identical. I was able to work out that the file contents were likely being contaminated on account of sharing a data channel. By including the file path in the data channel name as a unique indentifier, I can guarantee that the read event for a file should go to the right place. Curiously, I was already using one-time response events to communicate with the Electron backend, so I would've expected this bug to result in events being in the wrong order rather than being duplicated.
Follow Vue's style guide
This commit gets as far as creating the EditConditional component and seeing if its core conceit - that is, attaching the wanted component as a dynamic component - works. It does. But there's still a lot of work to be done, including creating dynamic components for each term of the conditional and propagating events back upwards which at the moment is entirely unimplemented.
I've got a pretty serious issue. There are several nodes in my event tree that can accept a couple of different types. There's also Through whatever magic, I can easily determine for any node (i.e. parent component) which node the data indicates it contains (i.e. which child component to render). But actually getting the data from the parent into the child is more difficult. My approach right now is to use a dynamic component and have a method in the parent that determines what kind of component it is. For example, the <!-- EditSingleMessage.vue -->
<template>
<component
:is="makeComponent().is"
v-bind="makeComponent().attrs"
/>
</template>
<script>
import FieldText from "./FieldText.vue"
import EditConditional from "./EditConditional.vue"
makeComponent() {
if (isConditional(this.text)) {
return {
is: EditConditional,
attrs: {
// whatever attributes EditConditional wants
}
}
}
return {
is: FieldText,
attrs: {
// whatever attributes FieldText wants
}
}
}
</script> Problem is, 'whatever attributes' is pretty nebulous, and there's really no way to type it. It works, because the Vue template is a black hole that sucks up and destroys all type information. But there's no way of knowing before runtime if the objects I'm creating will work, and even if they do, there's nothing that will tell me when I break them. I'm really looking forward to making the component tree for the rest of the game, because it's going to be so far divorced from all of these problems. Another thing is that this functionality is going to come up a lot. Any node that can accept multiple kinds of subnode is going to need it, and that's about half of them, plus more later if I decide I want it. So it's really important that I can get type validation for this. What should I do? Ideas, spitting them out as I think of them.
|
fcaa60a
to
1ee5e74
Compare
Dynamic components must now declare a separate type for their required properties (which will likely match their props), and components that consume them must have a method that outputs a range of components matching these interfaces. Note that currently only static downwards properties are implemented; I haven't implemented update events and don't know if they'll even work.
So I made a complete mess of myself making a type system. There was duplication everywhere and most of the types - being effectively-untyped Vue components - were actually useless, so although I was getting presumably-accurate typechecking errors, I've no idea how useful they actually were. Instead, I've moved as much type information as possible into the base DynamicComponent type and I'm making heavy use of generics. I'm not done yet, but I think this new system is much better. It's certainly a lot easier on the eyes. |
Alright, the better type system is here, and now it's finally time to work on the update events. They're unimplemented currently, and the current status is that text fields do change when you edit them, but they reset when changing event. My test node for update events is going to be the line "We're not currently testing." The tree for this node is I recall that someone advised me to use computed properties with getters and setters for this sort of thing, and in fact that's what I've already done for Once update events are done, the best way to immediately test it would be to implement the save system, and then check the diffs. Until then I can't be sure what changes have bubbled to the root and which are stuck in the prop. Also, another thing I just remembered: this event chain is for updates, but I also need to handle create, destroy and re-order events. |
Regarding backing up events files. I switched from the 'save button saves stuff' to an 'always be autosaving' approach, because I realised that's what the Proxy is set up to handle and I don't actually need a Proxy system at all if I'm not doing that. However, I also had a concern about not making too many backup files, because that could get overwhelming pretty fast. I decided it would be a good idea to round the backups to a timestamp, in this case to the nearest half hour. The files are hashed with that time and the current commit hash, so I'm making one backup file every half hour OR one backup file per commit, whichever comes first. However, the way I've got it implemented right now means that while I only create one backup file per half hour, that file contains only the most recent change I made. So it's pretty useless for undoing a half-hour's worth of changes. Instead, I could create a backup with the very first change, and then have any further backups fail until that half hour has elapsed. But that would make the first of such backups pretty much useless, as it'd be effectively identical to the one at the previous commit, and I'd have to wait half an hour before getting a more useful one. Combining this approach with more frequent backups e.g. every 10 minutes should be fine, though. And it won't make extraneous backups if I go ten minutes without making any changes, which is fairly reasonable. |
This allows me to use the 'value' keyword to refer to a floating point value, which is more accurate for both types of variable.
After a huge rethink and conceptual rework of what I want this editor to be (detailed in my Notion planning document), I'm going to not only extract this editor out of the base pull request (#35) but actually extract it to a separate repository. Then I'm going to totally change everything about it based on the new requirements, and rebrand it as an entirely separate thing, which the Maitreya project will consume. It's going to be a tough job extracting the changes out of this PR and into a separate repository. Any changes that affect the main Maitreya project I guess I'll try to append back onto #35. |
I have extracted the event editor out to its own repository: https://github.com/croque-scp/convomap Other than the event editor, this PR doesn't do anything useful beyond a change to the eslint config (#46) which I'll cherry-pick at some point. Commits have been copied to the new repository. Closing this PR. |
a save buttonan autosave that backs up the overwritten file<summary>