Projects live at URLs, have owners, and are edited together live
A project is only ever at /p/<id>/<slug>; / is the index of the projects
you own or edit. Every project has an owner, who can name editors;
anyone with the link can view. Every edit saves itself, one request in
flight at a time, as a patch of the leaves that changed, and a websocket
(channels + daphne) carries presence and each committed write to
everyone else in the project. The first write to a leaf wins, and the
loser is told.
Undo is per person: a step undoes only if the leaves it touched still
hold what it left, so it never takes a collaborator's work with it.
Named snapshots replace saving, and restore as an ordinary write.
An empty symbol now survives the leaf round trip with `:nodes {}`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c17ee138f2
commit
6a53adb5e0
31 changed files with 1918 additions and 158 deletions
|
|
@ -24,7 +24,9 @@ without parsing its leaves: which footage, which analysis, which blocks.
|
|||
"""
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Blob(models.Model):
|
||||
|
|
@ -201,10 +203,19 @@ class Project(models.Model):
|
|||
|
||||
`schema_version` identifies the stored document format. `seq` counts writes
|
||||
to this particular project; it is not a format version. Every write bumps
|
||||
`seq`, and a client that sees `seq > local + 1` refetches once broadcasts exist.
|
||||
`seq`, and a client that sees `seq > local + 1` refetches.
|
||||
|
||||
ANYONE WITH THE LINK CAN VIEW; the owner and the editors can write. Every
|
||||
project has an owner.
|
||||
"""
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
owner = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="projects",
|
||||
)
|
||||
editors = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL, blank=True, related_name="shared_projects",
|
||||
)
|
||||
name = models.CharField(max_length=200, default="untitled")
|
||||
schema_version = models.PositiveIntegerField(default=2)
|
||||
seq = models.PositiveBigIntegerField(default=0)
|
||||
|
|
@ -219,10 +230,19 @@ class Project(models.Model):
|
|||
return f"{self.name} ({self.id})"
|
||||
|
||||
def bump(self):
|
||||
self.seq += 1
|
||||
self.save(update_fields=["seq", "updated"])
|
||||
"""The next seq, taken with an UPDATE so that inside a transaction it is
|
||||
also the write lock: two concurrent saves cannot both get the same one."""
|
||||
Project.objects.filter(id=self.id).update(
|
||||
seq=models.F("seq") + 1, updated=timezone.now()
|
||||
)
|
||||
self.refresh_from_db(fields=["seq", "updated"])
|
||||
return self.seq
|
||||
|
||||
def can_edit(self, user):
|
||||
return user.is_authenticated and (
|
||||
user.id == self.owner_id or self.editors.filter(id=user.id).exists()
|
||||
)
|
||||
|
||||
|
||||
class Clip(models.Model):
|
||||
"""Tier 1: the unit of work, and the thing leaf paths are scoped by.
|
||||
|
|
@ -271,6 +291,9 @@ class Leaf(models.Model):
|
|||
path = models.CharField(max_length=300)
|
||||
value = models.JSONField()
|
||||
version = models.PositiveBigIntegerField(default=1)
|
||||
seq = models.PositiveBigIntegerField(
|
||||
default=0, help_text="the project seq of the write that last changed it",
|
||||
)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -288,7 +311,9 @@ class Leaf(models.Model):
|
|||
|
||||
|
||||
class Revision(models.Model):
|
||||
"""Tier 1: a snapshot of the authored layer, with a user and a summary.
|
||||
"""Tier 1: a snapshot of the authored layer, with a user and a summary — a
|
||||
named snapshot, which is how a person marks a version now that every edit
|
||||
saves itself.
|
||||
|
||||
ON AN EXPLICIT TRIGGER, not on every save. tl snapshots a small annotation
|
||||
layer; arthur's tier 1 will contain cel polygons, so a snapshot per save bloats
|
||||
|
|
@ -302,6 +327,9 @@ class Revision(models.Model):
|
|||
author = models.CharField(max_length=200, blank=True)
|
||||
summary = models.CharField(max_length=500, blank=True)
|
||||
document = models.JSONField(help_text="every leaf of the project, by path")
|
||||
blocks = models.JSONField(
|
||||
default=dict, help_text="each clip's tier-2 block keys, by cid, so a restore can name them",
|
||||
)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue