10}
11
12function Tooltip({ children, content }: TooltipProps) {
13 const [isVisible, setIsVisible] = useState(false);
14 const tooltipRef = useRef<HTMLDivElement>(null);
49}
50
51function formatBytes(bytes: number, decimals = 2) {
52 if (bytes === 0) return "0 Bytes";
53 const k = 1024;
58}
59
60function copyToClipboard(text: string) {
61 navigator.clipboard.writeText(text).then(() => {
62 console.log("Text copied to clipboard");
66}
67
68function ActionMenu({ blob, onDownload, onRename, onDelete, onMoveToPublic, onMoveOutOfPublic }) {
69 const [isOpen, setIsOpen] = useState(false);
70 const menuRef = useRef(null);
73
74 useEffect(() => {
75 function handleClickOutside(event) {
76 if (menuRef.current && !menuRef.current.contains(event.target)) {
77 event.stopPropagation();
155}
156
157function BlobItem({ blob, onSelect, isSelected, onDownload, onRename, onDelete, onMoveToPublic, onMoveOutOfPublic }) {
158 const [isLoading, setIsLoading] = useState(false);
159 const decodedKey = decodeURIComponent(blob.key);
216}
217
218function App({ initialEmail, initialProfile, sourceURL }) {
219 const encodeKey = (key: string) => encodeURIComponent(key);
220 const decodeKey = (key: string) => decodeURIComponent(key);
139## The Live Dev Experience
140
141One of the most important use cases of `vt` is "watch" functionality. There's a lot of reasons for this. One of the big ones is that, as you (and maybe your favorite LLM) edit your code locally, it is really handy to remove the friction of syncing with Val Town.
142
143From the beginning, my plan for this was to implement "git" behavior first (pushing and pulling), and then just doing file system watching to add live syncing using [Deno.watchFs](https://docs.deno.com/api/deno/~/Deno.watchFs) to handle file system events.
159## Some Nitty Gritty
160
161Internally, `vt` is broken up into two main parts: the `vt lib`, where the actual logic for `push`, `pull`, and other "git" operations is defined, and the `vt cmd` lib, where the CLI logic is laid out. Eventually, we want to make `vt` a esm library, where we expose the functionality of the `vt lib` component.
162
163For both cases, we're using [Deno's native testing framework](https://docs.deno.com/runtime/fundamentals/testing/). For `vt lib`, it's pretty straightforward, where we run tests in temp directories doing things like pulls or pushes, and then use the `sdk` to verify changes.
165For the command library, the CLI framework we're using for `vt` (cliffy) has a handy [Snapshot test module](https://cliffy.io/docs@v1.0.0-rc.7/testing/snapshot) that `vt` doesn't use. We decided against using snapshot tests mostly because we lose a lot of fine grain control. Instead, we took inspiration from cliffy in running the CLI tests as subprocesses, and make a lot of use of Deno's `assertStringIncludes` on `stdout`. One ongoing issue we've had with testing is that we've encountered a lot of "resource leaks." It's really cool that `Deno` tests can detect unawaited promises or unawaited/cancelled request bodies, but it doesn't do a good job of helping us locate where the issues are.
166
167`vt` stores configuration in `<your system configuration directory>/vt/config.yaml`, and loads it using `zod`. There's some interesting mechanics where we have multiple layers of configuration, like local and global, and prioritize configuration options in order. Once again, [Deno's standard library](https://jsr.io/@std/collections/doc/deep-merge) has been really handy in building out these components, like the `deepMerge` function.
168
169To figure out where your system configuration directory is, usually I'd use [npm's xdg-portable](https://www.npmjs.com/package/xdg-portable), which gets us your os-aware configuration directory, but it turns out that using this via `npm:xdg-portable` [doesn't work with Deno](https://github.com/rivy/js.xdg-portable/issues/3), and we can't use the officially recommended http import version since `jsr`, the registry we publish `vt` to, doesn't support http imports. I looked into this, and it seemed like an issue with their build process not including their Deno code. The solution I decided on? Fork [xdg-portable](https://github.com/404Wolf/xdg-portable-deno) to be Deno native! In the process, I removed a ton of bloat.
206But then we realized that, without rename detection, if you move a val with configuration -- like cron config, or custom endpoint HTTP vals, then doing the deletion/creation would cause you to lose all your config! And so, we added rename detection to `vt`.
207
208The rename detection algorithm is a bit complicated -- it works by looking at all files that got deleted and that got created, and then considering a file as renamed if a created file is sufficiently similar to a file that got deleted. When iterating over created files to see if a deleted file is similar enough to one of them, we use some heuristics to filter out files that could not possibly be similar enough, like the file length. Then we compute the Levenshtein distance between a given deleted file and created file, and consider a given created file "renamed" if it is above some theshold similar to a deleted file, and if it is similar enough to multiple, then the one it is most similar to [as it turns out, Deno's standard library has a super efficient edit distance function](https://jsr.io/@std/text). Git does fancy [directory rename detection](https://git-scm.com/docs/directory-rename-detection/2.22.0), which is something that, at least for now, `vt` does not do.
209
210Because rename detection is relatively expensive, it is internally implemented as an optional operation that doesn't always get used for every `vt` operation. For example, there isn't a lot of reason to do rename detection for `vt pull` -- it would really just be for reporting.