Skip to content

Commit

Permalink
Merge pull request #3 from zodman/chat_demo
Browse files Browse the repository at this point in the history
Chat demo ...
  • Loading branch information
zodman authored Nov 29, 2020
2 parents 264d0c5 + 1f53895 commit 07eed9e
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,4 @@ dmypy.json
node_modules
/static
/core/static/dist
.env/
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* zodman
* jonathan-s
2 changes: 2 additions & 0 deletions app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import core.views.example
import core.views.book_search
import core.views.chat


urlpatterns = [
Expand All @@ -13,4 +14,5 @@
path('book-search/', core.views.book_search.book_search,
name='book_search'),
path('example/', core.views.example.example, name='example'),
path('chat/', core.views.chat.chat, name='chat'),
] + staticfiles_urlpatterns()
52 changes: 52 additions & 0 deletions core/javascript/controllers/chat_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Rails from '@rails/ujs'
import { debounce } from 'lodash-es'
import ApplicationController from './application_controller'

let lastMessageId
const reload = controller => {
controller.stimulate('ChatReflex#reload')
}
const debouncedReload = debounce(reload, 100)

export default class extends ApplicationController {
static get targets () {
return ['list', 'input']
}

connect () {
super.connect()
this.scroll(100)
}

post (event) {
console.log("post");
Rails.stopEverything(event)
lastMessageId = Math.random()
this.stimulate(
'ChatReflex#post',
this.element.dataset.color,
this.inputTarget.value,
lastMessageId
)
}

afterPost () {
this.inputTarget.value = ''
this.inputTarget.focus()
this.scroll(1)
}

scroll (delay = 10) {
const lists = document.querySelectorAll('[data-target="chat.list"]')
setTimeout(() => {
lists.forEach(e => (e.scrollTop = e.scrollHeight))
}, delay)
}

reload (event) {
const { messageId } = event.detail
if (messageId === lastMessageId) return
debouncedReload(this)
}
}

14 changes: 11 additions & 3 deletions core/javascript/example.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { Application } from 'stimulus'
import StimulusReflex from 'stimulus_reflex'
import WebsocketConsumer from 'sockpuppet-js'

import CableReady from 'cable_ready'
import debounced from 'debounced'
import BookSearchController from './controllers/book_search_controller'
import ExampleController from './controllers/example_controller'
import ChatController from './controllers/chat_controller'

debounced.initialize()
//import TurboLinks from 'turbolinks'
import TurboLinks from 'turbolinks'

//TurboLinks.start()
TurboLinks.start()

const application = Application.start()
const ssl = location.protocol !== 'https:' ? '' : 's';
const consumer = new WebsocketConsumer(`ws${ssl}://${location.hostname}:${location.port}/ws/sockpuppet-sync`)

consumer.subscriptions.create('ChatChannel', {
received (data) {
if (data.cableReady) CableReady.perform(data.operations)
}
})

application.register("example", ExampleController)
application.register("book-search", BookSearchController)
application.register("chat", ChatController)
StimulusReflex.initialize(application, { consumer, debug: true })
8 changes: 4 additions & 4 deletions core/reflexes/book_search_reflex.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@


class BookSearchReflex(Reflex):
def perform(self, query=""):
resp = requests.get('http://openlibrary.org/search.json', params={'q':query})
def perform(self, query=''):
resp = requests.get('http://openlibrary.org/search.json', params={'q': query})
resp.raise_for_status()
books = resp.json()
self.books = books.get("docs", [])
self.count = books["num_found"]
self.books = books.get('docs', [])
self.count = books['num_found']
21 changes: 21 additions & 0 deletions core/reflexes/chat_reflex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from sockpuppet.reflex import Reflex
from sockpuppet.channel import Channel
from django.core.cache import cache
from django.utils import timezone


class ChatReflex(Reflex):
def post(self, color, message, message_id):
chats = cache.get("chats", [])
chats.append({
'message': message,
'message_id': message_id,
'created_at': timezone.now()
})
cache.set("chats", chats)
channel = Channel("chat")
channel.dispatch_event({
'name': 'chats:added',
'detail': {'messagre_id': message_id}
})
channel.broadcast()
21 changes: 21 additions & 0 deletions core/templates/_chat_demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

