templates.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. """Templates to use in the pynecone compiler."""
  2. from typing import Optional, Set
  3. from pynecone import constants
  4. from pynecone.utils import join
  5. # Template for the Pynecone config file.
  6. PCCONFIG = f"""import pynecone as pc
  7. config = pc.Config(
  8. app_name="{{app_name}}",
  9. db_url="{constants.DB_URL}",
  10. env=pc.Env.DEV,
  11. )
  12. """
  13. # Javascript formatting.
  14. CONST = "const {name} = {value}".format
  15. PROP = "{object}.{property}".format
  16. IMPORT_LIB = 'import "{lib}"'.format
  17. IMPORT_FIELDS = 'import {default}{others} from "{lib}"'.format
  18. def format_import(lib: str, default: str = "", rest: Optional[Set[str]] = None) -> str:
  19. """Format an import statement.
  20. Args:
  21. lib: The library to import from.
  22. default: The default field to import.
  23. rest: The set of fields to import from the library.
  24. Returns:
  25. The compiled import statement.
  26. """
  27. # Handle the case of direct imports with no libraries.
  28. if not lib:
  29. assert not default, "No default field allowed for empty library."
  30. assert rest is not None and len(rest) > 0, "No fields to import."
  31. return join([IMPORT_LIB(lib=lib) for lib in sorted(rest)])
  32. # Handle importing from a library.
  33. rest = rest or set()
  34. if len(default) == 0 and len(rest) == 0:
  35. # Handle the case of importing a library with no fields.
  36. return IMPORT_LIB(lib=lib)
  37. # Handle importing specific fields from a library.
  38. others = f'{{{", ".join(sorted(rest))}}}' if len(rest) > 0 else ""
  39. if default != "" and len(rest) > 0:
  40. default += ", "
  41. return IMPORT_FIELDS(default=default, others=others, lib=lib)
  42. # Code to render a NextJS Document root.
  43. DOCUMENT_ROOT = join(
  44. [
  45. "{imports}",
  46. "export default function Document() {{",
  47. "return (",
  48. "{document}",
  49. ")",
  50. "}}",
  51. ]
  52. ).format
  53. # Template for the theme file.
  54. THEME = "export default {theme}".format
  55. # Code to render a single NextJS page.
  56. PAGE = join(
  57. [
  58. "{imports}",
  59. "{custom_code}",
  60. "{constants}",
  61. "export default function Component() {{",
  62. "{state}",
  63. "{events}",
  64. "{effects}",
  65. "return (",
  66. "{render}",
  67. ")",
  68. "}}",
  69. ]
  70. ).format
  71. # Code to render a single exported custom component.
  72. COMPONENT = join(
  73. [
  74. "export const {name} = memo(({{{props}}}) => (",
  75. "{render}",
  76. "))",
  77. ]
  78. ).format
  79. # Code to render the custom components page.
  80. COMPONENTS = join(
  81. [
  82. "{imports}",
  83. "{components}",
  84. ]
  85. ).format
  86. # React state declarations.
  87. USE_STATE = CONST(
  88. name="[{state}, {set_state}]", value="useState({initial_state})"
  89. ).format
  90. def format_state_setter(state: str) -> str:
  91. """Format a state setter.
  92. Args:
  93. state: The name of the state variable.
  94. Returns:
  95. The compiled state setter.
  96. """
  97. return f"set{state[0].upper() + state[1:]}"
  98. def format_state(
  99. state: str,
  100. initial_state: str,
  101. ) -> str:
  102. """Format a state declaration.
  103. Args:
  104. state: The name of the state variable.
  105. initial_state: The initial state of the state variable.
  106. Returns:
  107. The compiled state declaration.
  108. """
  109. set_state = format_state_setter(state)
  110. return USE_STATE(state=state, set_state=set_state, initial_state=initial_state)
  111. # Events.
  112. EVENT_ENDPOINT = constants.Endpoint.EVENT.name
  113. EVENT_FN = join(
  114. [
  115. "const Event = events => {set_state}({{",
  116. " ...{state},",
  117. " events: [...{state}.events, ...events],",
  118. "}})",
  119. ]
  120. ).format
  121. # Effects.
  122. ROUTER = constants.ROUTER
  123. RESULT = constants.RESULT
  124. PROCESSING = constants.PROCESSING
  125. SOCKET = constants.SOCKET
  126. STATE = constants.STATE
  127. EVENTS = constants.EVENTS
  128. SET_RESULT = format_state_setter(RESULT)
  129. READY = f"const {{ isReady }} = {ROUTER};"
  130. USE_EFFECT = join(
  131. [
  132. "useEffect(() => {{",
  133. " if(!isReady) {{",
  134. " return;",
  135. " }}",
  136. " const reconnectSocket = () => {{",
  137. f" {SOCKET}.current.reconnect()",
  138. " }}",
  139. f" if (typeof {SOCKET}.current !== 'undefined') {{{{",
  140. f" if (!{SOCKET}.current) {{{{",
  141. f" window.addEventListener('focus', reconnectSocket)",
  142. f" connect({SOCKET}, {{state}}, {{set_state}}, {RESULT}, {SET_RESULT}, {ROUTER}, {EVENT_ENDPOINT})",
  143. " }}",
  144. " }}",
  145. " const update = async () => {{",
  146. f" if ({RESULT}.{STATE} != null) {{{{",
  147. f" {{set_state}}({{{{",
  148. f" ...{RESULT}.{STATE},",
  149. f" events: [...{{state}}.{EVENTS}, ...{RESULT}.{EVENTS}],",
  150. " }})",
  151. f" {SET_RESULT}({{{{",
  152. f" {STATE}: null,",
  153. f" {EVENTS}: [],",
  154. f" {PROCESSING}: false,",
  155. " }})",
  156. " }}",
  157. f" await updateState({{state}}, {{set_state}}, {RESULT}, {SET_RESULT}, {ROUTER}, {SOCKET}.current)",
  158. " }}",
  159. " update()",
  160. "}})",
  161. ]
  162. ).format
  163. # Routing
  164. ROUTER = f"const {constants.ROUTER} = useRouter()"
  165. # Sockets.
  166. SOCKET = "const socket = useRef(null)"