|
26 | 26 | "- ~~Line number side bar~~\n",
|
27 | 27 | "- auto-language detection\n",
|
28 | 28 | "- inline autocompletion\n",
|
29 |
| - "- save files\n", |
30 |
| - "- handle multiple files" |
| 29 | + "- ~~save files~~\n", |
| 30 | + "- ~~handle multiple files~~\n", |
| 31 | + "- execute code and show output" |
31 | 32 | ]
|
32 | 33 | },
|
33 | 34 | {
|
|
37 | 38 | "outputs": [],
|
38 | 39 | "source": [
|
39 | 40 | "#| export\n",
|
40 |
| - "from fasthtml import *\n", |
| 41 | + "from fasthtml.fastapp import *\n", |
| 42 | + "\n", |
| 43 | + "# Ace Editor (https://ace.c9.io/)\n", |
| 44 | + "ace_editor = Script(src=\"https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ace.min.js\")\n", |
| 45 | + "# Flexbox CSS (http://flexboxgrid.com/)\n", |
| 46 | + "gridlink = Link(rel=\"stylesheet\", href=\"https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css\", type=\"text/css\")\n", |
41 | 47 | "\n",
|
42 | 48 | "css = Style('''\\\n",
|
| 49 | + ".sidebar {\n", |
| 50 | + " background-color: #f4f4f4;\n", |
| 51 | + " overflow-y: auto;\n", |
| 52 | + " padding: 10px;\n", |
| 53 | + " box-shadow: 2px 0 5px rgba(0,0,0,0.1);\n", |
| 54 | + " height: calc(100vh - 40px);\n", |
| 55 | + "}\n", |
| 56 | + "\n", |
| 57 | + "#editor-container {\n", |
| 58 | + " flex: 1;\n", |
| 59 | + " height: calc(100vh - 40px);\n", |
| 60 | + "}\n", |
| 61 | + "\n", |
43 | 62 | "#editor {\n",
|
44 |
| - " position: absolute;\n", |
45 |
| - " top: 0;\n", |
46 |
| - " right: 0;\n", |
47 |
| - " bottom: 0;\n", |
48 |
| - " left: 0;\n", |
| 63 | + " height: 100%;\n", |
| 64 | + " width: 100%;\n", |
| 65 | + "}\n", |
| 66 | + "\n", |
| 67 | + ".box-row {\n", |
| 68 | + " border: 1px solid #ccc;\n", |
49 | 69 | "}\n",
|
50 | 70 | "''')\n",
|
51 |
| - "# Ace Editor (https://ace.c9.io/)\n", |
52 |
| - "ace_editor = Script(src=\"https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.0/ace.min.js\")\n", |
53 |
| - "app = FastHTML(hdrs=(ace_editor, css))\n", |
54 |
| - "rt = app.route" |
| 71 | + "\n", |
| 72 | + "app, files, File = fast_app('data/files.db', hdrs=(ace_editor, gridlink, css), id=int, filename=str, content=str, pk='id')\n", |
| 73 | + "rt = app.route\n", |
| 74 | + "\n", |
| 75 | + "id_curr = 'current-file'\n", |
| 76 | + "id_list = 'file-list'" |
| 77 | + ] |
| 78 | + }, |
| 79 | + { |
| 80 | + "cell_type": "code", |
| 81 | + "execution_count": null, |
| 82 | + "metadata": {}, |
| 83 | + "outputs": [], |
| 84 | + "source": [ |
| 85 | + "files.insert(File(filename='file1.txt', content='Hello, World!'))" |
55 | 86 | ]
|
56 | 87 | },
|
57 | 88 | {
|
|
62 | 93 | "source": [
|
63 | 94 | "#| export\n",
|
64 | 95 | "js_code = \"\"\"\\\n",
|
65 |
| - "var editor = ace.edit(\"editor\");\n", |
66 |
| - "editor.setTheme(\"ace/theme/monokai\");\n", |
67 |
| - "editor.session.setMode(\"ace/mode/javascript\");\n", |
| 96 | + "function renderEditor() {\n", |
| 97 | + " var editor = ace.edit(\"editor\");\n", |
| 98 | + " editor.setTheme(\"ace/theme/monokai\");\n", |
| 99 | + " editor.session.setMode(\"ace/mode/javascript\");\n", |
| 100 | + "}\n", |
| 101 | + "\n", |
| 102 | + "function getFileContent() {\n", |
| 103 | + " var editor = ace.edit(\"editor\");\n", |
| 104 | + " return editor.getValue();\n", |
| 105 | + "}\n", |
| 106 | + "\n", |
| 107 | + "renderEditor();\n", |
68 | 108 | "\"\"\"\n",
|
69 | 109 | "\n",
|
70 | 110 | "example_code = \"\"\"\\\n",
|
|
74 | 114 | "}\"\"\""
|
75 | 115 | ]
|
76 | 116 | },
|
| 117 | + { |
| 118 | + "cell_type": "code", |
| 119 | + "execution_count": null, |
| 120 | + "metadata": {}, |
| 121 | + "outputs": [], |
| 122 | + "source": [ |
| 123 | + "#| export\n", |
| 124 | + "def SaveFile():\n", |
| 125 | + " return Form(\n", |
| 126 | + " Input(type=\"text\", id=\"filename\", name=\"filename\", placeholder=\"Filename\", required=True),\n", |
| 127 | + " Button(\"Save\", type=\"submit\", hx_post=\"/save\", target_id=id_list, hx_swap=\"beforeend\", hx_vals=\"js:{content: getFileContent(), filename: filename.value}\"),\n", |
| 128 | + " cls=\"col-xs-12\"\n", |
| 129 | + " )\n", |
| 130 | + " ...\n", |
| 131 | + "def Toolbar():\n", |
| 132 | + " return Div(\n", |
| 133 | + " Div(\n", |
| 134 | + " Select(\n", |
| 135 | + " Option(\"JavaScript\", value=\"javascript\"),\n", |
| 136 | + " Option(\"Python\", value=\"python\"),\n", |
| 137 | + " Option(\"HTML\", value=\"html\"),\n", |
| 138 | + " Option(\"CSS\", value=\"css\"),\n", |
| 139 | + " Option(\"Markdown\", value=\"markdown\"),\n", |
| 140 | + " id=\"language\"\n", |
| 141 | + " ),\n", |
| 142 | + " Button(\"Run\", id=\"run\"),\n", |
| 143 | + " SaveFile(),\n", |
| 144 | + " cls=\"col-xs-12 toolbar\"\n", |
| 145 | + " ),\n", |
| 146 | + " cls=\"row\"\n", |
| 147 | + " )" |
| 148 | + ] |
| 149 | + }, |
| 150 | + { |
| 151 | + "cell_type": "code", |
| 152 | + "execution_count": null, |
| 153 | + "metadata": {}, |
| 154 | + "outputs": [], |
| 155 | + "source": [ |
| 156 | + "#| export\n", |
| 157 | + "def FileRow(file: File):\n", |
| 158 | + " return Li(\n", |
| 159 | + " A(\n", |
| 160 | + " file.filename, hx_get=f'/files/{file.id}', target_id=\"editor-container\", hx_swap=\"innerHTML\",\n", |
| 161 | + " hx_on=\"htmx:afterSwap: renderEditor()\",\n", |
| 162 | + " ),\n", |
| 163 | + " id=f'file-{file.id}'\n", |
| 164 | + " )" |
| 165 | + ] |
| 166 | + }, |
| 167 | + { |
| 168 | + "cell_type": "code", |
| 169 | + "execution_count": null, |
| 170 | + "metadata": {}, |
| 171 | + "outputs": [], |
| 172 | + "source": [ |
| 173 | + "#| export\n", |
| 174 | + "def Sidebar():\n", |
| 175 | + " return Div(\n", |
| 176 | + " Div(\n", |
| 177 | + " Ul(*map(FileRow, files()), id=id_list), cls=\"sidebar\"\n", |
| 178 | + " ),\n", |
| 179 | + " cls=\"col-xs-12 col-sm-3\"\n", |
| 180 | + " )" |
| 181 | + ] |
| 182 | + }, |
77 | 183 | {
|
78 | 184 | "cell_type": "code",
|
79 | 185 | "execution_count": 3,
|
|
82 | 188 | "source": [
|
83 | 189 | "#| export\n",
|
84 | 190 | "def CodeEditor():\n",
|
85 |
| - " editor = Div(example_code, id=\"editor\")\n", |
86 |
| - " return Title(\"Code Editor\",), editor, Script(NotStr(js_code))" |
| 191 | + " toolbar = Toolbar()\n", |
| 192 | + " main = Div(\n", |
| 193 | + " Sidebar(),\n", |
| 194 | + " Div(\n", |
| 195 | + " Div(example_code, id=\"editor\"),\n", |
| 196 | + " id=\"editor-container\", cls=\"col-xs-12 col-sm-9\", hx_on=\"htmx:afterSwap: renderEditor()\"\n", |
| 197 | + " ),\n", |
| 198 | + " cls=\"row\"\n", |
| 199 | + " )\n", |
| 200 | + " return Title(\"Code Editor\",), Div(toolbar, main, cls=\"container-fluid\"), Script(NotStr(js_code))" |
87 | 201 | ]
|
88 | 202 | },
|
89 | 203 | {
|
|
134 | 248 | },
|
135 | 249 | {
|
136 | 250 | "cell_type": "code",
|
137 |
| - "execution_count": 22, |
| 251 | + "execution_count": null, |
| 252 | + "metadata": {}, |
| 253 | + "outputs": [], |
| 254 | + "source": [ |
| 255 | + "#| export\n", |
| 256 | + "@rt(\"/files/{id}\")\n", |
| 257 | + "def get(id:int):\n", |
| 258 | + " return Div(files[id].content, id=\"editor\", cls=\"ace_editor ace-tm\")#, hx_on=\"htmx:afterSwap: renderEditor()\")," |
| 259 | + ] |
| 260 | + }, |
| 261 | + { |
| 262 | + "cell_type": "code", |
| 263 | + "execution_count": null, |
| 264 | + "metadata": {}, |
| 265 | + "outputs": [], |
| 266 | + "source": [ |
| 267 | + "#| export\n", |
| 268 | + "@rt(\"/save\")\n", |
| 269 | + "def post(filename: str, content: str):\n", |
| 270 | + " file = File(filename=filename, content=content, id=len(files()) + 1)\n", |
| 271 | + " files.insert(file)\n", |
| 272 | + " return FileRow(file)" |
| 273 | + ] |
| 274 | + }, |
| 275 | + { |
| 276 | + "cell_type": "code", |
| 277 | + "execution_count": 53, |
138 | 278 | "metadata": {},
|
139 | 279 | "outputs": [],
|
140 | 280 | "source": [
|
|
0 commit comments