<article id="red"
data-controller="chat"
data-action="chats:added@document->chat#reload cable-ready:after-morph@document->chat#scroll">
<span data-target="chat.list">
{% for chat in chats %}
<aside class="message" style="min-height:auto; margin-top: 15px;">
<p>
{{chat.message}} <sup>{{chat.created_at|timesince}} ago </sup>
</p>
</aside>
{% endfor %}
</span>
<form>
<textarea data-target="chat.input" placeholder="Type your message.."></textarea>
<button data-action="click->chat#post" type="submit"> Send </button>
<div>
<small>Message storage on <a href="https://docs.djangoproject.com/en/3.1/topics/cache/#local-memory-caching">LocMemCache</a></small>
</div>
</form>
</article>
16 changes: 9 additions & 7 deletions core/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
src="https://i.imgur.com/FX0KFM7m.jpg"
style="filter: grayscale(0.9) contrast(1.2); height: 4rem;" alt="" />
<h1 class="leading-none m-0 pl-1 self-center">
<a href="{% url 'index' %}">Sockpuppet</a>
<a href="{% url 'index' %}">Sockpuppet</a> <small>Expo</small>
</h1>
</header>
<hr>
Expand All @@ -34,21 +34,23 @@ <h1 class="leading-none m-0 pl-1 self-center">
<ul>
<li><a href="{% url 'example' %}">Example</a></li>
<li><a href="{% url 'book_search' %}">BookSearch</a></li>
<li><a href="{% url 'chat' %}">Chat</a></li>
</ul>
</nav>
</aside>
<main class="flex-grow">
<h2> {% block subtitle %}<h1>Django Sockpuppet</h1>{% endblock subtitle %}</h2>
<h2> {% block subtitle %}Django Sockpuppet{% endblock subtitle %}</h2>
{% block main %}
{% endblock %}
</main>
</flex-container>
<footer>
<p>
by <a href="https://twitter.com/zodman">@zodman</a>
</p>
<a href="http://www.djangoproject.com/"><img src="https://www.djangoproject.com/m/img/badges/djangosite80x15.gif"
border="0" alt="A Django site." title="A Django site." /></a>
<a href="https://github.com/zodman/django-sockpuppet-expo">
<img src="https://img.shields.io/static/v1?label=github&message=django-sockpuppet-expo&logo=github">
</a>
<a href="https://github.com/zodman/django-socketpuppet-expo">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/zodman/django-socketpuppet-expo?style=social">
</a>
</footer>
</body>

Expand Down
7 changes: 7 additions & 0 deletions core/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{% extends "base.html" %}

{% block main %}
<p>
It is an exciting new way to build reactive, modern real-time apps.
It eliminates the hassle of maintaining state on the client.
It's a new way of thinking... and it works with technologies that Django developers already use, like server rendered HTML, Russian Doll caching, Stimulus and Turbolinks.
<br>
The demos on this site are an attempt to teach others how to build reactive applications with django-sockpuppet.
</p>
{% endblock main %}
10 changes: 2 additions & 8 deletions core/views/book_search.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
from django.views.generic.base import TemplateView
from .mixins import MixinBase
from .mixins import BookSearchMixin


class BookSearch(MixinBase, TemplateView):
class BookSearch(BookSearchMixin, TemplateView):
demo_template = "_book_search_demo.html"
subtitle = 'Search Book'
files = (
('core/reflexes/book_search_reflex.py', 'python', 'python3'),
('core/views/book_search.py', 'python', 'python3'),
('core/javascript/controllers/book_search_controller.js', 'javascript', 'javascript'),
('core/templates/_book_search_demo.html', 'html', 'htmldjango'),
)

book_search = BookSearch.as_view()
16 changes: 16 additions & 0 deletions core/views/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.views.generic.base import TemplateView
from django.core.cache import cache
from .mixins import ChatMixin

class ChatView(ChatMixin, TemplateView):
demo_template = '_chat_demo.html'
subtitle = 'Chat'

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['chats'] = cache.get("chats", [])
return context

chat = ChatView.as_view()


10 changes: 2 additions & 8 deletions core/views/example.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
from django.views.generic.base import TemplateView
from .mixins import MixinBase
from .mixins import ExampleMixin

