Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat demo ... #3

Merged
merged 9 commits into from
Nov 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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