class ExampleView(MixinBase, TemplateView):
class ExampleView(ExampleMixin, TemplateView):
demo_template = '_example_demo.html'
subtitle = 'Increment'
files = (
('core/views/example.py', 'python', 'python3'),
('core/reflexes/example_reflex.py', 'python', 'python3'),
('core/javascript/controllers/example_controller.js', 'javascript', 'javascript'),
('core/templates/_example_demo.html', 'html', 'htmldjango'),
)

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
Expand Down
51 changes: 40 additions & 11 deletions core/views/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,56 @@


class MixinBase:
template_name="demo.html"
template_name = "demo.html"
demo_template = None
subtitle = None

def get_files(self):
files = defaultdict(list)
path_ = lambda x: open(os.path.join(BASE_PATH, x)).read()
for filename, filetype, pygment_type in self.files:
for filename, filetype, pygment_type in self.files:
filesrc = path_(filename)
files[filetype].append({
'src': filesrc,
'pygment_type': pygment_type,
'filename': filename,
'loc': len(filesrc.split('\n'))
})
files[filetype].append(
{
"src": filesrc,
"pygment_type": pygment_type,
"filename": filename,
"loc": len(filesrc.split("\n")),
}
)
return dict(files)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['files'] = self.get_files()
context['demo_template'] = self.demo_template
context['subtitle'] = self.subtitle
context["files"] = self.get_files()
context["demo_template"] = self.demo_template
context["subtitle"] = self.subtitle
return context


class BookSearchMixin(MixinBase):
files = (
("core/reflexes/book_search_reflex.py", "python", "python3"),
("core/views/book_search.py", "python", "python3"),
("core/javascript/controllers/book_search_controller.js", "javascript",
"javascript",),
("core/templates/_book_search_demo.html", "html", "htmldjango"),
)


class ExampleMixin(MixinBase):
files = (
('core/views/example.py', 'python', 'python3'),
('core/reflexes/example_reflex.py', 'python', 'python3'),
('core/javascript/controllers/example_controller.js', 'javascript', 'javascript'),
('core/templates/_example_demo.html', 'html', 'htmldjango'),
)

class ChatMixin(MixinBase):
files = (
('core/views/chat.py', 'python', 'python3'),
('core/reflexes/chat_reflex.py', 'python', 'python3'),
('core/javascript/controllers/chat_controller.js', 'javascript', 'javascript'),
('core/templates/_chat_demo.html', 'html', 'htmldjango'),
)

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@
"stimulus_reflex": "^3.4.0-pre5",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0"
},
"dependencies": {
"@rails/ujs": "^6.0.3-4",
"lodash-es": "^4.17.15",
"turbolinks": "^5.2.0"
}
}
15 changes: 15 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.0.3.tgz#722b4b639936129307ddbab3a390f6bcacf3e7bc"
integrity sha512-I01hgqxxnOgOtJTGlq0ZsGJYiTEEiSGVEGQn3vimZSqEP1HqzyFNbzGTq14Xdyeow2yGJjygjoFF1pmtE+SQaw==

"@rails/ujs@^6.0.3-4":
version "6.0.3-4"
resolved "https://nexus.gcds.coke.com/repository/npm-group/@rails/ujs/-/ujs-6.0.3-4.tgz#8dafc84178080f9c4f21076953ea1d0dc0bfe0fc"
integrity sha512-pNEEndJYNMCYEZG79MkoMc40AYKBfm0md8pawJ/SUu/1aIhToJcKu+9hHT/7WMLudsakOgC/C8KKFuZOs4QTgw==

"@stimulus/core@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@stimulus/core/-/core-1.1.1.tgz#42b0cfe5b73ca492f41de64b77a03980bae92c82"
Expand Down Expand Up @@ -639,6 +644,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"

lodash-es@^4.17.15:
version "4.17.15"
resolved "https://nexus.gcds.coke.com/repository/npm-group/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==

lodash@^4.17.15:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
Expand Down Expand Up @@ -976,6 +986,11 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==

turbolinks@^5.2.0:
version "5.2.0"
resolved "https://nexus.gcds.coke.com/repository/npm-group/turbolinks/-/turbolinks-5.2.0.tgz#e6877a55ea5c1cb3bb225f0a4ae303d6d32ff77c"
integrity sha512-pMiez3tyBo6uRHFNNZoYMmrES/IaGgMhQQM+VFF36keryjb5ms0XkVpmKHkfW/4Vy96qiGW3K9bz0tF5sK9bBw==

typical@^5.0.0, typical@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066"
Expand Down

0 comments on commit 07eed9e

Please sign in to comment.