diff --git a/packages/app/src/app/components/CodeEditor/Configuration/index.tsx b/packages/app/src/app/components/CodeEditor/Configuration/index.tsx index d3a4e9a846d..372445bb4b6 100644 --- a/packages/app/src/app/components/CodeEditor/Configuration/index.tsx +++ b/packages/app/src/app/components/CodeEditor/Configuration/index.tsx @@ -3,7 +3,7 @@ import { TextOperation } from 'ot'; import { Module } from '@codesandbox/common/lib/types'; import getUI from '@codesandbox/common/lib/templates/configuration/ui'; import { getType } from 'app/utils/get-type'; -import { EntryIcons } from 'app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons'; +import { EntryIcons } from 'app/components/EntryIcons'; import Tooltip from '@codesandbox/common/lib/components/Tooltip'; import { ConfigurationFile } from '@codesandbox/common/lib/templates/configuration/types'; diff --git a/packages/app/src/app/components/CodeEditor/FilePath/index.js b/packages/app/src/app/components/CodeEditor/FilePath/index.js index 854adbf922e..9992296f2ad 100644 --- a/packages/app/src/app/components/CodeEditor/FilePath/index.js +++ b/packages/app/src/app/components/CodeEditor/FilePath/index.js @@ -1,7 +1,7 @@ import * as React from 'react'; import { getModulePath } from '@codesandbox/common/lib/sandbox/modules'; import Tooltip from '@codesandbox/common/lib/components/Tooltip'; -import EntryIcons from 'app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons'; +import EntryIcons from 'app/components/EntryIcons'; import { getType } from 'app/utils/get-type'; import { Container, Chevron, FileName, StyledExitZen } from './elements'; diff --git a/packages/app/src/app/components/CodeEditor/FuzzySearch/index.js b/packages/app/src/app/components/CodeEditor/FuzzySearch/index.js index 2a7890c74ba..36a6de82cdb 100644 --- a/packages/app/src/app/components/CodeEditor/FuzzySearch/index.js +++ b/packages/app/src/app/components/CodeEditor/FuzzySearch/index.js @@ -4,7 +4,7 @@ import Downshift from 'downshift'; import matchSorter from 'match-sorter'; import { getModulePath } from '@codesandbox/common/lib/sandbox/modules'; import Input from '@codesandbox/common/lib/components/Input'; -import { EntryIcons } from 'app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons'; +import { EntryIcons } from 'app/components/EntryIcons'; // eslint-disable-next-line import/extensions import { getType } from 'app/utils/get-type.ts'; import { ESC } from '@codesandbox/common/lib/utils/keycodes'; diff --git a/packages/app/src/app/components/CodeEditor/VSCode/Configuration/elements.ts b/packages/app/src/app/components/CodeEditor/VSCode/Configuration/elements.ts deleted file mode 100644 index 45dbff9e00e..00000000000 --- a/packages/app/src/app/components/CodeEditor/VSCode/Configuration/elements.ts +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; - -export const Container = styled.div` - padding: 1rem; - color: ${props => props.theme['sideBar.foreground'] || 'inherit'}; - box-sizing: border-box; - max-height: 100%; - overflow: auto; -`; - -export const Title = styled.h1` - display: inline-block; - font-weight: 600; - font-size: 1.25rem; - color: ${props => props.theme['sideBar.foreground'] || 'inherit'}; - text-transform: uppercase; - margin-top: 0; - margin-bottom: 0; - margin-left: 1rem; - flex: 1; -`; - -export const Description = styled.p` - font-size: 1; - line-height: 1.4; - color: ${props => props.theme['sideBar.foreground'] || 'inherit'}; -`; diff --git a/packages/app/src/app/components/CodeEditor/VSCode/Configuration/index.tsx b/packages/app/src/app/components/CodeEditor/VSCode/Configuration/index.tsx deleted file mode 100644 index 3663a7a6bc5..00000000000 --- a/packages/app/src/app/components/CodeEditor/VSCode/Configuration/index.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { ConfigurationFile } from '@codesandbox/common/lib/templates/configuration/types'; -import { withTheme } from 'styled-components'; -import { ThemeProvider } from '@codesandbox/components'; -import getUI from '@codesandbox/common/lib/templates/configuration/ui'; -import theme from '@codesandbox/common/lib/theme'; -import { Module } from '@codesandbox/common/lib/types'; -import { EntryIcons } from 'app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons'; -import { getType } from 'app/utils/get-type'; -import { TextOperation } from 'ot'; -import React from 'react'; - -import { Editor, Props as EditorProps } from '../../types'; // eslint-disable-line -import { Container, Description, Title } from './elements'; - -type Disposable = { - dispose: () => void; -}; - -type Props = EditorProps & { - config: ConfigurationFile; - onChange: (code: string, moduleShortid: string) => void; - toggleConfigUI: () => void; - onDidChangeDirty: (cb: () => void) => Disposable; - getCode: () => string; - onChangeVSCode: (val: string) => void; - onDispose: (cb: () => void) => void; - openText: () => void; - theme: any; -}; - -class ConfigurationComponent extends React.PureComponent { - disposeInitializer: Function; - currentModule: Module; - dirtyChangeListener: Disposable; - receivingCode: boolean = false; - - constructor(props: Props) { - super(props); - - this.registerListeners(); - } - - registerListeners() { - if (this.props.onDidChangeDirty) { - this.dirtyChangeListener = this.props.onDidChangeDirty(() => { - this.forceUpdate(); - this.props.onChange( - this.props.getCode(), - this.props.currentModule.shortid - ); - }); - } - - this.props.onDispose(() => { - this.dirtyChangeListener.dispose(); - }); - } - - componentDidMount() { - if (this.props.onInitialized) { - this.disposeInitializer = this.props.onInitialized(this); - } - } - - componentDidUpdate(prevProps) { - if (prevProps.currentModule.id !== this.props.currentModule.id) { - this.dirtyChangeListener.dispose(); - - this.registerListeners(); - } - } - - componentWillUnmount() { - if (this.disposeInitializer) { - this.disposeInitializer(); - } - } - - sendLiveChanges = (code: string) => { - const { sendTransforms, isLive, onCodeReceived } = this.props; - if (sendTransforms) { - const oldCode = this.currentModule.code || ''; - - // We don't know exactly what changed, just that the code changed. So - // we send the whole code. - - const op = new TextOperation(); - - op.delete(oldCode.length); - op.insert(code); - - sendTransforms(op); - } else if (!isLive && onCodeReceived) { - onCodeReceived(); - } - }; - - updateFile = (code: string) => { - const { isLive, sendTransforms } = this.props; - - if (isLive && sendTransforms && !this.receivingCode) { - this.sendLiveChanges(code); - } - - this.props.onChangeVSCode(code); - }; - - render() { - const { config, width, height, sandbox } = this.props; - const { currentModule } = this.props; - - const { ConfigWizard } = getUI(config.type); - - return ( - -
- - {config.title} -
- - - {config.description}{' '} - - More info... - - - - - -
- ); - } -} - -export const Configuration = withTheme(ConfigurationComponent); diff --git a/packages/app/src/app/components/CodeEditor/VSCode/elements.ts b/packages/app/src/app/components/CodeEditor/VSCode/elements.ts deleted file mode 100644 index d92a89ba540..00000000000 --- a/packages/app/src/app/components/CodeEditor/VSCode/elements.ts +++ /dev/null @@ -1,13 +0,0 @@ -import styled, { createGlobalStyle } from 'styled-components'; - -export const Container = styled.div` - width: 100%; - height: 100%; - z-index: 30; -`; - -export const GlobalStyles = createGlobalStyle` - .monaco-quick-open-widget { - z-index: 50 !important; - } -`; diff --git a/packages/app/src/app/components/CodeEditor/VSCode/icon-theme.css b/packages/app/src/app/components/CodeEditor/VSCode/icon-theme.css deleted file mode 100644 index b903d7a6687..00000000000 --- a/packages/app/src/app/components/CodeEditor/VSCode/icon-theme.css +++ /dev/null @@ -1,1491 +0,0 @@ -@font-face { - src: url('/static/fonts/seti.woff') format('woff'); - font-family: 'seti'; - font-weight: normal; - font-style: normal; -} -.show-file-icons .file-icon::before, -.show-file-icons .folder-icon::before, -.show-file-icons .rootfolder-icon::before { - font-family: 'seti'; - font-size: 150%; -} -.show-file-icons .file-icon::before, -.show-file-icons .txt-ext-file-icon.ext-file-icon.file-icon::before { - color: #d4d7d6; - content: '\E01D'; -} -.show-file-icons .bat-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E086'; -} -.show-file-icons .clojure-lang-file-icon.file-icon::before { - color: #8dc149; - content: '\E010'; -} -.show-file-icons .coffeescript-lang-file-icon.file-icon::before { - color: #cbcb41; - content: '\E012'; -} -.show-file-icons .c-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E009'; -} -.show-file-icons .cpp-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E016'; -} -.show-file-icons .csharp-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E008'; -} -.show-file-icons .css-lang-file-icon.file-icon::before, -.show-file-icons - .css\.map-ext-file-icon.map-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .sss-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E019'; -} -.show-file-icons .dockerfile-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E01F'; -} -.show-file-icons .fsharp-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E028'; -} -.show-file-icons .go-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E033'; -} -.show-file-icons .groovy-lang-file-icon.file-icon::before, -.show-file-icons .gsp-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E035'; -} -.show-file-icons .handlebars-lang-file-icon.file-icon::before, -.show-file-icons .mustache-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .stache-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E058'; -} -.show-file-icons .html-lang-file-icon.file-icon::before { - color: #e37933; - content: '\E03E'; -} -.show-file-icons .properties-lang-file-icon.file-icon::before, -.show-file-icons .java-lang-file-icon.file-icon::before, -.show-file-icons .class-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .classpath-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E046'; -} -.show-file-icons .javascriptreact-lang-file-icon.file-icon::before, -.show-file-icons .typescriptreact-lang-file-icon.file-icon::before, -.show-file-icons .cjsx-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E069'; -} -.show-file-icons .javascript-lang-file-icon.file-icon::before, -.show-file-icons - .js\.map-ext-file-icon.map-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .es-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .es5-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .es7-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E047'; -} -.show-file-icons .json-lang-file-icon.file-icon::before, -.show-file-icons .jsonc-lang-file-icon.file-icon::before, -.show-file-icons .cson-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E04B'; -} -.show-file-icons .less-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E04E'; -} -.show-file-icons .lua-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E053'; -} -.show-file-icons .makefile-lang-file-icon.file-icon::before { - color: #e37933; - content: '\E054'; -} -.show-file-icons .markdown-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E055'; -} -.show-file-icons .objective-c-lang-file-icon.file-icon::before { - color: #cbcb41; - content: '\E009'; -} -.show-file-icons .perl-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E060'; -} -.show-file-icons .php-lang-file-icon.file-icon::before, -.show-file-icons - .php\.inc-ext-file-icon.inc-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E062'; -} -.show-file-icons .powershell-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E063'; -} -.show-file-icons .jade-lang-file-icon.file-icon::before { - color: #cc3e44; - content: '\E045'; -} -.show-file-icons .python-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E067'; -} -.show-file-icons .ruby-lang-file-icon.file-icon::before { - color: #cc3e44; - content: '\E06B'; -} -.show-file-icons .rust-lang-file-icon.file-icon::before { - color: #6d8086; - content: '\E06C'; -} -.show-file-icons .scss-lang-file-icon.file-icon::before, -.show-file-icons .sass-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .sass-lint\.yml-name-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before { - color: #f55385; - content: '\E06E'; -} -.show-file-icons .shellscript-lang-file-icon.file-icon::before, -.show-file-icons .fish-ext-file-icon.ext-file-icon.file-icon::before { - color: #4d5a5e; - content: '\E073'; -} -.show-file-icons .sql-lang-file-icon.file-icon::before { - color: #f55385; - content: '\E01C'; -} -.show-file-icons .swift-lang-file-icon.file-icon::before { - color: #e37933; - content: '\E07A'; -} -.show-file-icons .typescript-lang-file-icon.file-icon::before { - color: #519aba; - content: '\E080'; -} -.show-file-icons .xml-lang-file-icon.file-icon::before { - color: #e37933; - content: '\E089'; -} -.show-file-icons .yaml-lang-file-icon.file-icon::before { - color: #a074c4; - content: '\E08B'; -} -.show-file-icons .bsl-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E007'; -} -.show-file-icons .mdo-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E057'; -} -.show-file-icons .asm-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .s-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E003'; -} -.show-file-icons .h-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E009'; -} -.show-file-icons .hh-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .hpp-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .hxx-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E016'; -} -.show-file-icons .edn-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E010'; -} -.show-file-icons .cfc-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .cfm-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E014'; -} -.show-file-icons .config-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .cfg-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .conf-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .toml-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .direnv-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .env-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .static-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .editorconfig-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .slugignore-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .htaccess-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .mime\.types-name-file-icon.types-ext-file-icon.ext-file-icon.file-icon::before { - color: #6d8086; - content: '\E015'; -} -.show-file-icons .cr-ext-file-icon.ext-file-icon.file-icon::before { - color: #d4d7d6; - content: '\E017'; -} -.show-file-icons .ecr-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .slang-ext-file-icon.ext-file-icon.file-icon::before { - color: #d4d7d6; - content: '\E018'; -} -.show-file-icons .csv-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E01A'; -} -.show-file-icons .xls-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .xlsx-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E088'; -} -.show-file-icons .cake-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E00A'; -} -.show-file-icons .ctp-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E00B'; -} -.show-file-icons .d-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E01B'; -} -.show-file-icons .doc-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .docx-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E087'; -} -.show-file-icons .ejs-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E021'; -} -.show-file-icons .ex-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E022'; -} -.show-file-icons .exs-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E023'; -} -.show-file-icons .elm-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E024'; -} -.show-file-icons .ico-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E029'; -} -.show-file-icons .gitignore-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .gitconfig-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .gitkeep-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .gitattributes-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .gitmodules-ext-file-icon.ext-file-icon.file-icon::before { - color: #41535b; - content: '\E02E'; -} -.show-file-icons .slide-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .article-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E032'; -} -.show-file-icons .gradle-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E034'; -} -.show-file-icons .haml-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E039'; -} -.show-file-icons .hs-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .lhs-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E03A'; -} -.show-file-icons .hx-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E03B'; -} -.show-file-icons .hxs-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E03B'; -} -.show-file-icons .hxp-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E03B'; -} -.show-file-icons .hxml-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E03B'; -} -.show-file-icons - .spec\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E047'; -} -.show-file-icons .jinja-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .jinja2-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E049'; -} -.show-file-icons .jl-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E04C'; -} -.show-file-icons .liquid-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E050'; -} -.show-file-icons .ls-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E051'; -} -.show-file-icons .njk-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .nunjucks-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .nunjs-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .nunj-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .njs-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .nj-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E05C'; -} -.show-file-icons - .npm-debug\.log-ext-file-icon.log-ext-file-icon.ext-file-icon.file-icon::before { - color: #41535b; - content: '\E05A'; -} -.show-file-icons .npmignore-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .npmrc-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E05A'; -} -.show-file-icons .ml-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .mli-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .cmx-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .cmxa-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E05D'; -} -.show-file-icons .odata-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E05E'; -} -.show-file-icons .pug-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E065'; -} -.show-file-icons .pp-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .epp-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E066'; -} -.show-file-icons .erb-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .erb\.html-ext-file-icon.html-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .html\.erb-ext-file-icon.erb-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E03F'; -} -.show-file-icons .springbeans-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E076'; -} -.show-file-icons .slim-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E074'; -} -.show-file-icons - .smarty\.tpl-ext-file-icon.tpl-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E075'; -} -.show-file-icons .sbt-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E06F'; -} -.show-file-icons .scala-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E070'; -} -.show-file-icons .sol-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E027'; -} -.show-file-icons .styl-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E077'; -} -.show-file-icons .tf-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .tf\.json-ext-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .tfvars-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E07B'; -} -.show-file-icons .tex-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E07C'; -} -.show-file-icons .sty-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E07C'; -} -.show-file-icons .dtx-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E07C'; -} -.show-file-icons .ins-ext-file-icon.ext-file-icon.file-icon::before { - color: #d4d7d6; - content: '\E07C'; -} -.show-file-icons .twig-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E07F'; -} -.show-file-icons - .spec\.ts-ext-file-icon.ts-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E080'; -} -.show-file-icons .vala-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .vapi-ext-file-icon.ext-file-icon.file-icon::before { - color: #6d8086; - content: '\E081'; -} -.show-file-icons .vue-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E083'; -} -.show-file-icons .jar-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E08C'; -} -.show-file-icons .zip-ext-file-icon.ext-file-icon.file-icon::before { - color: #6d8086; - content: '\E08C'; -} -.show-file-icons .wgt-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E085'; -} -.show-file-icons .ai-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E041'; -} -.show-file-icons .psd-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E061'; -} -.show-file-icons .pdf-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E05F'; -} -.show-file-icons .eot-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .ttf-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .woff-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .woff2-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E02D'; -} -.show-file-icons .gif-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .jpg-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .jpeg-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .png-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .pxm-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .svgx-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E042'; -} -.show-file-icons .svg-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E079'; -} -.show-file-icons .sublime-project-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .sublime-workspace-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E078'; -} -.show-file-icons .component-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .cls-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E06D'; -} -.show-file-icons .mov-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .ogv-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .webm-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .avi-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .mpg-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .mp4-ext-file-icon.ext-file-icon.file-icon::before { - color: #f55385; - content: '\E082'; -} -.show-file-icons .mp3-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .ogg-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .wav-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E004'; -} -.show-file-icons .babelrc-ext-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E005'; -} -.show-file-icons .bowerrc-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .bower\.json-name-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E006'; -} -.show-file-icons .dockerignore-ext-file-icon.ext-file-icon.file-icon::before { - color: #4d5a5e; - content: '\E01F'; -} -.show-file-icons - .codeclimate\.yml-ext-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E011'; -} -.show-file-icons .eslintrc-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .eslintrc\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .eslintrc\.yaml-ext-file-icon.yaml-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .eslintrc\.yml-ext-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .eslintrc\.json-ext-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E026'; -} -.show-file-icons .eslintignore-ext-file-icon.ext-file-icon.file-icon::before { - color: #4d5a5e; - content: '\E026'; -} -.show-file-icons .firebaserc-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .firebase\.json-name-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E02A'; -} -.show-file-icons .jshintrc-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .jscsrc-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E047'; -} -.show-file-icons .tmp-ext-file-icon.ext-file-icon.file-icon::before { - color: #6d8086; - content: '\E00F'; -} -.show-file-icons .key-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .cert-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E052'; -} -.show-file-icons .ds_store-ext-file-icon.ext-file-icon.file-icon::before { - color: #41535b; - content: '\E040'; -} -.show-file-icons .mix-name-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E03D'; -} -.show-file-icons - .karma\.conf\.js-name-file-icon.conf\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .karma\.conf\.coffee-name-file-icon.conf\.coffee-ext-file-icon.coffee-ext-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E04D'; -} -.show-file-icons - .readme\.md-name-file-icon.md-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E043'; -} -.show-file-icons - .changelog\.md-name-file-icon.md-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .changelog-name-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .version\.md-name-file-icon.md-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .version-name-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E00F'; -} -.show-file-icons .mvnw-name-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E056'; -} -.show-file-icons .jenkinsfile-name-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E048'; -} -.show-file-icons - .docker-healthcheck-name-file-icon.ext-file-icon.file-icon::before { - color: #8dc149; - content: '\E01F'; -} -.show-file-icons - .docker-compose\.yml-name-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .docker-compose\.yaml-name-file-icon.yaml-ext-file-icon.ext-file-icon.file-icon::before { - color: #f55385; - content: '\E01F'; -} -.show-file-icons .geckodriver-name-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E02B'; -} -.show-file-icons - .gruntfile\.js-name-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .gruntfile\.babel\.js-name-file-icon.babel\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .gruntfile\.coffee-name-file-icon.coffee-ext-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E036'; -} -.show-file-icons .gulpfile-name-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E037'; -} -.show-file-icons - .ionic\.config\.json-name-file-icon.config\.json-ext-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .ionic\.project-name-file-icon.project-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E044'; -} -.show-file-icons - .rollup\.config\.js-name-file-icon.config\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E06A'; -} -.show-file-icons - .yarn\.clean-name-file-icon.clean-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .yarn\.lock-name-file-icon.lock-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E08A'; -} -.show-file-icons - .webpack\.config\.js-name-file-icon.config\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.show-file-icons - .webpack\.config\.build\.js-name-file-icon.config\.build\.js-ext-file-icon.build\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E084'; -} -.show-file-icons .license-name-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .licence-name-file-icon.ext-file-icon.file-icon::before, -.show-file-icons .copying-name-file-icon.ext-file-icon.file-icon::before { - color: #cbcb41; - content: '\E04F'; -} -.show-file-icons .compiling-name-file-icon.ext-file-icon.file-icon::before { - color: #e37933; - content: '\E04F'; -} -.show-file-icons .contributing-name-file-icon.ext-file-icon.file-icon::before { - color: #cc3e44; - content: '\E04F'; -} -.show-file-icons .qmakefile-name-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E054'; -} -.show-file-icons .omakefile-name-file-icon.ext-file-icon.file-icon::before { - color: #6d8086; - content: '\E054'; -} -.show-file-icons - .cmakelists\.txt-name-file-icon.txt-ext-file-icon.ext-file-icon.file-icon::before { - color: #519aba; - content: '\E054'; -} -.show-file-icons .procfile-name-file-icon.ext-file-icon.file-icon::before { - color: #a074c4; - content: '\E03C'; -} -.show-file-icons .todo-name-file-icon.ext-file-icon.file-icon::before { - content: '\E07E'; -} -.show-file-icons - .npm-debug\.log-name-file-icon.log-ext-file-icon.ext-file-icon.file-icon::before { - color: #41535b; - content: '\E05B'; -} -.vs .show-file-icons .file-icon::before, -.vs .show-file-icons .txt-ext-file-icon.ext-file-icon.file-icon::before { - color: #bfc2c1; - content: '\E01D'; -} -.vs .show-file-icons .bat-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E086'; -} -.vs .show-file-icons .clojure-lang-file-icon.file-icon::before { - color: #7fae42; - content: '\E010'; -} -.vs .show-file-icons .coffeescript-lang-file-icon.file-icon::before { - color: #b7b73b; - content: '\E012'; -} -.vs .show-file-icons .c-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E009'; -} -.vs .show-file-icons .cpp-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E016'; -} -.vs .show-file-icons .csharp-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E008'; -} -.vs .show-file-icons .css-lang-file-icon.file-icon::before, -.vs - .show-file-icons - .css\.map-ext-file-icon.map-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .sss-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E019'; -} -.vs .show-file-icons .dockerfile-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E01F'; -} -.vs .show-file-icons .fsharp-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E028'; -} -.vs .show-file-icons .go-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E033'; -} -.vs .show-file-icons .groovy-lang-file-icon.file-icon::before, -.vs .show-file-icons .gsp-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E035'; -} -.vs .show-file-icons .handlebars-lang-file-icon.file-icon::before, -.vs .show-file-icons .mustache-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .stache-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E058'; -} -.vs .show-file-icons .html-lang-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E03E'; -} -.vs .show-file-icons .properties-lang-file-icon.file-icon::before, -.vs .show-file-icons .java-lang-file-icon.file-icon::before, -.vs .show-file-icons .class-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .classpath-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E046'; -} -.vs .show-file-icons .javascriptreact-lang-file-icon.file-icon::before, -.vs .show-file-icons .typescriptreact-lang-file-icon.file-icon::before, -.vs .show-file-icons .cjsx-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E069'; -} -.vs .show-file-icons .javascript-lang-file-icon.file-icon::before, -.vs - .show-file-icons - .js\.map-ext-file-icon.map-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .es-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .es5-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .es7-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E047'; -} -.vs .show-file-icons .json-lang-file-icon.file-icon::before, -.vs .show-file-icons .jsonc-lang-file-icon.file-icon::before, -.vs .show-file-icons .cson-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E04B'; -} -.vs .show-file-icons .less-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E04E'; -} -.vs .show-file-icons .lua-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E053'; -} -.vs .show-file-icons .makefile-lang-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E054'; -} -.vs .show-file-icons .markdown-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E055'; -} -.vs .show-file-icons .objective-c-lang-file-icon.file-icon::before { - color: #b7b73b; - content: '\E009'; -} -.vs .show-file-icons .perl-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E060'; -} -.vs .show-file-icons .php-lang-file-icon.file-icon::before, -.vs - .show-file-icons - .php\.inc-ext-file-icon.inc-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E062'; -} -.vs .show-file-icons .powershell-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E063'; -} -.vs .show-file-icons .jade-lang-file-icon.file-icon::before { - color: #b8383d; - content: '\E045'; -} -.vs .show-file-icons .python-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E067'; -} -.vs .show-file-icons .ruby-lang-file-icon.file-icon::before { - color: #b8383d; - content: '\E06B'; -} -.vs .show-file-icons .rust-lang-file-icon.file-icon::before { - color: #627379; - content: '\E06C'; -} -.vs .show-file-icons .scss-lang-file-icon.file-icon::before, -.vs .show-file-icons .sass-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .sass-lint\.yml-name-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before { - color: #dd4b78; - content: '\E06E'; -} -.vs .show-file-icons .shellscript-lang-file-icon.file-icon::before, -.vs .show-file-icons .fish-ext-file-icon.ext-file-icon.file-icon::before { - color: #455155; - content: '\E073'; -} -.vs .show-file-icons .sql-lang-file-icon.file-icon::before { - color: #dd4b78; - content: '\E01C'; -} -.vs .show-file-icons .swift-lang-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E07A'; -} -.vs .show-file-icons .typescript-lang-file-icon.file-icon::before { - color: #498ba7; - content: '\E080'; -} -.vs .show-file-icons .xml-lang-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E089'; -} -.vs .show-file-icons .yaml-lang-file-icon.file-icon::before { - color: #9068b0; - content: '\E08B'; -} -.vs .show-file-icons .bsl-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E007'; -} -.vs .show-file-icons .mdo-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E057'; -} -.vs .show-file-icons .asm-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .s-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E003'; -} -.vs .show-file-icons .h-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E009'; -} -.vs .show-file-icons .hh-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .hpp-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .hxx-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E016'; -} -.vs .show-file-icons .edn-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E010'; -} -.vs .show-file-icons .cfc-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .cfm-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E014'; -} -.vs .show-file-icons .config-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .cfg-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .conf-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .toml-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .direnv-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .env-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .static-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .editorconfig-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .slugignore-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .htaccess-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .mime\.types-name-file-icon.types-ext-file-icon.ext-file-icon.file-icon::before { - color: #627379; - content: '\E015'; -} -.vs .show-file-icons .cr-ext-file-icon.ext-file-icon.file-icon::before { - color: #bfc2c1; - content: '\E017'; -} -.vs .show-file-icons .ecr-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .slang-ext-file-icon.ext-file-icon.file-icon::before { - color: #bfc2c1; - content: '\E018'; -} -.vs .show-file-icons .csv-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E01A'; -} -.vs .show-file-icons .xls-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .xlsx-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E088'; -} -.vs .show-file-icons .cake-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E00A'; -} -.vs .show-file-icons .ctp-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E00B'; -} -.vs .show-file-icons .d-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E01B'; -} -.vs .show-file-icons .doc-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .docx-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E087'; -} -.vs .show-file-icons .ejs-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E021'; -} -.vs .show-file-icons .ex-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E022'; -} -.vs .show-file-icons .exs-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E023'; -} -.vs .show-file-icons .elm-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E024'; -} -.vs .show-file-icons .ico-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E029'; -} -.vs .show-file-icons .gitignore-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .gitconfig-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .gitkeep-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .gitattributes-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .gitmodules-ext-file-icon.ext-file-icon.file-icon::before { - color: #3b4b52; - content: '\E02E'; -} -.vs .show-file-icons .slide-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .article-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E032'; -} -.vs .show-file-icons .gradle-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E034'; -} -.vs .show-file-icons .haml-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E039'; -} -.vs .show-file-icons .hs-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .lhs-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E03A'; -} -.vs .show-file-icons .hx-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E03B'; -} -.vs .show-file-icons .hxs-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E03B'; -} -.vs .show-file-icons .hxp-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E03B'; -} -.vs .show-file-icons .hxml-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E03B'; -} -.vs - .show-file-icons - .spec\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E047'; -} -.vs .show-file-icons .jinja-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .jinja2-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E049'; -} -.vs .show-file-icons .jl-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E04C'; -} -.vs .show-file-icons .liquid-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E050'; -} -.vs .show-file-icons .ls-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E051'; -} -.vs .show-file-icons .njk-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .nunjucks-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .nunjs-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .nunj-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .njs-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .nj-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E05C'; -} -.vs - .show-file-icons - .npm-debug\.log-ext-file-icon.log-ext-file-icon.ext-file-icon.file-icon::before { - color: #3b4b52; - content: '\E05A'; -} -.vs .show-file-icons .npmignore-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .npmrc-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E05A'; -} -.vs .show-file-icons .ml-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .mli-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .cmx-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .cmxa-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E05D'; -} -.vs .show-file-icons .odata-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E05E'; -} -.vs .show-file-icons .pug-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E065'; -} -.vs .show-file-icons .pp-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .epp-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E066'; -} -.vs .show-file-icons .erb-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .erb\.html-ext-file-icon.html-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .html\.erb-ext-file-icon.erb-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E03F'; -} -.vs - .show-file-icons - .springbeans-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E076'; -} -.vs .show-file-icons .slim-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E074'; -} -.vs - .show-file-icons - .smarty\.tpl-ext-file-icon.tpl-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E075'; -} -.vs .show-file-icons .sbt-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E06F'; -} -.vs .show-file-icons .scala-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E070'; -} -.vs .show-file-icons .sol-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E027'; -} -.vs .show-file-icons .styl-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E077'; -} -.vs .show-file-icons .tf-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .tf\.json-ext-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .tfvars-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E07B'; -} -.vs .show-file-icons .tex-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E07C'; -} -.vs .show-file-icons .sty-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E07C'; -} -.vs .show-file-icons .dtx-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E07C'; -} -.vs .show-file-icons .ins-ext-file-icon.ext-file-icon.file-icon::before { - color: #bfc2c1; - content: '\E07C'; -} -.vs .show-file-icons .twig-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E07F'; -} -.vs - .show-file-icons - .spec\.ts-ext-file-icon.ts-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E080'; -} -.vs .show-file-icons .vala-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .vapi-ext-file-icon.ext-file-icon.file-icon::before { - color: #627379; - content: '\E081'; -} -.vs .show-file-icons .vue-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E083'; -} -.vs .show-file-icons .jar-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E08C'; -} -.vs .show-file-icons .zip-ext-file-icon.ext-file-icon.file-icon::before { - color: #627379; - content: '\E08C'; -} -.vs .show-file-icons .wgt-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E085'; -} -.vs .show-file-icons .ai-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E041'; -} -.vs .show-file-icons .psd-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E061'; -} -.vs .show-file-icons .pdf-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E05F'; -} -.vs .show-file-icons .eot-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .ttf-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .woff-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .woff2-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E02D'; -} -.vs .show-file-icons .gif-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .jpg-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .jpeg-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .png-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .pxm-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .svgx-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E042'; -} -.vs .show-file-icons .svg-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E079'; -} -.vs - .show-file-icons - .sublime-project-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .sublime-workspace-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E078'; -} -.vs .show-file-icons .component-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .cls-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E06D'; -} -.vs .show-file-icons .mov-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .ogv-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .webm-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .avi-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .mpg-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .mp4-ext-file-icon.ext-file-icon.file-icon::before { - color: #dd4b78; - content: '\E082'; -} -.vs .show-file-icons .mp3-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .ogg-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .wav-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E004'; -} -.vs .show-file-icons .babelrc-ext-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E005'; -} -.vs .show-file-icons .bowerrc-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .bower\.json-name-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E006'; -} -.vs - .show-file-icons - .dockerignore-ext-file-icon.ext-file-icon.file-icon::before { - color: #455155; - content: '\E01F'; -} -.vs - .show-file-icons - .codeclimate\.yml-ext-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E011'; -} -.vs .show-file-icons .eslintrc-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .eslintrc\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .eslintrc\.yaml-ext-file-icon.yaml-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .eslintrc\.yml-ext-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .eslintrc\.json-ext-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E026'; -} -.vs - .show-file-icons - .eslintignore-ext-file-icon.ext-file-icon.file-icon::before { - color: #455155; - content: '\E026'; -} -.vs .show-file-icons .firebaserc-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .firebase\.json-name-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E02A'; -} -.vs .show-file-icons .jshintrc-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .jscsrc-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E047'; -} -.vs .show-file-icons .tmp-ext-file-icon.ext-file-icon.file-icon::before { - color: #627379; - content: '\E00F'; -} -.vs .show-file-icons .key-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .cert-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E052'; -} -.vs .show-file-icons .ds_store-ext-file-icon.ext-file-icon.file-icon::before { - color: #3b4b52; - content: '\E040'; -} -.vs .show-file-icons .mix-name-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E03D'; -} -.vs - .show-file-icons - .karma\.conf\.js-name-file-icon.conf\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .karma\.conf\.coffee-name-file-icon.conf\.coffee-ext-file-icon.coffee-ext-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E04D'; -} -.vs - .show-file-icons - .readme\.md-name-file-icon.md-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E043'; -} -.vs - .show-file-icons - .changelog\.md-name-file-icon.md-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .changelog-name-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .version\.md-name-file-icon.md-ext-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .version-name-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E00F'; -} -.vs .show-file-icons .mvnw-name-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E056'; -} -.vs - .show-file-icons - .jenkinsfile-name-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E048'; -} -.vs - .show-file-icons - .docker-healthcheck-name-file-icon.ext-file-icon.file-icon::before { - color: #7fae42; - content: '\E01F'; -} -.vs - .show-file-icons - .docker-compose\.yml-name-file-icon.yml-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .docker-compose\.yaml-name-file-icon.yaml-ext-file-icon.ext-file-icon.file-icon::before { - color: #dd4b78; - content: '\E01F'; -} -.vs - .show-file-icons - .geckodriver-name-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E02B'; -} -.vs - .show-file-icons - .gruntfile\.js-name-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .gruntfile\.babel\.js-name-file-icon.babel\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .gruntfile\.coffee-name-file-icon.coffee-ext-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E036'; -} -.vs .show-file-icons .gulpfile-name-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E037'; -} -.vs - .show-file-icons - .ionic\.config\.json-name-file-icon.config\.json-ext-file-icon.json-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .ionic\.project-name-file-icon.project-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E044'; -} -.vs - .show-file-icons - .rollup\.config\.js-name-file-icon.config\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E06A'; -} -.vs - .show-file-icons - .yarn\.clean-name-file-icon.clean-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .yarn\.lock-name-file-icon.lock-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E08A'; -} -.vs - .show-file-icons - .webpack\.config\.js-name-file-icon.config\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before, -.vs - .show-file-icons - .webpack\.config\.build\.js-name-file-icon.config\.build\.js-ext-file-icon.build\.js-ext-file-icon.js-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E084'; -} -.vs .show-file-icons .license-name-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .licence-name-file-icon.ext-file-icon.file-icon::before, -.vs .show-file-icons .copying-name-file-icon.ext-file-icon.file-icon::before { - color: #b7b73b; - content: '\E04F'; -} -.vs .show-file-icons .compiling-name-file-icon.ext-file-icon.file-icon::before { - color: #cc6d2e; - content: '\E04F'; -} -.vs - .show-file-icons - .contributing-name-file-icon.ext-file-icon.file-icon::before { - color: #b8383d; - content: '\E04F'; -} -.vs .show-file-icons .qmakefile-name-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E054'; -} -.vs .show-file-icons .omakefile-name-file-icon.ext-file-icon.file-icon::before { - color: #627379; - content: '\E054'; -} -.vs - .show-file-icons - .cmakelists\.txt-name-file-icon.txt-ext-file-icon.ext-file-icon.file-icon::before { - color: #498ba7; - content: '\E054'; -} -.vs .show-file-icons .procfile-name-file-icon.ext-file-icon.file-icon::before { - color: #9068b0; - content: '\E03C'; -} -.vs - .show-file-icons - .npm-debug\.log-name-file-icon.log-ext-file-icon.ext-file-icon.file-icon::before { - color: #3b4b52; - content: '\E05B'; -} diff --git a/packages/app/src/app/components/CodeEditor/VSCode/index.tsx b/packages/app/src/app/components/CodeEditor/VSCode/index.tsx deleted file mode 100644 index c28b1a734ba..00000000000 --- a/packages/app/src/app/components/CodeEditor/VSCode/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import './icon-theme.css'; -import './workbench-theme.css'; - -import getTemplate from '@codesandbox/common/lib/templates'; -import getUI from '@codesandbox/common/lib/templates/configuration/ui'; -import theme from '@codesandbox/common/lib/theme'; -import { useActions, useAppState, useEffects } from 'app/overmind'; -import { json } from 'overmind'; -import React, { useEffect, useRef } from 'react'; -import { render } from 'react-dom'; -import { ThemeProvider } from 'styled-components'; - -import { Configuration } from './Configuration'; -import { Container, GlobalStyles } from './elements'; - -export const VSCode: React.FunctionComponent = () => { - const state = useAppState(); - const actions = useActions(); - const effects = useEffects(); - const containerEl = useRef(null); - - const getCurrentModule = React.useCallback( - () => state.editor.currentModule, - [] // eslint-disable-line - ); - const currentSandboxTemplate = state.editor.currentSandbox?.template; - useEffect(() => { - const rootEl = containerEl.current; - const mainContainer = effects.vscode.getEditorElement( - (modulePath: string) => { - if (!state.editor.currentSandbox) { - return false; - } - - const template = getTemplate(state.editor.currentSandbox.template); - const config = template.configurationFiles[modulePath]; - - const ui = config && getUI(config.type); - return ( - ui && - ui.ConfigWizard && - ((container, extraProps) => - render( - - - actions.editor.codeChanged({ code, moduleShortid }) - } - // Copy the object, we don't want mutations in the component - currentModule={json(getCurrentModule())} - config={config} - sandbox={state.editor.currentSandbox} - {...(extraProps as any)} - /> - , - container - )) - ); - } - ); - - rootEl.appendChild(mainContainer); - const { width, height } = rootEl.getBoundingClientRect(); - effects.vscode.updateLayout(width, height); - - document.getElementById('root').classList.add('monaco-shell'); - - return () => { - document.getElementById('root').classList.remove('monaco-shell'); - }; - }, [ - actions.editor, - effects.vscode, - state.editor.currentSandbox, - currentSandboxTemplate, - getCurrentModule, - ]); - - return ( - - - - ); -}; diff --git a/packages/app/src/app/components/CodeEditor/VSCode/workbench-theme.css b/packages/app/src/app/components/CodeEditor/VSCode/workbench-theme.css deleted file mode 100644 index 157d3552c4d..00000000000 --- a/packages/app/src/app/components/CodeEditor/VSCode/workbench-theme.css +++ /dev/null @@ -1,241 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench { - font-size: 13px; - line-height: 1.4em; - position: relative; - z-index: 1; - /* overflow: hidden; Removed for allowing menus to overlay eg. preview */ - - width: 100%; - height: 100%; -} - -.monaco-workbench > .part { - position: absolute; - box-sizing: border-box; -} - -.monaco-workbench - .part.editor - > .content - .editor-group-container - > .title - .tabs-container - > .tab.sizing-fit { - min-width: -moz-fit-content; -} - -.monaco-font-aliasing-antialiased { - -webkit-font-smoothing: antialiased; -} - -.monaco-font-aliasing-none { - -webkit-font-smoothing: none; -} - -@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { - .monaco-font-aliasing-auto { - -webkit-font-smoothing: antialiased; - } -} - -/* Taken from shell.css */ - -/* .monaco-shell { - height: 100%; - width: 100%; - margin: 0; - padding: 0; - overflow: hidden; - font-size: 11px; - user-select: none; -} */ - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -/* .monaco-shell img { - border: 0; -} */ - -.monaco-shell label { - cursor: pointer; -} - -.monaco-shell a { - text-decoration: none; -} - -.monaco-shell a:active { - color: inherit; - background-color: inherit; -} - -.monaco-shell a.plain { - color: inherit; - text-decoration: none; -} - -.monaco-shell a.plain:hover, -.monaco-shell a.plain.hover { - color: inherit; - text-decoration: none; -} - -.monaco-shell input { - /* color: inherit; */ - font-family: inherit; - /* font-size: 100%; */ -} - -.monaco-shell select { - font-family: inherit; -} - -.monaco-shell .pointer { - cursor: pointer; -} - -.monaco-shell .context-view { - -webkit-app-region: no-drag; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical { - padding: 0.5em 0; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-menu-item { - height: 1.8em; -} - -.monaco-shell - .monaco-menu - .monaco-action-bar.vertical - .action-label:not(.separator), -.monaco-shell .monaco-menu .monaco-action-bar.vertical .keybinding { - font-size: inherit; - padding: 0 2em; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .menu-item-check { - font-size: inherit; - width: 2em; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label.separator { - font-size: inherit; - padding: 0.2em 0 0 0; - margin-bottom: 0.2em; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .submenu-indicator { - font-size: 60%; - padding: 0 1.8em; -} - -.monaco-shell - .linux - .monaco-menu - .monaco-action-bar.vertical - .submenu-indicator { - height: 100%; - -webkit-mask-size: 10px 10px; - mask-size: 10px 10px; -} - -.monaco-shell .monaco-menu .action-item { - cursor: default; -} - -/* START Keyboard Focus Indication Styles */ - -.monaco-shell [tabindex='0']:focus, -.monaco-shell .synthetic-focus, -.monaco-shell select:focus, -.monaco-shell input[type='button']:focus, -.monaco-shell input[type='text']:focus, -.monaco-shell textarea:focus, -.monaco-shell input[type='checkbox']:focus { - outline-width: 1px; - outline-style: solid; - outline-offset: -1px; - opacity: 1 !important; -} - -.monaco-shell [tabindex='0']:active, -.monaco-shell select:active, -.monaco-shell input[type='button']:active, -.monaco-shell input[type='checkbox']:active, -.monaco-shell - .monaco-tree - .monaco-tree-row - .monaco-shell - .monaco-tree.focused.no-focused-item:active:before { - outline: 0 !important; /* fixes some flashing outlines from showing up when clicking */ -} - -.monaco-shell .mac select:focus { - border-color: transparent; /* outline is a square, but border has a radius, so we avoid this glitch when focused (https://github.com/Microsoft/vscode/issues/26045) */ -} - -.monaco-shell - .monaco-tree.focused - .monaco-tree-row.focused - [tabindex='0']:focus { - outline-width: 1px; /* higher contrast color for focusable elements in a row that shows focus feedback */ - outline-style: solid; -} - -.monaco-shell .monaco-tree.focused.no-focused-item:focus:before, -.monaco-shell .monaco-list:not(.element-focused):focus:before { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 5; /* make sure we are on top of the tree items */ - content: ''; - pointer-events: none; /* enable click through */ - outline: 1px solid; /* we still need to handle the empty tree or no focus item case */ - outline-width: 1px; - outline-style: solid; - outline-offset: -1px; -} - -.monaco-shell .synthetic-focus :focus { - outline: 0 !important; /* elements within widgets that draw synthetic-focus should never show focus */ -} - -.monaco-shell .monaco-inputbox.info.synthetic-focus, -.monaco-shell .monaco-inputbox.warning.synthetic-focus, -.monaco-shell .monaco-inputbox.error.synthetic-focus, -.monaco-shell .monaco-inputbox.info input[type='text']:focus, -.monaco-shell .monaco-inputbox.warning input[type='text']:focus, -.monaco-shell .monaco-inputbox.error input[type='text']:focus { - outline: 0 !important; /* outline is not going well with decoration */ -} - -.monaco-shell .monaco-tree.focused:focus, -.monaco-shell .monaco-list:focus { - outline: 0 !important; /* tree indicates focus not via outline but through the focused item */ -} - -/* CodeSandbox Change */ -.context-view.monaco-menu-container { - font-size: 0.75rem; -} - -/* Fixes https://github.com/codesandbox/codesandbox-client/issues/3882 */ -.monaco-editor .parameter-hints-widget { - overflow-y: scroll; -} diff --git a/packages/app/src/app/components/CodeEditor/index.tsx b/packages/app/src/app/components/CodeEditor/index.tsx deleted file mode 100644 index 305460db92c..00000000000 --- a/packages/app/src/app/components/CodeEditor/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable react/no-unused-state */ -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; -import { getModulePath } from '@codesandbox/common/lib/sandbox/modules'; -import getDefinition from '@codesandbox/common/lib/templates'; -import getUI from '@codesandbox/common/lib/templates/configuration/ui'; -import React from 'react'; -import QuestionIcon from 'react-icons/lib/go/question'; - -import { Icons } from './elements'; -import { Props } from './types'; // eslint-disable-line -import { VSCode } from './VSCode'; - -type State = { - showConfigUI: boolean; -}; - -export class CodeEditor extends React.PureComponent< - Props & { - editor?: 'vscode' | 'monaco' | 'codemirror'; - style?: React.CSSProperties; - }, - State -> { - state = { - showConfigUI: true, - }; - - toggleConfigUI = () => { - this.setState(state => ({ showConfigUI: !state.showConfigUI })); - }; - - render() { - const { props } = this; - - const { isModuleSynced, sandbox, currentModule: module } = props; - - const template = getDefinition(sandbox.template); - const modulePath = getModulePath( - sandbox.modules, - sandbox.directories, - module.id - ); - const config = template.configurationFiles[modulePath]; - - return ( -
- {!isModuleSynced(module.shortid) && module.title === 'index.html' && ( - - You may have to save this file and refresh the preview to see - changes - - )} - {config && getUI(config.type) ? ( - - {config.partialSupportDisclaimer ? ( - - Partially Supported Config{' '} - - - ) : ( -
Supported Configuration
- )} -
- ) : null} - -
- ); - } -} diff --git a/packages/app/src/app/components/Create/CreateBox.tsx b/packages/app/src/app/components/Create/CreateBox.tsx index bf3361d4335..67d66a7abfc 100644 --- a/packages/app/src/app/components/Create/CreateBox.tsx +++ b/packages/app/src/app/components/Create/CreateBox.tsx @@ -12,8 +12,6 @@ import React, { useState, useEffect } from 'react'; import track from '@codesandbox/common/lib/utils/analytics'; import { sandboxUrl } from '@codesandbox/common/lib/utils/url-generator'; -import { useBetaSandboxEditor } from 'app/hooks/useBetaSandboxEditor'; - import { ModalContentProps } from 'app/pages/common/Modals'; import { useGlobalPersistedState } from 'app/hooks/usePersistedState'; @@ -143,13 +141,10 @@ export const CreateBox: React.FC = ({ } }, [searchQuery]); - const [hasBetaEditorExperiment] = useBetaSandboxEditor(); - const selectTemplate = (sandbox: SandboxToFork) => { if (!hasLogIn) { // Open template in editor for anonymous users - window.location.href = - sandbox.editorUrl || sandboxUrl(sandbox, hasBetaEditorExperiment); + window.location.href = sandbox.editorUrl || sandboxUrl(sandbox); return; } @@ -163,8 +158,7 @@ export const CreateBox: React.FC = ({ }; const openTemplate = (sandbox: SandboxToFork) => { - const url = - sandbox.editorUrl || sandboxUrl(sandbox, hasBetaEditorExperiment); + const url = sandbox.editorUrl || sandboxUrl(sandbox); window.open(url, '_blank'); track(`Create - Open template`, { @@ -230,7 +224,7 @@ export const CreateBox: React.FC = ({ ( initialCollectionId ); - const [hasBetaEditorExperiment] = useBetaSandboxEditor(); const [autoLaunchVSCode] = useGlobalPersistedState( 'AUTO_LAUNCH_VSCODE', false @@ -414,13 +407,12 @@ const CreateBoxConfig: React.FC<{ setLoading(true); - actions.editor - .forkExternalSandbox({ + actions.dashboard + .forkSandbox({ sandboxId, openInNewWindow: false, openInVSCode, autoLaunchVSCode, - hasBetaEditorExperiment, customVMTier, redirectAfterFork: !isStandalone, body: { @@ -438,7 +430,7 @@ const CreateBoxConfig: React.FC<{ data: { id: forkedSandbox.id, alias: forkedSandbox.alias, - url: sandboxUrl(forkedSandbox, hasBetaEditorExperiment), + url: sandboxUrl(forkedSandbox), }, }, '*' diff --git a/packages/app/src/app/components/Create/CreateRepoFiles.tsx b/packages/app/src/app/components/Create/CreateRepoFiles.tsx index 30cc74054a4..d37abae7d4e 100644 --- a/packages/app/src/app/components/Create/CreateRepoFiles.tsx +++ b/packages/app/src/app/components/Create/CreateRepoFiles.tsx @@ -7,7 +7,7 @@ export function CreateRepoFiles() { useEffect(() => { window.addEventListener('message', event => { if (event.data.type === 'create-repo-files') { - actions.git + actions .createRepoFiles(event.data.sandbox) .then(data => { window.parent.postMessage( diff --git a/packages/app/src/app/components/Create/ImportRepository/components/AccountSelect.tsx b/packages/app/src/app/components/Create/ImportRepository/components/AccountSelect.tsx index 6cd24db3cf7..8f9a4a785e4 100644 --- a/packages/app/src/app/components/Create/ImportRepository/components/AccountSelect.tsx +++ b/packages/app/src/app/components/Create/ImportRepository/components/AccountSelect.tsx @@ -26,7 +26,7 @@ export const AccountSelect = ({ <> diff --git a/packages/app/src/app/components/Create/ImportRepository/steps/ConfigureRepo.tsx b/packages/app/src/app/components/Create/ImportRepository/steps/ConfigureRepo.tsx index 06902b0bf47..400334d5405 100644 --- a/packages/app/src/app/components/Create/ImportRepository/steps/ConfigureRepo.tsx +++ b/packages/app/src/app/components/Create/ImportRepository/steps/ConfigureRepo.tsx @@ -186,7 +186,7 @@ export const ConfigureRepo: React.FC = ({ setSelectedTier(parseInt(e.target.value, 10))} diff --git a/packages/app/src/app/components/Create/ImportRepository/steps/SearchInOrganizations.tsx b/packages/app/src/app/components/Create/ImportRepository/steps/SearchInOrganizations.tsx index 3e7ee70881e..2009a09b4b3 100644 --- a/packages/app/src/app/components/Create/ImportRepository/steps/SearchInOrganizations.tsx +++ b/packages/app/src/app/components/Create/ImportRepository/steps/SearchInOrganizations.tsx @@ -187,7 +187,7 @@ export const SearchInOrganizations: React.FC = ({ No repository matching the search. Please double check the repository name or try the{' '} Find by URL diff --git a/packages/app/src/app/components/Create/TemplateCard.tsx b/packages/app/src/app/components/Create/TemplateCard.tsx index 72629a88a4b..ef5453323a6 100644 --- a/packages/app/src/app/components/Create/TemplateCard.tsx +++ b/packages/app/src/app/components/Create/TemplateCard.tsx @@ -31,7 +31,7 @@ export const TemplateCard = ({ return ( { if (disabled) { diff --git a/packages/app/src/app/components/Create/elements.tsx b/packages/app/src/app/components/Create/elements.tsx index 29d37d1ec1e..6d58bffd132 100644 --- a/packages/app/src/app/components/Create/elements.tsx +++ b/packages/app/src/app/components/Create/elements.tsx @@ -1,8 +1,9 @@ import styled, { keyframes } from 'styled-components'; import React, { ReactNode } from 'react'; import { Tab as BaseTab, TabList, TabPanel, TabStateReturn } from 'reakit/Tab'; +import { Element } from '@codesandbox/components'; -export const Container = styled.div` +export const Container = styled(Element)` height: 600px; overflow: hidden; border-radius: 4px; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons/elements.ts b/packages/app/src/app/components/EntryIcons/elements.ts similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons/elements.ts rename to packages/app/src/app/components/EntryIcons/elements.ts diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons/getIconURL.ts b/packages/app/src/app/components/EntryIcons/getIconURL.ts similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons/getIconURL.ts rename to packages/app/src/app/components/EntryIcons/getIconURL.ts diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons/index.tsx b/packages/app/src/app/components/EntryIcons/index.tsx similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons/index.tsx rename to packages/app/src/app/components/EntryIcons/index.tsx diff --git a/packages/app/src/app/components/GitProgress/elements.ts b/packages/app/src/app/components/GitProgress/elements.ts deleted file mode 100644 index 03302724c04..00000000000 --- a/packages/app/src/app/components/GitProgress/elements.ts +++ /dev/null @@ -1,55 +0,0 @@ -import styled, { css, keyframes } from 'styled-components'; -import delayInEffect from '@codesandbox/common/lib/utils/animation/delay-effect'; -import { OpaqueLogo as BaseOpaqueLogo } from 'app/components/OpaqueLogo'; -import { GitHubLogo as BaseGitHubLogo } from 'app/components/GitHubLogo'; -import { Cube as BaseCube } from '../Cube'; - -export const DeployAnimationContainer = styled.div<{ deploying: boolean }>` - ${({ deploying }) => css` - display: flex; - justify-content: center; - align-items: center; - padding: 3rem; - ${deploying && delayInEffect(0, false)}; - `} -`; - -export const GitHubLogo = styled(BaseGitHubLogo)` - position: absolute; - color: white; - font-size: 4rem; - transform: translateY(10px) translateX(80px); -`; - -const cubeAnimation = keyframes` - 0% { - transform: translateY(20px) translateX(-100px) scale(0, 0); - } - - 20% { - transform: translateY(20px) translateX(-100px) scale(1, 1); - } - - 80% { - transform: translateY(20px) translateX(80px) scale(1, 1); - } - - 100% { - transform: translateY(20px) translateX(80px) scale(1, 1); - } -`; - -export const Cube = styled(BaseCube)<{ delay: number }>` - ${({ delay }) => css` - position: absolute; - animation: ${cubeAnimation} 2s ease-in infinite; - animation-delay: ${delay * 0.5}s; - transform: translateY(20px) translateX(-100px) scale(0, 0); - `} -`; - -export const OpaqueLogo = styled(BaseOpaqueLogo)` - position: absolute; - z-index: 10; - transform: translateY(15px) translateX(-100px); -`; diff --git a/packages/app/src/app/components/GitProgress/index.tsx b/packages/app/src/app/components/GitProgress/index.tsx deleted file mode 100644 index 192ee801eae..00000000000 --- a/packages/app/src/app/components/GitProgress/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { FunctionComponent, ReactNode } from 'react'; -import { Element, Text } from '@codesandbox/components'; -import { - Cube, - DeployAnimationContainer, - GitHubLogo, - OpaqueLogo, -} from './elements'; - -type Props = { - message: string; - result: ReactNode; -}; -export const GitProgress: FunctionComponent = ({ message, result }) => ( - - {result ? ( - - {result} - - ) : ( - <> - - - - {[0, 1, 2, 3].map(i => ( - - ))} - - - - - {message} - - - )} - -); diff --git a/packages/app/src/app/components/Markdown/Code.tsx b/packages/app/src/app/components/Markdown/Code.tsx deleted file mode 100644 index 57540c5c7f6..00000000000 --- a/packages/app/src/app/components/Markdown/Code.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import Highlight, { defaultProps } from 'prism-react-renderer'; -import { Element } from '@codesandbox/components'; -import css from '@styled-system/css'; -import { useAppState } from 'app/overmind'; -import { withTheme } from 'styled-components'; -import { makeTheme } from '@codesandbox/common/lib/utils/makeTheme'; - -export const Code = withTheme(({ value, language, theme }) => { - const state = useAppState(); - - const defaultLanguage = () => { - const template = state.editor.currentSandbox.template; - if (template === 'create-react-app') { - return 'jsx'; - } - return 'js'; - }; - return value ? ( - - {({ className, style, tokens, getLineProps, getTokenProps }) => ( - - {tokens.map((line, i) => ( - - {line.map((token, key) => ( - - ))} - - ))} - - )} - - ) : null; -}); diff --git a/packages/app/src/app/components/Markdown/Image.tsx b/packages/app/src/app/components/Markdown/Image.tsx deleted file mode 100644 index f0ca606cd92..00000000000 --- a/packages/app/src/app/components/Markdown/Image.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useState } from 'react'; -import { useAppState } from 'app/overmind'; -import Modal from 'react-modal'; -import { Text, Element, Button, IconButton } from '@codesandbox/components'; -import css from '@styled-system/css'; - -export const Image: React.FC<{ - src: string; - alt: string; - ignorePrivateSandboxRestriction?: boolean; -}> = props => { - const state = useAppState(); - const [modalOpen, setModalOpen] = useState(false); - const privateSandbox = - state.editor.currentSandbox.privacy === 1 || - state.editor.currentSandbox.privacy === 2; - return props.ignorePrivateSandboxRestriction || privateSandbox ? ( - <> - - setModalOpen(false)} - > - setModalOpen(false)} - css={{ - position: 'absolute', - right: 8, - top: 8, - color: 'white', - backgroundColor: 'rgba(0, 0, 0, 0.4)', - }} - /> - {props.alt} - - - ) : ( - - - Images are not shown in public sandboxes - - - ); -}; diff --git a/packages/app/src/app/components/Markdown/InlineCode.tsx b/packages/app/src/app/components/Markdown/InlineCode.tsx deleted file mode 100644 index d6a376e4e09..00000000000 --- a/packages/app/src/app/components/Markdown/InlineCode.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { Text, Element } from '@codesandbox/components'; -import css from '@styled-system/css'; - -export const InlineCode = props => ( - - - {props.children} - - -); diff --git a/packages/app/src/app/components/Markdown/Link.tsx b/packages/app/src/app/components/Markdown/Link.tsx deleted file mode 100644 index 03445cd39fe..00000000000 --- a/packages/app/src/app/components/Markdown/Link.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Button, Link, Text } from '@codesandbox/components'; -import css from '@styled-system/css'; -import { useAppState, useActions } from 'app/overmind'; -import React from 'react'; - -export const LinkElement = ({ href, children, ...props }) => { - let commentId = null; - const state = useAppState(); - const actions = useActions(); - const { id, alias } = state.editor.currentSandbox; - - try { - commentId = new URLSearchParams(new URL(href).search).get('comment'); - } catch { - commentId = null; - } - - if (!children.length) { - return {href}; - } - - if (href.startsWith('user://')) { - return ( - event.stopPropagation()} - > - {children[0].props.children} - - ); - } - - if ( - href.includes(window.location.href) && - (href.includes(id) || href.includes(alias)) && - commentId - ) { - return ( - - ); - } - - if (!href.includes('codesandbox')) { - return ( - - {children[0].props.children} - - ); - } - - return ( - - {children[0].props.children} - - ); -}; diff --git a/packages/app/src/app/components/Markdown/index.tsx b/packages/app/src/app/components/Markdown/index.tsx deleted file mode 100644 index fbd09894d73..00000000000 --- a/packages/app/src/app/components/Markdown/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Element, Link, Text } from '@codesandbox/components'; -import css from '@styled-system/css'; -import React from 'react'; -import ReactMarkdown from 'react-markdown'; - -import { Code } from './Code'; -import { Image } from './Image'; -import { InlineCode } from './InlineCode'; -import { LinkElement } from './Link'; - -export const Markdown = ({ source }) => ( - - ( - - {children} - - ), - heading: ({ children }) => ( - - {children} - - ), - blockquote: ({ children }) => ( - - {children} - - ), - code: Code, - link: LinkElement, - linkReference: props => {props.children}, - image: Image, - imageReference: Image, - thematicBreak: () => null, - inlineCode: InlineCode, - }} - > - {source ? source.replace(/\n/gi, ' \n\n') : source} - - -); diff --git a/packages/app/src/app/components/Notifications/Skeleton.js b/packages/app/src/app/components/Notifications/Skeleton.js index e92b16d7e47..7f5e36c55e5 100644 --- a/packages/app/src/app/components/Notifications/Skeleton.js +++ b/packages/app/src/app/components/Notifications/Skeleton.js @@ -1,7 +1,7 @@ import React from 'react'; import css from '@styled-system/css'; import { ListItem, Stack } from '@codesandbox/components'; -import { SkeletonTextBlock } from 'app/pages/Sandbox/Editor/Skeleton/elements'; +import { SkeletonTextBlock } from 'app/components/Skeleton/elements'; const NotificationSkeleton = () => ( - props.theme.light ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.4)'}; - width: 100%; - padding: 1rem; - border-bottom: 2px solid; - border-color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.3)' : 'rgba(255, 255, 255, 0.3)'}; -`; diff --git a/packages/app/src/app/components/Preview/DevTools/TerminalUpgrade/index.tsx b/packages/app/src/app/components/Preview/DevTools/TerminalUpgrade/index.tsx deleted file mode 100644 index 08d68602f3c..00000000000 --- a/packages/app/src/app/components/Preview/DevTools/TerminalUpgrade/index.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Button, ThemeProvider, Text, Stack } from '@codesandbox/components'; -import React from 'react'; -import themeType from '@codesandbox/common/lib/theme'; - -import { withTheme } from 'styled-components'; -import { useUpgradeFromV1ToV2 } from 'app/hooks/useUpgradeFromV1ToV2'; -import { DevToolProps } from '..'; -import { StyledTitle } from './elements'; - -type StyledProps = DevToolProps & { - theme: typeof themeType & { light: boolean }; -}; - -export const TerminalUpgradeComponent: React.FC = ({ - hidden, - theme, -}) => { - const { perform, loading, canConvert } = useUpgradeFromV1ToV2('Terminal Tab'); - - if (hidden) { - return null; - } - - return ( - - - Terminal - - - To use the terminal, you need to upgrade your Sandbox into a Devbox. - - - Devboxes are an improved coding experience that runs your code in - the cloud. They bring new languages, servers, databases, a built-in - AI assistant, and much more. See a preview below. - - - - {canConvert - ? `Do you want to convert it into a Devbox?` - : `Do you want to fork into a Devbox?`} - - - - - - - - - ); -}; - -export const terminalUpgrade = { - id: 'codesandbox.terminalUpgrade', - title: 'Terminal', - Content: withTheme(TerminalUpgradeComponent), - actions: [], -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Skeleton/elements.ts b/packages/app/src/app/components/Skeleton/elements.ts similarity index 96% rename from packages/app/src/app/pages/Sandbox/Editor/Skeleton/elements.ts rename to packages/app/src/app/components/Skeleton/elements.ts index eba1163d2dc..082d4bc068b 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Skeleton/elements.ts +++ b/packages/app/src/app/components/Skeleton/elements.ts @@ -1,12 +1,13 @@ import styled, { css, keyframes } from 'styled-components'; import Color from 'color'; +import { Element } from '@codesandbox/components'; const pulse = keyframes` 0% { background-position: 100% 50%; } 100% { background-position: -100% 50%; } `; -export const SkeletonTextBlock = styled.div(props => { +export const SkeletonTextBlock = styled(Element)(props => { let color = props.theme.colors?.sideBar.border || '#242424'; const themeType = props.theme.vscodeTheme.type; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Skeleton/index.tsx b/packages/app/src/app/components/Skeleton/index.tsx similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Skeleton/index.tsx rename to packages/app/src/app/components/Skeleton/index.tsx diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ModuleTab/elements.js b/packages/app/src/app/components/Tabs/ModuleTab/elements.js similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ModuleTab/elements.js rename to packages/app/src/app/components/Tabs/ModuleTab/elements.js diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ModuleTab/index.js b/packages/app/src/app/components/Tabs/ModuleTab/index.js similarity index 95% rename from packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ModuleTab/index.js rename to packages/app/src/app/components/Tabs/ModuleTab/index.js index 44c0e1045a0..529003012c3 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ModuleTab/index.js +++ b/packages/app/src/app/components/Tabs/ModuleTab/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import EntryIcons from 'app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/EntryIcons'; +import EntryIcons from 'app/components/EntryIcons'; // eslint-disable-next-line import/extensions import { getType } from 'app/utils/get-type.ts'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/Tab/elements.ts b/packages/app/src/app/components/Tabs/Tab/elements.ts similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/Tab/elements.ts rename to packages/app/src/app/components/Tabs/Tab/elements.ts diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/Tab/index.js b/packages/app/src/app/components/Tabs/Tab/index.js similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/Tab/index.js rename to packages/app/src/app/components/Tabs/Tab/index.js diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/TabContainer/index.js b/packages/app/src/app/components/Tabs/TabContainer/index.js similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/TabContainer/index.js rename to packages/app/src/app/components/Tabs/TabContainer/index.js diff --git a/packages/app/src/app/components/TeamAvatar/TeamAvatar.tsx b/packages/app/src/app/components/TeamAvatar/TeamAvatar.tsx index 09073e20530..2d3b2344564 100644 --- a/packages/app/src/app/components/TeamAvatar/TeamAvatar.tsx +++ b/packages/app/src/app/components/TeamAvatar/TeamAvatar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Stack, Text } from '@codesandbox/components'; +import { Element, Stack, Text } from '@codesandbox/components'; import css from '@styled-system/css'; const backgrounds = [ @@ -42,7 +42,8 @@ export const TeamAvatar = ({ const avatarSize = size === 'bigger' ? '55px' : '24px'; return avatar ? ( - = ({ - message, - result, -}) => ( - - {result ? ( - - {result} - - ) : ( - <> - - - {[0, 1, 2, 3].map(i => ( - - ))} - - - - {message} - - - )} - -); diff --git a/packages/app/src/app/components/UploadProgress/elements.ts b/packages/app/src/app/components/UploadProgress/elements.ts deleted file mode 100644 index b06f159d209..00000000000 --- a/packages/app/src/app/components/UploadProgress/elements.ts +++ /dev/null @@ -1,53 +0,0 @@ -import styled, { css, keyframes } from 'styled-components'; -import LocalLogo from 'react-icons/lib/md/laptop'; -import delayInEffect from '@codesandbox/common/lib/utils/animation/delay-effect'; -import { OpaqueLogo } from 'app/components/OpaqueLogo'; -import { Cube } from '../Cube'; - -export const UploadAnimationContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - padding: 3rem; - ${delayInEffect(0, false)} -`; - -export const StyledLocalLogo = styled(LocalLogo)` - position: absolute; - z-index: 20; - font-size: 4rem; - transform: translateY(15px) translateX(-100px); -`; - -const cubeAnimation = keyframes` -0% { - transform: translateY(20px) translateX(-100px) scale(0, 0); -} - -20% { - transform: translateY(20px) translateX(-100px) scale(1, 1); -} - -80% { - transform: translateY(20px) translateX(80px) scale(1, 1); -} - -100% { - transform: translateY(20px) translateX(80px) scale(1, 1); -} -`; - -export const StyledCube = styled(Cube)<{ delay: number }>` - ${({ delay }) => css` - position: absolute; - animation: ${cubeAnimation} 2s ease-in infinite; - animation-delay: ${delay * 0.5}s; - transform: translateY(20px) translateX(-100px) scale(0, 0); - `} -`; - -export const StyledLogo = styled(OpaqueLogo)` - position: absolute; - z-index: 10; - transform: translateY(10px) translateX(80px); -`; diff --git a/packages/app/src/app/components/UploadProgress/index.ts b/packages/app/src/app/components/UploadProgress/index.ts deleted file mode 100644 index 4881bdcf3c0..00000000000 --- a/packages/app/src/app/components/UploadProgress/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UploadProgress } from './UploadProgress'; diff --git a/packages/app/src/app/components/UserSearchInput/UserSearchInput.tsx b/packages/app/src/app/components/UserSearchInput/UserSearchInput.tsx deleted file mode 100644 index 020039234b8..00000000000 --- a/packages/app/src/app/components/UserSearchInput/UserSearchInput.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React, { useEffect } from 'react'; -import Downshift, { DownshiftProps } from 'downshift'; -import css from '@styled-system/css'; -import { Input, List, ListAction } from '@codesandbox/components'; -import { useAppState } from 'app/overmind'; - -type User = { - id: string; - username: string; - avatar_url: string; -}; - -interface IUserAutoComplete { - inputValue: string; - allowSelf?: boolean; - children: (answer: { - users: User[]; - loading: boolean; - error: Error | null; - }) => JSX.Element; -} - -const UserAutoComplete = ({ - inputValue, - children, - allowSelf = false, -}: IUserAutoComplete) => { - const [users, setUsers] = React.useState([]); - const [loading, setLoading] = React.useState(true); - const [error, setError] = React.useState(null); - const { user } = useAppState(); - - useEffect(() => { - setLoading(true); - setError(null); - - let timeoutId: number; - - if (inputValue.length > 2 && !inputValue.includes('@')) { - // Small debounce - timeoutId = window.setTimeout(() => { - fetch(`/api/v1/users/search?username=${inputValue}`) - .then(x => x.json()) - .then(x => { - const fetchedUsers = allowSelf - ? x - : x.filter(member => member.username !== user?.username); - setUsers(fetchedUsers); - setLoading(false); - }) - .catch(e => { - setError(e); - }); - }, 300); - } else { - setUsers([]); - } - - return () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - }; - }, [allowSelf, inputValue, user]); - - return children({ users, loading, error }); -}; - -interface IUserSearchInputProps { - onInputValueChange: DownshiftProps['onInputValueChange']; - inputValue: string; - allowSelf?: boolean; - [key: string]: any; -} - -// There is a conflict with 'as' typing (string vs 'select' | 'option', etc...) -const InputWithoutTypes = Input as any; - -export const UserSearchInput = ({ - onInputValueChange, - inputValue, - allowSelf = false, - ...props -}: IUserSearchInputProps) => ( - - {({ - getInputProps, - getItemProps, - getRootProps, - getMenuProps, - isOpen, - selectedItem, - inputValue: currentInputValue, - highlightedIndex, - }) => ( -
-
- -
- - - {({ users, error }) => - users.length > 0 && !error && isOpen ? ( - - {users.map((item, index) => ( - - {item.username}{' '} - {item.username} - - ))} - - ) : null - } - -
- )} -
-); diff --git a/packages/app/src/app/components/UserSearchInput/index.ts b/packages/app/src/app/components/UserSearchInput/index.ts deleted file mode 100644 index 299ad2b7a1a..00000000000 --- a/packages/app/src/app/components/UserSearchInput/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UserSearchInput } from './UserSearchInput'; diff --git a/packages/app/src/app/components/VercelLogo.tsx b/packages/app/src/app/components/VercelLogo.tsx deleted file mode 100644 index 28b294336c6..00000000000 --- a/packages/app/src/app/components/VercelLogo.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import IconBase from 'react-icons/lib/IconBase'; - -export const VercelLogo: React.FC> = props => ( - - - -); diff --git a/packages/app/src/app/components/WorkspaceSetup/elements.ts b/packages/app/src/app/components/WorkspaceSetup/elements.ts index f0ade105a35..f70bd02c52d 100644 --- a/packages/app/src/app/components/WorkspaceSetup/elements.ts +++ b/packages/app/src/app/components/WorkspaceSetup/elements.ts @@ -1,3 +1,4 @@ +import { Element } from '@codesandbox/components'; import styled, { keyframes, css } from 'styled-components'; export const fadeIn = keyframes` @@ -10,6 +11,6 @@ export const fadeAnimation = () => ${fadeIn} 0.2s ease-out; `; -export const AnimatedStep = styled.div` +export const AnimatedStep = styled(Element)` animation: ${fadeAnimation}; `; diff --git a/packages/app/src/app/components/dashboard/LargeCTAButton.tsx b/packages/app/src/app/components/dashboard/LargeCTAButton.tsx index aeb4b654c44..2b4bcd39a22 100644 --- a/packages/app/src/app/components/dashboard/LargeCTAButton.tsx +++ b/packages/app/src/app/components/dashboard/LargeCTAButton.tsx @@ -24,7 +24,7 @@ export const LargeCTAButton = ({ ; }; -export type CollaboratorFragment = { - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { __typename?: 'User'; id: any; username: string; avatarUrl: string }; -}; - -export type InvitationFragment = { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; -}; - -export type SandboxChangedFragment = { - __typename?: 'Sandbox'; - id: string; - privacy: number; - title: string | null; - description: string | null; - authorization: Authorization; -}; - -export type AddCollaboratorMutationVariables = Exact<{ - sandboxId: Scalars['ID']; - username: Scalars['String']; - authorization: Authorization; -}>; - -export type AddCollaboratorMutation = { - __typename?: 'RootMutationType'; - addCollaborator: { - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { __typename?: 'User'; id: any; username: string; avatarUrl: string }; - }; -}; - -export type RemoveCollaboratorMutationVariables = Exact<{ - sandboxId: Scalars['ID']; - username: Scalars['String']; -}>; - -export type RemoveCollaboratorMutation = { - __typename?: 'RootMutationType'; - removeCollaborator: { - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { __typename?: 'User'; id: any; username: string; avatarUrl: string }; - }; -}; - -export type ChangeCollaboratorAuthorizationMutationVariables = Exact<{ - sandboxId: Scalars['ID']; - username: Scalars['String']; - authorization: Authorization; -}>; - -export type ChangeCollaboratorAuthorizationMutation = { - __typename?: 'RootMutationType'; - changeCollaboratorAuthorization: { - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { __typename?: 'User'; id: any; username: string; avatarUrl: string }; - }; -}; - -export type InviteCollaboratorMutationVariables = Exact<{ - sandboxId: Scalars['ID']; - authorization: Authorization; - email: Scalars['String']; -}>; - -export type InviteCollaboratorMutation = { - __typename?: 'RootMutationType'; - createSandboxInvitation: { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }; -}; - -export type RevokeSandboxInvitationMutationVariables = Exact<{ - sandboxId: Scalars['ID']; - invitationId: Scalars['UUID4']; -}>; - -export type RevokeSandboxInvitationMutation = { - __typename?: 'RootMutationType'; - revokeSandboxInvitation: { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }; -}; - -export type ChangeSandboxInvitationAuthorizationMutationVariables = Exact<{ - sandboxId: Scalars['ID']; - invitationId: Scalars['UUID4']; - authorization: Authorization; -}>; - -export type ChangeSandboxInvitationAuthorizationMutation = { - __typename?: 'RootMutationType'; - changeSandboxInvitationAuthorization: { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }; -}; - -export type RedeemSandboxInvitationMutationVariables = Exact<{ - sandboxId: Scalars['ID']; - invitationToken: Scalars['String']; -}>; - -export type RedeemSandboxInvitationMutation = { - __typename?: 'RootMutationType'; - redeemSandboxInvitation: { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }; -}; - -export type SandboxCollaboratorsQueryVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type SandboxCollaboratorsQuery = { - __typename?: 'RootQueryType'; - sandbox: { - __typename?: 'Sandbox'; - collaborators: Array<{ - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { - __typename?: 'User'; - id: any; - username: string; - avatarUrl: string; - }; - }>; - } | null; -}; - -export type SandboxInvitationsQueryVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type SandboxInvitationsQuery = { - __typename?: 'RootQueryType'; - sandbox: { - __typename?: 'Sandbox'; - invitations: Array<{ - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }>; - } | null; -}; - -export type OnCollaboratorAddedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type OnCollaboratorAddedSubscription = { - __typename?: 'RootSubscriptionType'; - collaboratorAdded: { - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { __typename?: 'User'; id: any; username: string; avatarUrl: string }; - }; -}; - -export type OnCollaboratorChangedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type OnCollaboratorChangedSubscription = { - __typename?: 'RootSubscriptionType'; - collaboratorChanged: { - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { __typename?: 'User'; id: any; username: string; avatarUrl: string }; - }; -}; - -export type OnCollaboratorRemovedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type OnCollaboratorRemovedSubscription = { - __typename?: 'RootSubscriptionType'; - collaboratorRemoved: { - __typename?: 'Collaborator'; - id: any; - authorization: Authorization; - lastSeenAt: any | null; - warning: string | null; - user: { __typename?: 'User'; id: any; username: string; avatarUrl: string }; - }; -}; - -export type OnInvitationCreatedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type OnInvitationCreatedSubscription = { - __typename?: 'RootSubscriptionType'; - invitationCreated: { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }; -}; - -export type OnInvitationRemovedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type OnInvitationRemovedSubscription = { - __typename?: 'RootSubscriptionType'; - invitationRemoved: { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }; -}; - -export type OnInvitationChangedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type OnInvitationChangedSubscription = { - __typename?: 'RootSubscriptionType'; - invitationChanged: { - __typename?: 'Invitation'; - id: string | null; - authorization: Authorization; - email: string | null; - }; -}; - -export type OnSandboxChangedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type OnSandboxChangedSubscription = { - __typename?: 'RootSubscriptionType'; - sandboxChanged: { - __typename?: 'Sandbox'; - id: string; - privacy: number; - title: string | null; - description: string | null; - authorization: Authorization; - }; -}; - -export type CodeReferenceMetadataFragment = { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; -}; - -export type UserReferenceMetadataFragment = { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; -}; - -export type ImageReferenceMetadataFragment = { - __typename?: 'ImageReferenceMetadata'; - fileName: string; -}; - -export type PreviewReferenceMetadataFragment = { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; -}; - -export type CommentFragment = { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; -}; - -export type CommentWithRepliesFragment = { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - comments: Array<{ - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }>; -}; - -export type CreateCommentMutationVariables = Exact<{ - id: InputMaybe; - content: Scalars['String']; - sandboxId: Scalars['ID']; - parentCommentId: InputMaybe; - userReferences: InputMaybe | UserReference>; - codeReferences: InputMaybe | CodeReference>; - imageReferences: InputMaybe | ImageReference>; -}>; - -export type CreateCommentMutation = { - __typename?: 'RootMutationType'; - createComment: { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }; -}; - -export type CreateCodeCommentMutationVariables = Exact<{ - id: InputMaybe; - content: Scalars['String']; - sandboxId: Scalars['ID']; - parentCommentId: InputMaybe; - anchorReference: CodeReference; - userReferences: InputMaybe | UserReference>; - codeReferences: InputMaybe | CodeReference>; - imageReferences: InputMaybe | ImageReference>; -}>; - -export type CreateCodeCommentMutation = { - __typename?: 'RootMutationType'; - createCodeComment: { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }; -}; - -export type CreatePreviewCommentMutationVariables = Exact<{ - id: InputMaybe; - content: Scalars['String']; - sandboxId: Scalars['ID']; - parentCommentId: InputMaybe; - anchorReference: PreviewReference; - userReferences: InputMaybe | UserReference>; - codeReferences: InputMaybe | CodeReference>; - imageReferences: InputMaybe | ImageReference>; -}>; - -export type CreatePreviewCommentMutation = { - __typename?: 'RootMutationType'; - createPreviewComment: { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }; -}; - -export type DeleteCommentMutationVariables = Exact<{ - commentId: Scalars['UUID4']; - sandboxId: Scalars['ID']; -}>; - -export type DeleteCommentMutation = { - __typename?: 'RootMutationType'; - deleteComment: { __typename?: 'Comment'; id: any }; -}; - -export type UpdateCommentMutationVariables = Exact<{ - commentId: Scalars['UUID4']; - sandboxId: Scalars['ID']; - content: InputMaybe; - userReferences: InputMaybe | UserReference>; - codeReferences: InputMaybe | CodeReference>; - imageReferences: InputMaybe | ImageReference>; -}>; - -export type UpdateCommentMutation = { - __typename?: 'RootMutationType'; - updateComment: { __typename?: 'Comment'; id: any }; -}; - -export type ResolveCommentMutationVariables = Exact<{ - commentId: Scalars['UUID4']; - sandboxId: Scalars['ID']; -}>; - -export type ResolveCommentMutation = { - __typename?: 'RootMutationType'; - resolveComment: { __typename?: 'Comment'; id: any }; -}; - -export type UnresolveCommentMutationVariables = Exact<{ - commentId: Scalars['UUID4']; - sandboxId: Scalars['ID']; -}>; - -export type UnresolveCommentMutation = { - __typename?: 'RootMutationType'; - unresolveComment: { __typename?: 'Comment'; id: any }; -}; - -export type SandboxCommentQueryVariables = Exact<{ - sandboxId: Scalars['ID']; - commentId: Scalars['UUID4']; -}>; - -export type SandboxCommentQuery = { - __typename?: 'RootQueryType'; - sandbox: { - __typename?: 'Sandbox'; - comment: { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - comments: Array<{ - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }>; - } | null; - } | null; -}; - -export type SandboxCommentsQueryVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type SandboxCommentsQuery = { - __typename?: 'RootQueryType'; - sandbox: { - __typename?: 'Sandbox'; - comments: Array<{ - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }>; - } | null; -}; - -export type CommentAddedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type CommentAddedSubscription = { - __typename?: 'RootSubscriptionType'; - commentAdded: { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - sandbox: { __typename?: 'Sandbox'; id: string }; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }; -}; - -export type CommentChangedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type CommentChangedSubscription = { - __typename?: 'RootSubscriptionType'; - commentChanged: { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - sandbox: { __typename?: 'Sandbox'; id: string }; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }; -}; - -export type CommentRemovedSubscriptionVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type CommentRemovedSubscription = { - __typename?: 'RootSubscriptionType'; - commentRemoved: { - __typename?: 'Comment'; - id: any; - content: string | null; - insertedAt: any; - updatedAt: any; - isResolved: boolean; - replyCount: number; - sandbox: { __typename?: 'Sandbox'; id: string }; - anchorReference: { - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata' } - | { - __typename?: 'PreviewReferenceMetadata'; - width: number; - height: number; - x: number; - y: number; - screenshotUrl: string | null; - userAgent: string; - } - | { __typename?: 'UserReferenceMetadata' }; - } | null; - references: Array<{ - __typename?: 'Reference'; - id: any; - resource: string; - type: string; - metadata: - | { - __typename?: 'CodeReferenceMetadata'; - anchor: number; - code: string; - head: number; - path: string; - } - | { __typename?: 'ImageReferenceMetadata'; fileName: string } - | { __typename?: 'PreviewReferenceMetadata' } - | { - __typename?: 'UserReferenceMetadata'; - username: string; - userId: string; - }; - }>; - user: { - __typename?: 'User'; - id: any; - name: string | null; - username: string; - avatarUrl: string; - }; - parentComment: { __typename?: 'Comment'; id: any } | null; - }; -}; - export type SandboxFragmentDashboardFragment = { __typename?: 'Sandbox'; id: string; @@ -7569,86 +6357,6 @@ export type RejectTeamInvitationMutation = { rejectTeamInvitation: string; }; -export type BookmarkTemplateMutationVariables = Exact<{ - template: Scalars['UUID4']; - team: InputMaybe; -}>; - -export type BookmarkTemplateMutation = { - __typename?: 'RootMutationType'; - template: { - __typename?: 'Template'; - id: any | null; - bookmarked: Array<{ - __typename?: 'Bookmarked'; - isBookmarked: boolean | null; - entity: - | { __typename: 'Team'; id: any; name: string } - | { __typename: 'User'; id: any; name: string } - | null; - } | null> | null; - } | null; -}; - -export type UnbookmarkTemplateMutationVariables = Exact<{ - template: Scalars['UUID4']; - team: InputMaybe; -}>; - -export type UnbookmarkTemplateMutation = { - __typename?: 'RootMutationType'; - template: { - __typename?: 'Template'; - id: any | null; - bookmarked: Array<{ - __typename?: 'Bookmarked'; - isBookmarked: boolean | null; - entity: - | { __typename: 'Team'; id: any; name: string } - | { __typename: 'User'; id: any; name: string } - | null; - } | null> | null; - } | null; -}; - -export type BookmarkTemplateFieldsFragment = { - __typename?: 'Template'; - id: any | null; - bookmarked: Array<{ - __typename?: 'Bookmarked'; - isBookmarked: boolean | null; - entity: - | { __typename: 'Team'; id: any; name: string } - | { __typename: 'User'; id: any; name: string } - | null; - } | null> | null; -}; - -export type BookmarkedSandboxInfoQueryVariables = Exact<{ - sandboxId: Scalars['ID']; -}>; - -export type BookmarkedSandboxInfoQuery = { - __typename?: 'RootQueryType'; - sandbox: { - __typename?: 'Sandbox'; - id: string; - author: { __typename?: 'User'; id: any; name: string } | null; - customTemplate: { - __typename?: 'Template'; - id: any | null; - bookmarked: Array<{ - __typename?: 'Bookmarked'; - isBookmarked: boolean | null; - entity: - | { __typename: 'Team'; id: any; name: string } - | { __typename: 'User'; id: any; name: string } - | null; - } | null> | null; - } | null; - } | null; -}; - export type TeamByTokenQueryVariables = Exact<{ inviteToken: Scalars['String']; }>; diff --git a/packages/app/src/app/hooks/useBetaSandboxEditor.ts b/packages/app/src/app/hooks/useBetaSandboxEditor.ts deleted file mode 100644 index 1b4d7e3f094..00000000000 --- a/packages/app/src/app/hooks/useBetaSandboxEditor.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This will be removed in the first cleanup post release, but for now -// it is the simplesy way to ensure everyone is on the v2 editor -export const useBetaSandboxEditor = (): [boolean] => { - return [true]; -}; diff --git a/packages/app/src/app/hooks/useCurrencyFromTimeZone.ts b/packages/app/src/app/hooks/useCurrencyFromTimeZone.ts deleted file mode 100644 index ed2fb795c0e..00000000000 --- a/packages/app/src/app/hooks/useCurrencyFromTimeZone.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const useCurrencyFromTimeZone = () => { - const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - - if (timeZone === 'Asia/Kolkata' || timeZone === 'Asia/Calcutta') { - return 'INR'; - } - - return 'USD'; -}; diff --git a/packages/app/src/app/hooks/useIsEditorPage.ts b/packages/app/src/app/hooks/useIsEditorPage.ts deleted file mode 100644 index 2eb876c279b..00000000000 --- a/packages/app/src/app/hooks/useIsEditorPage.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { editorUrl } from '@codesandbox/common/lib/utils/url-generator'; -import { matchPath, useLocation } from 'react-router-dom'; - -export const useIsEditorPage = (): boolean => { - const { pathname } = useLocation(); - const match = matchPath(pathname, editorUrl()); - return match !== null; -}; diff --git a/packages/app/src/app/hooks/useUpgradeFromV1ToV2.ts b/packages/app/src/app/hooks/useUpgradeFromV1ToV2.ts deleted file mode 100644 index 8e17b39aa39..00000000000 --- a/packages/app/src/app/hooks/useUpgradeFromV1ToV2.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { useState } from 'react'; -import { useEffects, useAppState } from 'app/overmind'; -import { sandboxUrl } from '@codesandbox/common/lib/utils/url-generator'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import track from '@codesandbox/common/lib/utils/analytics'; - -export const useUpgradeFromV1ToV2 = ( - trackingLocation: string, - alwaysFork?: boolean -) => { - const state = useAppState(); - const effects = useEffects(); - const [isLoading, setIsLoading] = useState(false); - const updateSandbox = useEffects().api.updateSandbox; - const canConvert = state.editor.currentSandbox - ? hasPermission(state.editor.currentSandbox.authorization, 'write_code') - : false; - - return { - loading: isLoading, - canConvert, - perform: async () => { - if (!state.editor.currentSandbox) { - return; - } - - setIsLoading(true); - - track(`Editor - ${trackingLocation} Convert to Devbox`, { - owned: canConvert, - }); - - try { - const sandboxId = state.editor.currentSandbox.id; - - if (canConvert && !alwaysFork) { - const alias = state.editor.currentSandbox.alias; - - await updateSandbox(sandboxId, { - v2: true, - }); - - const sandboxV2Url = sandboxUrl({ - id: sandboxId, - alias, - isV2: true, - query: { - welcome: 'true', - }, - }); - - window.location.href = sandboxV2Url; - } else { - const forkedSandbox = await effects.api.forkSandbox(sandboxId, { - v2: true, - teamId: state.activeTeam, - }); - - const sandboxV2Url = sandboxUrl({ - id: forkedSandbox.id, - alias: forkedSandbox.alias, - isV2: true, - query: { - welcome: 'true', - }, - }); - - window.location.href = sandboxV2Url; - } - } catch (err) { - setIsLoading(false); - effects.notificationToast.error( - 'Failed to convert. Please try again.' - ); - } - }, - }; -}; diff --git a/packages/app/src/app/overmind/actions.ts b/packages/app/src/app/overmind/actions.ts index 5e3efcdd6f8..ceb65ab0e5e 100755 --- a/packages/app/src/app/overmind/actions.ts +++ b/packages/app/src/app/overmind/actions.ts @@ -5,6 +5,7 @@ import { convertTypeToStatus, } from '@codesandbox/common/lib/utils/notifications'; import { protocolAndHost } from '@codesandbox/common/lib/utils/url-generator'; +import { Sandbox } from '@codesandbox/common/lib/types'; import { withLoadApp } from './factories'; import * as internalActions from './internalActions'; @@ -13,7 +14,6 @@ import { Context } from '.'; import { DEFAULT_DASHBOARD_SANDBOXES } from './namespaces/dashboard/state'; import { FinalizeSignUpOptions } from './effects/api/types'; import { AuthOptions, GHScopeOption } from './utils/auth'; -import { renameZeitToVercel } from './utils/vercel'; export const internal = internalActions; @@ -30,23 +30,11 @@ export const onInitializeOvermind = async ( effects.live.initialize({ provideJwtToken, - onApplyOperation: actions.live.applyTransformation, - onOperationError: actions.live.onOperationError, }); effects.flows.initialize(overmindInstance.reaction); - // We consider recover mode something to be done when browser actually crashes, meaning there is no unmount - effects.browser.onUnload(() => { - if (state.editor.currentSandbox && state.connected) { - effects.moduleRecover.clearSandbox(state.editor.currentSandbox.id); - } - }); - effects.api.initialize({ - getParsedConfigurations() { - return state.editor.parsedConfigurations; - }, provideJwtToken() { if (process.env.LOCAL_SERVER || process.env.STAGING) { return localStorage.getItem('devJwt'); @@ -84,87 +72,8 @@ export const onInitializeOvermind = async ( }, }); - effects.vercel.initialize({ - getToken() { - return state.user?.integrations.vercel?.token ?? null; - }, - }); - - effects.netlify.initialize({ - getUserId() { - return state.user?.id ?? null; - }, - provideJwtToken() { - if (process.env.LOCAL_SERVER || process.env.STAGING) { - return Promise.resolve(localStorage.getItem('devJwt')); - } - - return provideJwtToken(); - }, - }); - - effects.githubPages.initialize({ - provideJwtToken() { - if (process.env.LOCAL_SERVER || process.env.STAGING) { - return Promise.resolve(localStorage.getItem('devJwt')); - } - return provideJwtToken(); - }, - }); - - effects.prettyfier.initialize({ - getCurrentModule() { - return state.editor.currentModule; - }, - getPrettierConfig() { - let config = state.preferences.settings.prettierConfig; - const configFromSandbox = state.editor.currentSandbox?.modules.find( - module => - module.directoryShortid == null && module.title === '.prettierrc' - ); - - if (configFromSandbox) { - config = JSON.parse(configFromSandbox.code); - } - - return config; - }, - }); - - effects.vscode.initialize({ - getCurrentSandbox: () => state.editor.currentSandbox, - getCurrentModule: () => state.editor.currentModule, - getSandboxFs: () => state.editor.modulesByPath, - getCurrentUser: () => state.user, - onOperationApplied: actions.editor.onOperationApplied, - onCodeChange: actions.editor.codeChanged, - onSelectionChanged: selection => { - actions.editor.onSelectionChanged(selection); - actions.live.onSelectionChanged(selection); - }, - onViewRangeChanged: actions.live.onViewRangeChanged, - onCommentClick: actions.comments.onCommentClick, - reaction: overmindInstance.reaction, - getState: (path: string) => - path ? path.split('.').reduce((aggr, key) => aggr[key], state) : state, - getSignal: (path: string) => - path.split('.').reduce((aggr, key) => aggr[key], actions), - }); - - effects.preview.initialize(); - actions.internal.setViewModeForDashboard(); - effects.browser.onWindowMessage(event => { - if (event.data.type === 'screenshot-requested-from-preview') { - actions.preview.createPreviewComment(); - } - }); - - effects.browserExtension.hasExtension().then(hasExtension => { - actions.preview.setExtension(hasExtension); - }); - try { state.features = await effects.api.getFeatures(); } catch { @@ -244,23 +153,11 @@ export const connectionChanged = ({ state }: Context, connected: boolean) => { }; type ModalName = - | 'githubPagesLogs' - | 'deleteDeployment' - | 'deleteSandbox' | 'feedback' - | 'forkServerModal' - | 'liveSessionEnded' - | 'netlifyLogs' | 'preferences' - | 'searchDependencies' - | 'share' - | 'signInForTemplates' | 'userSurvey' - | 'liveSessionRestricted' | 'sandboxPicker' | 'minimumPrivacy' - | 'addMemberToWorkspace' - | 'legacyPayment' | 'import' | 'create'; @@ -349,61 +246,6 @@ export const removeNotification = ({ state }: Context, id: number) => { state.notifications.splice(notificationToRemoveIndex, 1); }; -export const signInVercelClicked = async ({ - state, - effects: { browser, api, notificationToast }, - actions, -}: Context) => { - state.isLoadingVercel = true; - - /** - * We're opening a browser popup here with the /auth/zeit page but then do a server - * side redirect to the Vercel sign in page. This only works on production, not locally. - * We also can't rename the zeit route to vercel (yet) because the server will throw an - * invalid redirect_uri error. - */ - const popup = browser.openPopup('/auth/zeit', 'sign in'); - const data: { code: string } = await browser.waitForMessage('signin'); - - popup.close(); - - if (data && data.code) { - try { - const currentUser = await api.createVercelIntegration(data.code); - state.user = renameZeitToVercel(currentUser); - } catch (error) { - actions.internal.handleError({ - message: 'Not able to add a Vercel integration. Please try again.', - error, - }); - } - - try { - await actions.deployment.internal.getVercelUserDetails(); - - // Not sure if we ever reach the catch clause below because the error has already - // been caught in getVercelUserDetails. - } catch (error) { - actions.internal.handleError({ - message: - 'We were not able to fetch your Vercel user details. You should still be able to deploy to Vercel, please try again if needed.', - error, - }); - } - } else { - notificationToast.error('Could not authorize with Vercel'); - } - - state.isLoadingVercel = false; -}; - -export const signOutVercelClicked = async ({ state, effects }: Context) => { - if (state.user?.integrations?.vercel) { - await effects.api.signoutVercel(); - state.user.integrations.vercel = null; - } -}; - export const authTokenRequested = async ({ actions }: Context) => { await actions.internal.authorize(); }; @@ -423,17 +265,10 @@ export const signInGithubClicked = async ( state.isLoadingGithub = true; await actions.internal.signIn({ includedScopes, provider: 'github' }); state.isLoadingGithub = false; - if (state.editor.currentSandbox?.originalGit) { - actions.git.loadGitSource(); - } }; export const signOutClicked = async ({ state, effects, actions }: Context) => { effects.analytics.track('Sign Out', {}); - state.workspace.openedWorkspaceItem = 'files'; - if (state.live.isLive) { - actions.live.internal.disconnect(); - } await effects.api.signout(); effects.browser.storage.remove(TEAM_ID_LOCAL_STORAGE); effects.router.clearWorkspaceId(); @@ -469,34 +304,6 @@ export const track = ( effects.analytics.track(name, data); }; -export const refetchSandboxInfo = async ({ - actions, - effects, - state, -}: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox?.id) { - return; - } - - const updatedSandbox = await effects.api.getSandbox(sandbox.id); - - sandbox.collection = updatedSandbox.collection; - sandbox.owned = updatedSandbox.owned; - sandbox.userLiked = updatedSandbox.userLiked; - sandbox.title = updatedSandbox.title; - sandbox.description = updatedSandbox.description; - sandbox.team = updatedSandbox.team; - sandbox.roomId = updatedSandbox.roomId; - sandbox.authorization = updatedSandbox.authorization; - sandbox.privacy = updatedSandbox.privacy; - sandbox.featureFlags = updatedSandbox.featureFlags; - sandbox.npmRegistries = updatedSandbox.npmRegistries; - - await actions.editor.internal.initializeSandbox(sandbox); -}; - export const acceptTeamInvitation = ( { effects, actions }: Context, { @@ -634,3 +441,51 @@ export const getSandboxesLimits = async ({ effects, state }: Context) => { export const clearNewUserFirstWorkspaceId = ({ state }: Context) => { state.newUserFirstWorkspaceId = null; }; + +export const gotUploadedFiles = async ( + { state, actions, effects }: Context, + message: string +) => { + const modal = 'storageManagement'; + effects.analytics.track('Open Modal', { modal }); + state.currentModalMessage = message; + state.currentModal = modal; + + try { + const uploadedFilesInfo = await effects.api.getUploads(); + + state.uploadedFiles = uploadedFilesInfo.uploads; + state.maxStorage = uploadedFilesInfo.maxSize; + state.usedStorage = uploadedFilesInfo.currentSize; + } catch (error) { + actions.internal.handleError({ + message: 'Unable to get uploaded files information', + error, + }); + } +}; + +export const createRepoFiles = ({ effects }: Context, sandbox: Sandbox) => { + return effects.git.export(sandbox); +}; + +export const deleteUploadedFile = async ( + { actions, effects, state }: Context, + id: string +) => { + if (!state.uploadedFiles) { + return; + } + const index = state.uploadedFiles.findIndex(file => file.id === id); + const removedFiles = state.uploadedFiles.splice(index, 1); + + try { + await effects.api.deleteUploadedFile(id); + } catch (error) { + state.uploadedFiles.splice(index, 0, ...removedFiles); + actions.internal.handleError({ + message: 'Unable to delete uploaded file', + error, + }); + } +}; diff --git a/packages/app/src/app/overmind/effects/algoliaSearch.ts b/packages/app/src/app/overmind/effects/algoliaSearch.ts deleted file mode 100755 index 15cae021486..00000000000 --- a/packages/app/src/app/overmind/effects/algoliaSearch.ts +++ /dev/null @@ -1,47 +0,0 @@ -import search from 'algoliasearch'; -import { Dependency } from '@codesandbox/common/lib/types/algolia'; -import { getGreensockAlias, GREENSOCK_ALIASES } from './utils/greensock'; - -const client = search('OFCNCOG2CU', '00383ecd8441ead30b1b0ff981c426f5'); -const NPMSearchIndex = client.initIndex('npm-search'); - -async function searchAlgolia(value: string | void, hits?: number) { - // @ts-ignore - const all: { - hits: Dependency[]; - } = await NPMSearchIndex.search(value || '', { - // @ts-ignore - hitsPerPage: hits || 10, - }); - - return all?.hits || []; -} - -export default { - async searchDependencies(value: string | void, hits?: number) { - if (!value) { - return searchAlgolia(value, hits); - } - - const greensockAlias = getGreensockAlias(value); - let searchResults: Dependency[]; - if (greensockAlias) { - const [depResults, gsap] = await Promise.all([ - searchAlgolia(value, 3 + GREENSOCK_ALIASES.length), - searchAlgolia(greensockAlias, 1), - ]); - // Alias any greensock result out, so that we don't have duplicate results and we don't show - // gsap for a plugin where only gsap-trial is applicable. - const filteredDepResults = depResults.filter( - d => !GREENSOCK_ALIASES.includes(d.name) - ); - // Then finally truncate the results back to 3 - filteredDepResults.length = 3; - searchResults = [gsap[0], ...filteredDepResults]; - } else { - searchResults = await searchAlgolia(value, 4); - } - - return searchResults; - }, -}; diff --git a/packages/app/src/app/overmind/effects/api/apiFactory.ts b/packages/app/src/app/overmind/effects/api/apiFactory.ts index ac9be3f963d..2d2b1d96896 100755 --- a/packages/app/src/app/overmind/effects/api/apiFactory.ts +++ b/packages/app/src/app/overmind/effects/api/apiFactory.ts @@ -43,7 +43,6 @@ export type Api = { export type ApiConfig = { provideJwtToken: () => string | null; - getParsedConfigurations: () => any; }; export default (config: ApiConfig) => { diff --git a/packages/app/src/app/overmind/effects/api/index.ts b/packages/app/src/app/overmind/effects/api/index.ts index a3ee19b1fdb..87c1d3b27c9 100755 --- a/packages/app/src/app/overmind/effects/api/index.ts +++ b/packages/app/src/app/overmind/effects/api/index.ts @@ -1,39 +1,20 @@ -import { TemplateType } from '@codesandbox/common/lib/templates'; import { CurrentUserFromAPI, CustomTemplate, - Dependency, - Directory, - EnvironmentVariable, - GitChanges, - GitCommit, - GitFileCompare, - GitInfo, - GitPathChanges, - GitPr, - Module, - NpmManifest, Profile, Sandbox, UploadedFilesInfo, UserQuery, UserSandbox, - SettingsSync, ForkSandboxBody, } from '@codesandbox/common/lib/types'; import { FETCH_TEAM_TEMPLATES } from 'app/components/Create/utils/queries'; import { client } from 'app/graphql/client'; import { PendingUserType } from 'app/overmind/state'; -import { - transformDirectory, - transformModule, - transformSandbox, -} from '../utils/sandbox'; +import { transformSandbox } from '../utils/sandbox'; import apiFactory, { Api, ApiConfig, Params } from './apiFactory'; import { - IDirectoryAPIResponse, - IModuleAPIResponse, SandboxAPIResponse, FinalizeSignUpOptions, MetaFeatures, @@ -77,14 +58,6 @@ export default { revokeToken(token: string): Promise { return api.delete(`/auth/revoke/${token}`); }, - getDependency(name: string, tag: string): Promise { - return api.get(`/dependencies/${name}@${tag}`); - }, - getDependencyManifest(sandboxId: string, name: string): Promise { - return api.get( - `/sandboxes/${sandboxId}/npm_registry/${name.replace('/', '%2f')}` - ); - }, async getSandbox(id: string, params?: Params): Promise { const sandbox = await api.get( `/sandboxes/${id}`, @@ -103,310 +76,12 @@ export default { return transformSandbox(sandbox); }, - createModule(sandboxId: string, module: Module): Promise { - return api - .post(`/sandboxes/${sandboxId}/modules`, { - module: { - title: module.title, - directoryShortid: module.directoryShortid, - code: module.code, - isBinary: module.isBinary === undefined ? false : module.isBinary, - }, - }) - .then(transformModule); - }, - async deleteModule(sandboxId: string, moduleShortid: string): Promise { - await api.delete( - `/sandboxes/${sandboxId}/modules/${moduleShortid}` - ); - }, - saveModuleCode( - sandboxId: string, - moduleShortid: string, - code: string - ): Promise { - return api - .put( - `/sandboxes/${sandboxId}/modules/${moduleShortid}`, - { - module: { code }, - } - ) - .then(transformModule); - }, - saveModulePrivateUpload( - sandboxId: string, - moduleShortid: string, - data: { - code: string; - uploadId: string; - sha: string; - } - ): Promise { - return api - .put( - `/sandboxes/${sandboxId}/modules/${moduleShortid}`, - { - module: data, - } - ) - .then(transformModule); - }, - saveModules(sandboxId: string, modules: Module[]): Promise { - return api - .put(`/sandboxes/${sandboxId}/modules/mupdate`, { - modules, - }) - .then(modulesResult => modulesResult.map(transformModule)); - }, - getGitChanges(sandboxId: string): Promise { - return api.get(`/sandboxes/${sandboxId}/git/diff`); - }, - saveGitOriginalCommitSha( - sandboxId: string, - commitSha: string - ): Promise { - return api.patch(`/sandboxes/${sandboxId}/original_git_commit_sha`, { - original_git_commit_sha: commitSha, - }); - }, - saveTemplate(sandboxId: string, template: TemplateType): Promise { - return api.put(`/sandboxes/${sandboxId}/`, { - sandbox: { template }, - }); - }, - unlikeSandbox(sandboxId: string) { - return api.request({ - method: 'DELETE', - url: `/sandboxes/${sandboxId}/likes`, - data: { - id: sandboxId, - }, - }); - }, - likeSandbox(sandboxId: string) { - return api.post(`/sandboxes/${sandboxId}/likes`, { - id: sandboxId, - }); - }, - savePrivacy(sandboxId: string, privacy: 0 | 1 | 2) { - return api.patch(`/sandboxes/${sandboxId}/privacy`, { - sandbox: { - privacy, - }, - }); - }, - saveFrozen(sandboxId: string, isFrozen: boolean) { - return api.put(`/sandboxes/${sandboxId}`, { - sandbox: { - is_frozen: isFrozen, - }, - }); - }, - getEnvironmentVariables( - sandboxId: string - ): Promise<{ [key: string]: string }> { - return api.get( - `/sandboxes/${sandboxId}/env`, - {}, - { shouldCamelize: false } - ); - }, - saveEnvironmentVariable( - sandboxId: string, - environmentVariable: EnvironmentVariable - ): Promise<{ [key: string]: string }> { - return api.post( - `/sandboxes/${sandboxId}/env`, - { - environment_variable: environmentVariable, - }, - { - shouldCamelize: false, - } - ); - }, - deleteEnvironmentVariable( - sandboxId: string, - name: string - ): Promise<{ [key: string]: string }> { - return api.delete( - `/sandboxes/${sandboxId}/env/${name}`, - {}, - { shouldCamelize: false } - ); - }, - saveModuleTitle(sandboxId: string, moduleShortid: string, title: string) { - return api.put( - `/sandboxes/${sandboxId}/modules/${moduleShortid}`, - { - module: { title }, - } - ); - }, - createDirectory( - sandboxId: string, - directoryShortid: string, - title: string - ): Promise { - return api - .post(`/sandboxes/${sandboxId}/directories`, { - directory: { - title, - directoryShortid, - }, - }) - .then(transformDirectory); - }, - saveModuleDirectory( - sandboxId: string, - moduleShortid: string, - directoryShortid: string - ) { - return api.put( - `/sandboxes/${sandboxId}/modules/${moduleShortid}`, - { - module: { directoryShortid }, - } - ); - }, - saveDirectoryDirectory( - sandboxId: string, - sourceDirectoryShortid: string, - targetDirectoryShortId: string - ) { - return api.put( - `/sandboxes/${sandboxId}/directories/${sourceDirectoryShortid}`, - { - directory: { directoryShortid: targetDirectoryShortId }, - } - ); - }, - deleteDirectory(sandboxId: string, directoryShortid: string) { - return api.delete( - `/sandboxes/${sandboxId}/directories/${directoryShortid}` - ); - }, - saveDirectoryTitle( - sandboxId: string, - directoryShortid: string, - title: string - ) { - return api.put( - `/sandboxes/${sandboxId}/directories/${directoryShortid}`, - { - directory: { title }, - } - ); - }, getUploads(): Promise { return api.get('/users/current_user/uploads'); }, deleteUploadedFile(uploadId: string): Promise { return api.delete(`/users/current_user/uploads/${uploadId}`); }, - createUpload(name: string, content: string): Promise<{ url: string }> { - return api.post('/users/current_user/uploads', { - content, - name, - }); - }, - async massCreateModules( - sandboxId: string, - directoryShortid: string | null, - modules: Module[], - directories: Directory[] - ): Promise<{ - modules: Module[]; - directories: Directory[]; - }> { - const data = (await api.post(`/sandboxes/${sandboxId}/modules/mcreate`, { - directoryShortid, - modules, - directories, - })) as { - modules: IModuleAPIResponse[]; - directories: IDirectoryAPIResponse[]; - }; - - return { - modules: data.modules.map(transformModule), - directories: data.directories.map(transformDirectory), - }; - }, - createGit( - sandboxId: string, - repoTitle: string, - data: object - ): Promise { - return api.post(`/sandboxes/${sandboxId}/git/repo/${repoTitle}`, data); - }, - createGitCommit( - sandboxId: string, - message: string, - changes: GitChanges, - parentCommitShas: string[] - ): Promise { - return api.post(`/sandboxes/${sandboxId}/git/commit`, { - id: sandboxId, - message, - changes, - parentCommitShas, - }); - }, - async compareGit( - sandboxId: string, - baseRef: string, - headRef: string, - includeContents = false - ): Promise<{ - baseCommitSha: string; - headCommitSha: string; - files: GitFileCompare[]; - }> { - const response: any = await api.post( - `/sandboxes/${sandboxId}/git/compare`, - { - baseRef, - headRef, - includeContents, - } - ); - - return response; - }, - getGitPr(sandboxId: string, prNumber: number): Promise { - return api.get(`/sandboxes/${sandboxId}/git/prs/${prNumber}`); - }, - async getGitRights(sandboxId: string) { - const response = await api.get<{ permission: 'admin' | 'write' | 'read' }>( - `/sandboxes/${sandboxId}/git/rights` - ); - - return response.permission; - }, - createGitPr( - sandboxId: string, - title: string, - description: string, - changes: GitChanges - ): Promise { - return api.post(`/sandboxes/${sandboxId}/git/pr`, { - sandboxId, - title, - description, - changes, - }); - }, - async createLiveRoom(sandboxId: string): Promise { - const data = await api.post<{ - id: string; - }>(`/sandboxes/${sandboxId}/live`, { - id: sandboxId, - }); - - return data.id; - }, getProfile(username: string): Promise { return api.get(`/users/${username}`); }, @@ -461,14 +136,6 @@ export default { }, }); }, - createTag(sandboxId: string, tagName: string): Promise { - return api.post(`/sandboxes/${sandboxId}/tags`, { - tag: tagName, - }); - }, - deleteTag(sandboxId: string, tagName: string): Promise { - return api.delete(`/sandboxes/${sandboxId}/tags/${tagName}`); - }, /** * Updates a sandbox. Used to update sandbox metadata but also to convert * a sandbox to a devbox. @@ -478,20 +145,6 @@ export default { sandbox: data, }); }, - createResource(sandboxId: string, resource: string): Promise { - return api.post(`/sandboxes/${sandboxId}/resources`, { - externalResource: resource, - }); - }, - deleteResource(sandboxId: string, resource: string): Promise { - return api.request({ - method: 'DELETE', - url: `/sandboxes/${sandboxId}/resources/`, - data: { - id: resource, - }, - }); - }, updatePrivacy( sandboxId: string, privacy: 0 | 1 | 2 @@ -502,64 +155,24 @@ export default { }, }); }, - createVercelIntegration(code: string): Promise { - return api.post(`/users/current_user/integrations/vercel`, { - code, - }); - }, signout(): Promise { return api.delete(`/users/signout`); }, signoutGithubIntegration(): Promise { return api.delete(`/users/current_user/integrations/github`); }, - signoutVercel(): Promise { - return api.delete(`/users/current_user/integrations/vercel`); - }, preloadTeamTemplates(teamId: string) { client.query({ query: FETCH_TEAM_TEMPLATES, variables: { teamId } }); }, - fetchTemplate( - sandboxId: string, - templateId: string - ): Promise { - return api.get(`/sandboxes/${sandboxId}/templates/`); - }, deleteTemplate( sandboxId: string, templateId: string ): Promise { return api.delete(`/sandboxes/${sandboxId}/templates/${templateId}`); }, - updateTemplate( - sandboxId: string, - template: CustomTemplate - ): Promise { - return api - .put<{ template: CustomTemplate }>( - `/sandboxes/${sandboxId}/templates/${template.id}`, - { - template, - } - ) - .then(data => data.template); - }, - createTemplate( - sandboxId: string, - template: { color: string; description: string; title: string } - ): Promise { - return api - .post<{ template: CustomTemplate }>(`/sandboxes/${sandboxId}/templates`, { - template, - }) - .then(data => data.template); - }, queryUsers(query: string): Promise { return api.get(`/users/search?username=${query}`); }, - makeGitSandbox(sandboxId: string): Promise { - return api.post(`/sandboxes/${sandboxId}/make_git_sandbox`, null); - }, updateUserFeaturedSandboxes( username: string, featuredSandboxIds: string[] @@ -570,27 +183,6 @@ export default { }, }); }, - createUserSettings({ - name, - settings, - }: { - name: string; - settings: string; - }): Promise { - return api.post(`/users/current_user/editor_settings`, { - name, - settings, - }); - }, - getUserSettings(): Promise { - return api.get(`/users/current_user/editor_settings`); - }, - editUserSettings(body: any, id: string): Promise { - return api.patch(`/users/current_user/editor_settings/${id}`, body); - }, - removeUserSetting(id: string): Promise { - return api.delete(`/users/current_user/editor_settings`); - }, sandboxesLimits() { return api.get<{ sandboxCount: number; diff --git a/packages/app/src/app/overmind/effects/browserExtension.ts b/packages/app/src/app/overmind/effects/browserExtension.ts deleted file mode 100644 index 009943fe3c6..00000000000 --- a/packages/app/src/app/overmind/effects/browserExtension.ts +++ /dev/null @@ -1,43 +0,0 @@ -export default { - install() { - return new Promise(resolve => { - const win = window.open( - 'https://chrome.google.com/webstore/detail/hdidglkcgdolpoijdckmafdnddjoglia', - 'CodeSandbox Extension', - 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=1100,height=300' - ); - const interval = setInterval(() => { - if (win && win.closed) { - clearInterval(interval); - resolve(); - } - }, 500); - }); - }, - setNotifiedImprovedScreenshots() { - localStorage.setItem('HAS_NOTIFIED_IMPROVED_SCREENSHOTS', 'true'); - }, - hasNotifiedImprovedScreenshots() { - return Boolean(localStorage.getItem('HAS_NOTIFIED_IMPROVED_SCREENSHOTS')); - }, - hasExtension(): Promise { - return new Promise(resolve => { - setTimeout(() => { - let extensionTimeout; - const listener = event => { - if (event.data.type === 'extension-pong') { - clearTimeout(extensionTimeout); - window.removeEventListener('message', listener); - resolve(true); - } - }; - extensionTimeout = setTimeout(() => { - window.removeEventListener('message', listener); - resolve(false); - }, 1000); - window.addEventListener('message', listener); - window.postMessage({ type: 'extension-ping' }, '*'); - }, 3000); - }); - }, -}; diff --git a/packages/app/src/app/overmind/effects/codesandboxApi.ts b/packages/app/src/app/overmind/effects/codesandboxApi.ts deleted file mode 100755 index 1c4cfb8dca0..00000000000 --- a/packages/app/src/app/overmind/effects/codesandboxApi.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { dispatch, listen } from 'codesandbox-api'; - -const listeners = new Map(); - -export default { - listen(action: ({ data }) => void): () => void { - if (listeners.get(action)) { - listeners.get(action)(); - } - - const disposer = listen(data => { - action({ data: data || {} }); - }); - listeners.set(action, disposer); - - return disposer; - }, - restartSandbox() { - dispatch({ type: 'socket:message', channel: 'sandbox:restart' }); - }, - disconnectSSE() { - dispatch({ type: 'codesandbox:sse:disconnect' }); - }, - logTerminalMessage(data: any) { - dispatch({ - type: 'terminal:message', - data, - }); - }, - exitShell(data: any) { - const { id, code, signal } = data; - - dispatch({ - type: 'shell:exit', - code, - signal, - id, - }); - }, - outShell(dataArg: any) { - const { id, data } = dataArg; - - dispatch({ - type: 'shell:out', - data, - id, - }); - }, -}; diff --git a/packages/app/src/app/overmind/effects/deployment/githubPages.ts b/packages/app/src/app/overmind/effects/deployment/githubPages.ts deleted file mode 100755 index 69c18149bc8..00000000000 --- a/packages/app/src/app/overmind/effects/deployment/githubPages.ts +++ /dev/null @@ -1,113 +0,0 @@ -import getTemplate from "@codesandbox/common/lib/templates"; -import { Sandbox } from "@codesandbox/common/lib/types"; -import axios from "axios"; - -const GHPagesBaseUrl = "https://builder.micro.codesandbox.io/gh-pages"; - -type Options = { - provideJwtToken: () => Promise; -}; - -export default (() => { - let _options: Options; - let _jwtToken: string | undefined; - const createHeaders = (jwt: string) => - jwt - ? { - Authorization: `Bearer ${jwt}`, - } - : {}; - - return { - initialize(options: Options) { - _options = options; - }, - async provideJwtCached() { - if (!_jwtToken) { - try { - const token = await _options.provideJwtToken(); - // Token expires after 10 minutes, we cache the token for 8 minutes. - const timeout = 1000 * 60 * 8; - setTimeout(() => { - _jwtToken = undefined; - }, timeout); - return token; - } catch (e) { - _jwtToken = undefined; - return Promise.reject(e); - } - } - - return _jwtToken; - }, - - async getSite(id: string) { - const url = `${GHPagesBaseUrl}/${id}`; - const token = await this.provideJwtCached(); - - const { data } = await axios.get(url, { - headers: createHeaders(token), - }); - return data; - }, - - async getLogs(id: string) { - const url = `${GHPagesBaseUrl}/${id}/status`; - const token = await this.provideJwtCached(); - - const { data } = await axios.get(url, { - headers: createHeaders(token), - }); - - return data; - }, - - async deploy(sandbox: Sandbox, username: string): Promise { - const token = await this.provideJwtCached(); - const template = getTemplate(sandbox.template); - const buildCommand = (name: string) => { - if (name === "styleguidist") { - return "styleguide:build"; - } - if (name === "nuxt") { - return "generate"; - } - - if (name === "parcel") { - return `build --public-url /csb-${sandbox.id}/`; - } - - if (name === "preact-cli") { - return "build --no-prerender"; - } - - if (name === "gatsby") { - return "build --prefix-paths"; - } - - return "build"; - }; - - const env = (name: string) => { - if (name === "create-react-app") { - return `PUBLIC_URL=https://${username.toLowerCase()}.github.io/csb-${ - sandbox.id - }/`; - } - return ""; - }; - - const website = await axios.post( - `${GHPagesBaseUrl}/${sandbox.id}`, - { - dist: template.distDir, - env: env(template.name), - buildCommand: buildCommand(template.name), - }, - { headers: createHeaders(token) } - ); - - return website; - }, - }; -})(); diff --git a/packages/app/src/app/overmind/effects/deployment/netlify.ts b/packages/app/src/app/overmind/effects/deployment/netlify.ts deleted file mode 100755 index 3e7aa927f86..00000000000 --- a/packages/app/src/app/overmind/effects/deployment/netlify.ts +++ /dev/null @@ -1,133 +0,0 @@ -import getTemplate from "@codesandbox/common/lib/templates"; -import { NetlifySite, Sandbox } from "@codesandbox/common/lib/types"; -import getNetlifyConfig from "app/utils/getNetlifyConfig"; -import axios from "axios"; - -const NetlifyBaseURL = "https://builder.micro.codesandbox.io/netlify/site"; - -type Options = { - getUserId(): string | null; - provideJwtToken: () => Promise; -}; - -export default (() => { - let _options: Options; - let _jwtToken: string | undefined; - const createHeaders = (jwt: string) => - jwt - ? { - Authorization: `Bearer ${jwt}`, - } - : {}; - - return { - initialize(options: Options) { - _options = options; - }, - async provideJwtCached() { - if (!_jwtToken) { - try { - const token = await _options.provideJwtToken(); - // Token expires after 10 minutes, we cache the token for 8 minutes. - const timeout = 1000 * 60 * 8; - setTimeout(() => { - _jwtToken = undefined; - }, timeout); - return token; - } catch (e) { - _jwtToken = undefined; - return Promise.reject(e); - } - } - - return _jwtToken; - }, - - async getLogs(id: string) { - const url = `${NetlifyBaseURL}/${id}/status`; - const token = await this.provideJwtCached(); - - const { data } = await axios.get(url, { - headers: createHeaders(token), - }); - - return data; - }, - async claimSite(sandboxId: string) { - const userId = _options.getUserId(); - const token = await this.provideJwtCached(); - const sessionId = `${userId}-${sandboxId}`; - - const { data } = await axios.post( - `${NetlifyBaseURL}/${sandboxId}/claim`, - { sessionId }, - { headers: createHeaders(token) } - ); - - return data.claim; - }, - async getDeployments(sandboxId: string): Promise { - const token = await this.provideJwtCached(); - const response = await axios.get(`${NetlifyBaseURL}/${sandboxId}`, { - headers: createHeaders(token), - }); - - return response.data; - }, - async deploy(sandbox: Sandbox): Promise { - const token = await this.provideJwtCached(); - const userId = _options.getUserId(); - const template = getTemplate(sandbox.template); - const buildCommand = (name: string) => { - if (name === "styleguidist") { - return "styleguide:build"; - } - - if (name === "preact-cli") { - return "build --no-prerender"; - } - - if (name === "nuxt") { - return "generate"; - } - - return "build"; - }; - const buildConfig = getNetlifyConfig(sandbox); - // command needs to be passed without the package manager name - const buildCommandFromConfig = (buildConfig.command || "") - .replace("npm run", "") - .replace("yarn ", ""); - let id = ""; - try { - const { data } = await axios.request({ - url: `${NetlifyBaseURL}/${sandbox.id}`, - headers: createHeaders(token), - }); - - id = data.site_id; - } catch (e) { - const { data } = await axios.post( - NetlifyBaseURL, - { - name: `csb-${sandbox.id}`, - sessionId: `${userId}-${sandbox.id}`, - }, - { headers: createHeaders(token) } - ); - id = data.site_id; - } - await axios.post( - `${NetlifyBaseURL}/${sandbox.id}/deploy`, - { - siteId: id, - dist: buildConfig.publish || template.distDir, - buildCommand: buildCommandFromConfig || buildCommand(template.name), - }, - { headers: createHeaders(token) } - ); - - return id; - }, - }; -})(); diff --git a/packages/app/src/app/overmind/effects/deployment/vercel.ts b/packages/app/src/app/overmind/effects/deployment/vercel.ts deleted file mode 100755 index 2321016a109..00000000000 --- a/packages/app/src/app/overmind/effects/deployment/vercel.ts +++ /dev/null @@ -1,256 +0,0 @@ -import getTemplate from '@codesandbox/common/lib/templates'; -import { - Sandbox, - VercelConfig, - VercelDeployment, - VercelUser, -} from '@codesandbox/common/lib/types'; -import axios from 'axios'; -import { omit } from 'lodash-es'; - -type Options = { - getToken(): string | null; -}; - -interface Object { - [key: string]: string; -} - -interface ApiData { - builds: Object[]; - config?: Object; - deploymentType?: string; - env?: Object; - files: File[]; - forceNew?: boolean; - name: string; - public?: boolean; - regions: string[]; - routes: Object[]; - scope?: string; - version: number; - target: string | null; -} - -interface File { - data: string; - encoding?: string; - file: string; -} - -export default (() => { - let _options: Options; - - function getDefaultHeaders() { - const token = _options.getToken(); - - if (!token) { - throw new Error('You have no Vercel token'); - } - - return { - Authorization: `bearer ${token}`, - }; - } - - return { - initialize(options: Options) { - _options = options; - }, - async checkEnvironmentVariables( - sandbox: Sandbox, - envVars: { - [key: string]: string; - } - ) { - const vercelConfig = this.getConfig(sandbox); - if (!vercelConfig || !vercelConfig.env) return null; - const all = Object.keys(vercelConfig.env).map(async envVar => { - const name = vercelConfig.env[envVar].split('@')[1]; - - if (envVars[envVar]) { - try { - await axios.post( - `https://api.vercel.com/v3/now/secrets/`, - { - name, - value: envVars[envVar], - }, - { headers: getDefaultHeaders() } - ); - // We don't do anything with the error for two main reasons - // 1 - Vercel already shows an error if a ENV variable is missing and you try to deploy - // 2 - This will also fail if the user already has the secret in their account - // eslint-disable-next-line no-empty - } catch {} - } - }); - - await Promise.all(all); - - return null; - }, - getConfig(sandbox: Sandbox): VercelConfig { - const vercelConfigFiles = sandbox.modules - .filter( - m => - m.title === 'vercel.json' || - m.title === 'now.json' || - (m.title === 'package.json' && JSON.parse(m.code).now) - ) - .map(c => JSON.parse(c.code)); - const vercelConfig = vercelConfigFiles[0] || {}; - - if (!vercelConfig.name) { - vercelConfig.name = `csb-${sandbox.id}`; - } - - return vercelConfig; - }, - async getDeployments(name: string): Promise { - const response = await axios.get<{ deployments: VercelDeployment[] }>( - 'https://api.vercel.com/v6/deployments', - { - headers: getDefaultHeaders(), - } - ); - - const deploys = response.data.deployments - .filter(d => d.name === name) - .sort((a, b) => (a.created < b.created ? 1 : -1)); - - return Promise.all(deploys); - }, - async getUser(): Promise { - const response = await axios.get('https://api.vercel.com/v2/user', { - headers: getDefaultHeaders(), - }); - - return response.data.user; - }, - async deleteDeployment(id: string): Promise { - const response = await axios.delete( - `https://api.vercel.com/v9/now/deployments/${id}`, - { - headers: getDefaultHeaders(), - } - ); - return response.data.deployments; - }, - async deploy(contents: any, sandbox: Sandbox, type?: any): Promise { - const apiData = await getApiData(contents, sandbox, type); - const deploymentVersion = apiData.version === 2 ? 'v9' : 'v3'; - - const response = await axios.post( - `https://api.vercel.com/${deploymentVersion}/now/deployments?forceNew=1`, - apiData, - { - headers: getDefaultHeaders(), - } - ); - - return `https://${response.data.url}`; - }, - }; -})(); - -async function getApiData(contents: any, sandbox: Sandbox, type?: any) { - const template = getTemplate(sandbox.template); - - let apiData: Partial = { - files: [], - }; - let packageJSON: any = {}; - let nowJSON: any = {}; - - const projectPackage = contents.files['package.json']; - const nowFile = contents.files['vercel.json'] || contents.files['now.json']; - - if (projectPackage) { - const data = await projectPackage.async('text'); // eslint-disable-line no-await-in-loop - - const parsed = JSON.parse(data); - packageJSON = parsed; - } - - if (nowFile) { - const data = await nowFile.async('text'); // eslint-disable-line no-await-in-loop - - const parsed = JSON.parse(data); - nowJSON = parsed; - } else if (packageJSON.now) { - // Also support package.json if imported like that - nowJSON = packageJSON.now; - } - - const nowDefaults: Pick = { - name: `csb-${sandbox.id}`, - }; - - const filePaths = nowJSON.files || Object.keys(contents.files); - - // We'll omit the homepage-value from package.json as it creates wrong assumptions over the now deployment environment. - packageJSON = omit(packageJSON, 'homepage'); - - // if the template is static we should not have a build command - if (template.name === 'static') { - packageJSON = omit(packageJSON, 'scripts.build'); - } - // We force the sandbox id, so Vercel will always group the deployments to a - // single sandbox - packageJSON.name = nowJSON.name || nowDefaults.name; - - apiData.name = nowJSON.name || nowDefaults.name; - apiData.deploymentType = nowJSON.type; - if (nowJSON.public) { - apiData.public = nowJSON.public; - } - - // We need to tell now the version, builds and routes - apiData.version = 2; - apiData.builds = nowJSON.builds; - apiData.routes = nowJSON.routes; - apiData.env = nowJSON.env; - apiData.scope = nowJSON.scope; - apiData['build.env'] = nowJSON['build.env']; - apiData.regions = nowJSON.regions; - apiData.target = type || 'staging'; - - if (!nowJSON.files && apiData?.files) { - apiData.files.push({ - file: 'package.json', - data: JSON.stringify(packageJSON, null, 2), - }); - } - - for (let i = 0; i < filePaths.length; i += 1) { - const filePath = filePaths[i]; - const file = contents.files[filePath]; - - if (!file.dir && filePath !== 'package.json' && apiData?.files) { - const data = await file.async('base64'); // eslint-disable-line no-await-in-loop - - apiData.files.push({ file: filePath, data, encoding: 'base64' }); - } - - // if person added some files but no package.json - if ( - filePath === 'package.json' && - apiData?.files && - !apiData.files.find(f => f.file === 'package.json') - ) { - apiData.files.push({ - file: 'package.json', - data: JSON.stringify(packageJSON, null, 2), - }); - } - } - - // this adds unnecessary code for version 2 - // packages/common/templates/template.js - if (template.alterDeploymentData && nowJSON.version !== 2) { - apiData = template.alterDeploymentData(apiData); - } - - return apiData; -} diff --git a/packages/app/src/app/overmind/effects/git/export-to-github.ts b/packages/app/src/app/overmind/effects/git/export-to-github.ts old mode 100755 new mode 100644 diff --git a/packages/app/src/app/overmind/effects/git/index.ts b/packages/app/src/app/overmind/effects/git/index.ts old mode 100755 new mode 100644 diff --git a/packages/app/src/app/overmind/effects/gql/collaborators/fragments.ts b/packages/app/src/app/overmind/effects/gql/collaborators/fragments.ts deleted file mode 100644 index 93c28c07f67..00000000000 --- a/packages/app/src/app/overmind/effects/gql/collaborators/fragments.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from 'overmind-graphql'; - -export const collaboratorFragment = gql` - fragment Collaborator on Collaborator { - id - authorization - lastSeenAt - warning - user { - id - username - avatarUrl - } - } -`; - -export const sandboxInvitationFragment = gql` - fragment Invitation on Invitation { - id - authorization - email - } -`; - -export const sandboxChangedFragment = gql` - fragment SandboxChanged on Sandbox { - id - privacy - title - description - authorization - } -`; diff --git a/packages/app/src/app/overmind/effects/gql/collaborators/mutations.ts b/packages/app/src/app/overmind/effects/gql/collaborators/mutations.ts deleted file mode 100644 index 3e5069fb658..00000000000 --- a/packages/app/src/app/overmind/effects/gql/collaborators/mutations.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - AddCollaboratorMutation, - AddCollaboratorMutationVariables, - ChangeCollaboratorAuthorizationMutation, - ChangeCollaboratorAuthorizationMutationVariables, - ChangeSandboxInvitationAuthorizationMutation, - ChangeSandboxInvitationAuthorizationMutationVariables, - InviteCollaboratorMutation, - InviteCollaboratorMutationVariables, - RedeemSandboxInvitationMutation, - RedeemSandboxInvitationMutationVariables, - RemoveCollaboratorMutation, - RemoveCollaboratorMutationVariables, - RevokeSandboxInvitationMutation, - RevokeSandboxInvitationMutationVariables, -} from 'app/graphql/types'; -import { gql, Query } from 'overmind-graphql'; - -import { collaboratorFragment, sandboxInvitationFragment } from './fragments'; - -export const addCollaborator: Query< - AddCollaboratorMutation, - AddCollaboratorMutationVariables -> = gql` - mutation AddCollaborator( - $sandboxId: ID! - $username: String! - $authorization: Authorization! - ) { - addCollaborator( - sandboxId: $sandboxId - username: $username - authorization: $authorization - ) { - ...Collaborator - } - } - ${collaboratorFragment} -`; - -export const removeCollaborator: Query< - RemoveCollaboratorMutation, - RemoveCollaboratorMutationVariables -> = gql` - mutation RemoveCollaborator($sandboxId: ID!, $username: String!) { - removeCollaborator(sandboxId: $sandboxId, username: $username) { - ...Collaborator - } - } - ${collaboratorFragment} -`; - -export const changeCollaboratorAuthorization: Query< - ChangeCollaboratorAuthorizationMutation, - ChangeCollaboratorAuthorizationMutationVariables -> = gql` - mutation ChangeCollaboratorAuthorization( - $sandboxId: ID! - $username: String! - $authorization: Authorization! - ) { - changeCollaboratorAuthorization( - sandboxId: $sandboxId - username: $username - authorization: $authorization - ) { - ...Collaborator - } - } - ${collaboratorFragment} -`; - -export const inviteCollaborator: Query< - InviteCollaboratorMutation, - InviteCollaboratorMutationVariables -> = gql` - mutation InviteCollaborator( - $sandboxId: ID! - $authorization: Authorization! - $email: String! - ) { - createSandboxInvitation( - sandboxId: $sandboxId - authorization: $authorization - email: $email - ) { - ...Invitation - } - } - ${sandboxInvitationFragment} -`; - -export const revokeInvitation: Query< - RevokeSandboxInvitationMutation, - RevokeSandboxInvitationMutationVariables -> = gql` - mutation RevokeSandboxInvitation($sandboxId: ID!, $invitationId: UUID4!) { - revokeSandboxInvitation( - sandboxId: $sandboxId - invitationId: $invitationId - ) { - ...Invitation - } - } - ${sandboxInvitationFragment} -`; - -export const changeSandboxInvitationAuthorization: Query< - ChangeSandboxInvitationAuthorizationMutation, - ChangeSandboxInvitationAuthorizationMutationVariables -> = gql` - mutation ChangeSandboxInvitationAuthorization( - $sandboxId: ID! - $invitationId: UUID4! - $authorization: Authorization! - ) { - changeSandboxInvitationAuthorization( - sandboxId: $sandboxId - invitationId: $invitationId - authorization: $authorization - ) { - ...Invitation - } - } - ${sandboxInvitationFragment} -`; - -export const redeemSandboxInvitation: Query< - RedeemSandboxInvitationMutation, - RedeemSandboxInvitationMutationVariables -> = gql` - mutation RedeemSandboxInvitation($sandboxId: ID!, $invitationToken: String!) { - redeemSandboxInvitation( - sandboxId: $sandboxId - invitationToken: $invitationToken - ) { - ...Invitation - } - } - ${sandboxInvitationFragment} -`; diff --git a/packages/app/src/app/overmind/effects/gql/collaborators/queries.ts b/packages/app/src/app/overmind/effects/gql/collaborators/queries.ts deleted file mode 100644 index cbb0eca07e2..00000000000 --- a/packages/app/src/app/overmind/effects/gql/collaborators/queries.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - SandboxCollaboratorsQuery, - SandboxCollaboratorsQueryVariables, - SandboxInvitationsQuery, - SandboxInvitationsQueryVariables, -} from 'app/graphql/types'; -import { gql, Query } from 'overmind-graphql'; - -import { collaboratorFragment, sandboxInvitationFragment } from './fragments'; - -export const collaborators: Query< - SandboxCollaboratorsQuery, - SandboxCollaboratorsQueryVariables -> = gql` - query SandboxCollaborators($sandboxId: ID!) { - sandbox(sandboxId: $sandboxId) { - collaborators { - ...Collaborator - } - } - } - ${collaboratorFragment} -`; - -export const invitations: Query< - SandboxInvitationsQuery, - SandboxInvitationsQueryVariables -> = gql` - query SandboxInvitations($sandboxId: ID!) { - sandbox(sandboxId: $sandboxId) { - invitations { - ...Invitation - } - } - } - ${sandboxInvitationFragment} -`; diff --git a/packages/app/src/app/overmind/effects/gql/collaborators/subscriptions.ts b/packages/app/src/app/overmind/effects/gql/collaborators/subscriptions.ts deleted file mode 100644 index 51d7b201733..00000000000 --- a/packages/app/src/app/overmind/effects/gql/collaborators/subscriptions.ts +++ /dev/null @@ -1,92 +0,0 @@ -import * as t from 'app/graphql/types'; -import { gql, Query } from 'overmind-graphql'; - -import { - collaboratorFragment, - sandboxChangedFragment, - sandboxInvitationFragment, -} from './fragments'; - -export const onCollaboratorAdded: Query< - t.OnCollaboratorAddedSubscription, - t.OnCollaboratorAddedSubscriptionVariables -> = gql` - subscription OnCollaboratorAdded($sandboxId: ID!) { - collaboratorAdded(sandboxId: $sandboxId) { - ...Collaborator - } - } - ${collaboratorFragment} -`; - -export const onCollaboratorChanged: Query< - t.OnCollaboratorChangedSubscription, - t.OnCollaboratorChangedSubscriptionVariables -> = gql` - subscription OnCollaboratorChanged($sandboxId: ID!) { - collaboratorChanged(sandboxId: $sandboxId) { - ...Collaborator - } - } - ${collaboratorFragment} -`; - -export const onCollaboratorRemoved: Query< - t.OnCollaboratorRemovedSubscription, - t.OnCollaboratorRemovedSubscriptionVariables -> = gql` - subscription OnCollaboratorRemoved($sandboxId: ID!) { - collaboratorRemoved(sandboxId: $sandboxId) { - ...Collaborator - } - } - ${collaboratorFragment} -`; - -export const onInvitationCreated: Query< - t.OnInvitationCreatedSubscription, - t.OnInvitationCreatedSubscriptionVariables -> = gql` - subscription OnInvitationCreated($sandboxId: ID!) { - invitationCreated(sandboxId: $sandboxId) { - ...Invitation - } - } - ${sandboxInvitationFragment} -`; - -export const onInvitationRemoved: Query< - t.OnInvitationRemovedSubscription, - t.OnInvitationRemovedSubscriptionVariables -> = gql` - subscription OnInvitationRemoved($sandboxId: ID!) { - invitationRemoved(sandboxId: $sandboxId) { - ...Invitation - } - } - ${sandboxInvitationFragment} -`; - -export const onInvitationChanged: Query< - t.OnInvitationChangedSubscription, - t.OnInvitationChangedSubscriptionVariables -> = gql` - subscription OnInvitationChanged($sandboxId: ID!) { - invitationChanged(sandboxId: $sandboxId) { - ...Invitation - } - } - ${sandboxInvitationFragment} -`; - -export const onSandboxChangged: Query< - t.OnSandboxChangedSubscription, - t.OnSandboxChangedSubscriptionVariables -> = gql` - subscription OnSandboxChanged($sandboxId: ID!) { - sandboxChanged(sandboxId: $sandboxId) { - ...SandboxChanged - } - } - ${sandboxChangedFragment} -`; diff --git a/packages/app/src/app/overmind/effects/gql/comments/fragments.ts b/packages/app/src/app/overmind/effects/gql/comments/fragments.ts deleted file mode 100644 index cf1519b68cf..00000000000 --- a/packages/app/src/app/overmind/effects/gql/comments/fragments.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { gql } from 'overmind-graphql'; - -export const codeReferenceMetadataFragment = gql` - fragment CodeReferenceMetadata on CodeReferenceMetadata { - anchor - code - head - path - } -`; - -export const userReferenceMetadataFragment = gql` - fragment UserReferenceMetadata on UserReferenceMetadata { - username - userId - } -`; - -export const imageReferenceMetadataFragment = gql` - fragment ImageReferenceMetadata on ImageReferenceMetadata { - fileName - } -`; - -export const previewReferenceMetadataFragment = gql` - fragment PreviewReferenceMetadata on PreviewReferenceMetadata { - width - height - x - y - screenshotUrl - userAgent - } -`; - -export const commentFragment = gql` - fragment Comment on Comment { - id - content - insertedAt - updatedAt - isResolved - anchorReference { - id - metadata { - ... on CodeReferenceMetadata { - ...CodeReferenceMetadata - } - ... on PreviewReferenceMetadata { - ...PreviewReferenceMetadata - } - } - resource - type - } - references { - id - metadata { - ... on UserReferenceMetadata { - ...UserReferenceMetadata - } - ... on CodeReferenceMetadata { - ...CodeReferenceMetadata - } - ... on ImageReferenceMetadata { - ...ImageReferenceMetadata - } - } - resource - type - } - user { - id - name - username - avatarUrl - } - parentComment { - id - } - replyCount - } - ${codeReferenceMetadataFragment} - ${userReferenceMetadataFragment} - ${imageReferenceMetadataFragment} - ${previewReferenceMetadataFragment} -`; - -export const commentWithRepliesFragment = gql` - fragment CommentWithReplies on Comment { - id - content - insertedAt - updatedAt - isResolved - anchorReference { - id - metadata { - ... on CodeReferenceMetadata { - ...CodeReferenceMetadata - } - ... on PreviewReferenceMetadata { - ...PreviewReferenceMetadata - } - } - resource - type - } - references { - id - metadata { - ... on UserReferenceMetadata { - ...UserReferenceMetadata - } - ... on CodeReferenceMetadata { - ...CodeReferenceMetadata - } - ... on ImageReferenceMetadata { - ...ImageReferenceMetadata - } - } - resource - type - } - user { - id - name - username - avatarUrl - } - parentComment { - id - } - replyCount - comments { - ...Comment - } - } - ${commentFragment} - ${codeReferenceMetadataFragment} - ${userReferenceMetadataFragment} - ${imageReferenceMetadataFragment} - ${previewReferenceMetadataFragment} -`; diff --git a/packages/app/src/app/overmind/effects/gql/comments/mutations.ts b/packages/app/src/app/overmind/effects/gql/comments/mutations.ts deleted file mode 100644 index 02d24d5a527..00000000000 --- a/packages/app/src/app/overmind/effects/gql/comments/mutations.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { - CreateCodeCommentMutation, - CreateCodeCommentMutationVariables, - CreateCommentMutation, - CreateCommentMutationVariables, - DeleteCommentMutation, - DeleteCommentMutationVariables, - ResolveCommentMutation, - ResolveCommentMutationVariables, - UnresolveCommentMutation, - UnresolveCommentMutationVariables, - UpdateCommentMutation, - UpdateCommentMutationVariables, - CreatePreviewCommentMutation, - CreatePreviewCommentMutationVariables, -} from 'app/graphql/types'; -import { Query, gql } from 'overmind-graphql'; - -import { commentFragment } from './fragments'; - -export const createComment: Query< - CreateCommentMutation, - CreateCommentMutationVariables -> = gql` - mutation CreateComment( - $id: ID - $content: String! - $sandboxId: ID! - $parentCommentId: ID - $userReferences: [UserReference!] - $codeReferences: [CodeReference!] - $imageReferences: [ImageReference!] - ) { - createComment( - id: $id - content: $content - sandboxId: $sandboxId - parentCommentId: $parentCommentId - userReferences: $userReferences - codeReferences: $codeReferences - imageReferences: $imageReferences - ) { - ...Comment - } - } - ${commentFragment} -`; - -export const createCodeComment: Query< - CreateCodeCommentMutation, - CreateCodeCommentMutationVariables -> = gql` - mutation CreateCodeComment( - $id: ID - $content: String! - $sandboxId: ID! - $parentCommentId: ID - $anchorReference: CodeReference! - $userReferences: [UserReference!] - $codeReferences: [CodeReference!] - $imageReferences: [ImageReference!] - ) { - createCodeComment( - id: $id - content: $content - sandboxId: $sandboxId - parentCommentId: $parentCommentId - anchorReference: $anchorReference - userReferences: $userReferences - codeReferences: $codeReferences - imageReferences: $imageReferences - ) { - ...Comment - } - } - ${commentFragment} -`; - -export const createPreviewComment: Query< - CreatePreviewCommentMutation, - CreatePreviewCommentMutationVariables -> = gql` - mutation CreatePreviewComment( - $id: ID - $content: String! - $sandboxId: ID! - $parentCommentId: ID - $anchorReference: PreviewReference! - $userReferences: [UserReference!] - $codeReferences: [CodeReference!] - $imageReferences: [ImageReference!] - ) { - createPreviewComment( - id: $id - content: $content - sandboxId: $sandboxId - parentCommentId: $parentCommentId - anchorReference: $anchorReference - userReferences: $userReferences - codeReferences: $codeReferences - imageReferences: $imageReferences - ) { - ...Comment - } - } - ${commentFragment} -`; - -export const deleteComment: Query< - DeleteCommentMutation, - DeleteCommentMutationVariables -> = gql` - mutation DeleteComment($commentId: UUID4!, $sandboxId: ID!) { - deleteComment(commentId: $commentId, sandboxId: $sandboxId) { - id - } - } -`; - -export const updateComment: Query< - UpdateCommentMutation, - UpdateCommentMutationVariables -> = gql` - mutation UpdateComment( - $commentId: UUID4! - $sandboxId: ID! - $content: String - $userReferences: [UserReference!] - $codeReferences: [CodeReference!] - $imageReferences: [ImageReference!] - ) { - updateComment( - commentId: $commentId - sandboxId: $sandboxId - content: $content - userReferences: $userReferences - codeReferences: $codeReferences - imageReferences: $imageReferences - ) { - id - } - } -`; - -export const resolveComment: Query< - ResolveCommentMutation, - ResolveCommentMutationVariables -> = gql` - mutation ResolveComment($commentId: UUID4!, $sandboxId: ID!) { - resolveComment(commentId: $commentId, sandboxId: $sandboxId) { - id - } - } -`; - -export const unresolveComment: Query< - UnresolveCommentMutation, - UnresolveCommentMutationVariables -> = gql` - mutation UnresolveComment($commentId: UUID4!, $sandboxId: ID!) { - unresolveComment(commentId: $commentId, sandboxId: $sandboxId) { - id - } - } -`; diff --git a/packages/app/src/app/overmind/effects/gql/comments/queries.ts b/packages/app/src/app/overmind/effects/gql/comments/queries.ts deleted file mode 100644 index 95c387560ad..00000000000 --- a/packages/app/src/app/overmind/effects/gql/comments/queries.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - SandboxCommentQuery, - SandboxCommentQueryVariables, - SandboxCommentsQuery, - SandboxCommentsQueryVariables, -} from 'app/graphql/types'; -import { gql, Query } from 'overmind-graphql'; - -import { commentFragment, commentWithRepliesFragment } from './fragments'; - -export const comment: Query< - SandboxCommentQuery, - SandboxCommentQueryVariables -> = gql` - query SandboxComment($sandboxId: ID!, $commentId: UUID4!) { - sandbox(sandboxId: $sandboxId) { - comment(commentId: $commentId) { - ...CommentWithReplies - } - } - } - ${commentWithRepliesFragment} -`; - -export const comments: Query< - SandboxCommentsQuery, - SandboxCommentsQueryVariables -> = gql` - query SandboxComments($sandboxId: ID!) { - sandbox(sandboxId: $sandboxId) { - comments { - ...Comment - } - } - } - ${commentFragment} -`; diff --git a/packages/app/src/app/overmind/effects/gql/comments/subscriptions.ts b/packages/app/src/app/overmind/effects/gql/comments/subscriptions.ts deleted file mode 100644 index aad22506580..00000000000 --- a/packages/app/src/app/overmind/effects/gql/comments/subscriptions.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - CommentAddedSubscription, - CommentAddedSubscriptionVariables, - CommentChangedSubscription, - CommentChangedSubscriptionVariables, - CommentRemovedSubscription, - CommentRemovedSubscriptionVariables, -} from 'app/graphql/types'; -import { gql, Query } from 'overmind-graphql'; - -import { commentFragment } from './fragments'; - -export const commentAdded: Query< - CommentAddedSubscription, - CommentAddedSubscriptionVariables -> = gql` - subscription CommentAdded($sandboxId: ID!) { - commentAdded(sandboxId: $sandboxId) { - ...Comment - sandbox { - id - } - } - } - ${commentFragment} -`; - -export const commentChanged: Query< - CommentChangedSubscription, - CommentChangedSubscriptionVariables -> = gql` - subscription CommentChanged($sandboxId: ID!) { - commentChanged(sandboxId: $sandboxId) { - ...Comment - sandbox { - id - } - } - } - ${commentFragment} -`; - -export const commentRemoved: Query< - CommentRemovedSubscription, - CommentRemovedSubscriptionVariables -> = gql` - subscription CommentRemoved($sandboxId: ID!) { - commentRemoved(sandboxId: $sandboxId) { - ...Comment - sandbox { - id - } - } - } - ${commentFragment} -`; diff --git a/packages/app/src/app/overmind/effects/gql/index.ts b/packages/app/src/app/overmind/effects/gql/index.ts index a73f8d5f75c..a2bdea913c8 100644 --- a/packages/app/src/app/overmind/effects/gql/index.ts +++ b/packages/app/src/app/overmind/effects/gql/index.ts @@ -1,11 +1,5 @@ import { graphql } from 'overmind-graphql'; -import * as collaboratorsMutations from './collaborators/mutations'; -import * as collaboratorsQueries from './collaborators/queries'; -import * as collaboratorsSubscriptions from './collaborators/subscriptions'; -import * as commentsMutations from './comments/mutations'; -import * as commentsQueries from './comments/queries'; -import * as commentSubscriptions from './comments/subscriptions'; import * as teamsQueries from './teams/queries'; import * as dashboardQueries from './dashboard/queries'; @@ -17,21 +11,13 @@ import * as notificationsQueries from './notifications/queries'; import * as notificationsMutations from './notifications/mutations'; export default graphql({ - subscriptions: { - ...collaboratorsSubscriptions, - ...commentSubscriptions, - }, queries: { - ...collaboratorsQueries, - ...commentsQueries, ...teamsQueries, ...dashboardQueries, ...sidebarQueries, ...notificationsQueries, }, mutations: { - ...collaboratorsMutations, - ...commentsMutations, ...dashboardMutations, ...notificationsMutations, }, diff --git a/packages/app/src/app/overmind/effects/index.ts b/packages/app/src/app/overmind/effects/index.ts index ce415ee5621..72a017f0430 100755 --- a/packages/app/src/app/overmind/effects/index.ts +++ b/packages/app/src/app/overmind/effects/index.ts @@ -1,30 +1,16 @@ export { default as utils } from './utils'; -export { default as git } from './git'; export { default as api } from './api'; export { default as browser } from './browser'; export { default as connection } from './connection'; -export { default as jsZip } from './jsZip'; -export { default as live } from './live'; -export { default as moduleRecover } from './moduleRecover'; export { default as notifications } from './notifications'; export { default as router } from './router'; export { default as settingsStore } from './settingsStore'; -export { default as sse } from './sse'; export { default as http } from './http'; export { default as analytics } from './analytics'; export { default as notificationToast } from './notificationToast'; -export { default as vscode } from './vscode'; -export { default as vercel } from './deployment/vercel'; -export { default as githubPages } from './deployment/githubPages'; -export { default as netlify } from './deployment/netlify'; -export { default as prettyfier } from './prettyfier'; export { default as zip } from './zip'; -export { default as codesandboxApi } from './codesandboxApi'; -export { default as themes } from './themes'; export { default as executor } from './executor'; -export { default as stripe } from './stripe'; -export { default as preview } from './preview'; export { default as flows } from './flows'; export { default as gql } from './gql'; -export { default as algoliaSearch } from './algoliaSearch'; -export { default as browserExtension } from './browserExtension'; +export { default as live } from './live'; +export { default as git } from './git'; diff --git a/packages/app/src/app/overmind/effects/jsZip.ts b/packages/app/src/app/overmind/effects/jsZip.ts deleted file mode 100755 index a7aca238454..00000000000 --- a/packages/app/src/app/overmind/effects/jsZip.ts +++ /dev/null @@ -1,7 +0,0 @@ -import JSZip from 'jszip'; - -export default { - loadAsync(file) { - return JSZip.loadAsync(file); - }, -}; diff --git a/packages/app/src/app/overmind/effects/live/clients.test.ts b/packages/app/src/app/overmind/effects/live/clients.test.ts deleted file mode 100644 index 7edede25164..00000000000 --- a/packages/app/src/app/overmind/effects/live/clients.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { getTextOperation } from '@codesandbox/common/lib/utils/diff'; -import { CodeSandboxOTClient } from './clients'; - -describe('OTClient', () => { - it("it doesn't acknowledge the same revision twice", async () => { - const client = new CodeSandboxOTClient( - 0, - 'test', - (revision, operation) => Promise.resolve({}), - operation => {} - ); - - const op = getTextOperation('ab', 'a'); - - // The case we're trying to solve here: - // (1) applyClient -> (no ack received) - // ----- - // serverReconnect - // (2) applyClient. Sent by serverReconnect - // (1) resolves. Resolved because of Phoenix - // (2) resolves. Resolved because of server reconnect - - client.applyClient(op); - await client.sendOperation(0, op); - - expect(client.revision).toBe(1); - }); -}); diff --git a/packages/app/src/app/overmind/effects/live/clients.ts b/packages/app/src/app/overmind/effects/live/clients.ts deleted file mode 100755 index d20de33a655..00000000000 --- a/packages/app/src/app/overmind/effects/live/clients.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { - captureException, - logBreadcrumb, -} from '@codesandbox/common/lib/utils/analytics/sentry'; -import { Blocker, blocker } from 'app/utils/blocker'; -import { TextOperation, SerializedTextOperation } from 'ot'; - -import { OTClient, synchronized_ } from './ot/client'; - -export type SendOperationResponse = - | { - composed_operation: SerializedTextOperation; - revision: number; - } - | {}; - -export type SendOperation = ( - moduleShortid: string, - revision: number, - operation: TextOperation -) => Promise; - -export type ApplyOperation = ( - moduleShortid: string, - operation: TextOperation -) => void; - -export class CodeSandboxOTClient extends OTClient { - /* - We need to be able to wait for a client to go intro synchronized - state. The reason is that we want to send a "save" event when the - client is synchronized - */ - public awaitSynchronized: Blocker | null; - moduleShortid: string; - onSendOperation: ( - revision: number, - operation: TextOperation - ) => Promise; - - onApplyOperation: (operation: TextOperation) => void; - - constructor( - revision: number, - moduleShortid: string, - onSendOperation: ( - revision: number, - operation: TextOperation - ) => Promise, - onApplyOperation: (operation: TextOperation) => void - ) { - super(revision); - this.moduleShortid = moduleShortid; - this.lastAcknowledgedRevision = revision - 1; - this.onSendOperation = onSendOperation; - this.onApplyOperation = onApplyOperation; - } - - lastAcknowledgedRevision: number = -1; - sendOperation(revision: number, operation: TextOperation) { - // Whenever we send an operation we enable the blocker - // that lets us wait for its resolvment when moving back - // to synchronized state - if (!this.awaitSynchronized) { - this.awaitSynchronized = blocker(); - } - - return this.onSendOperation(revision, operation) - .then(result => { - logBreadcrumb({ - category: 'ot', - message: `Acknowledging ${JSON.stringify({ - moduleShortid: this.moduleShortid, - revision, - operation, - })}`, - }); - - if ( - 'revision' in result && - this.revision !== result.revision && - result.composed_operation.length - ) { - this.resync( - TextOperation.fromJSON(result.composed_operation), - result.revision - ); - } - - try { - this.safeServerAck(revision); - } catch (err) { - captureException( - new Error( - `Server Ack ERROR ${JSON.stringify({ - moduleShortid: this.moduleShortid, - currentRevision: this.revision, - currentState: this.state.name, - operation, - })}` - ) - ); - } - }) - .catch(error => { - // If an operation errors on the server we will reject - // the blocker, as an action might be waiting for it to resolve, - // creating a user friendly error related to trying to save - if (this.awaitSynchronized) { - this.awaitSynchronized.reject(error); - } - - throw error; - }); - } - - applyOperation(operation: TextOperation) { - this.onApplyOperation(operation); - } - - resetAwaitSynchronized() { - // If we are back in synchronized state we resolve the blocker - if (this.state === synchronized_ && this.awaitSynchronized) { - const awaitSynchronized = this.awaitSynchronized; - this.awaitSynchronized = null; - awaitSynchronized.resolve(); - } - } - - safeServerAck(revision: number) { - // We make sure to not acknowledge the same revision twice - if (this.lastAcknowledgedRevision < revision) { - this.lastAcknowledgedRevision = revision; - super.serverAck(); - } - - this.resetAwaitSynchronized(); - } - - applyClient(operation: TextOperation) { - logBreadcrumb({ - category: 'ot', - message: `Apply Client ${JSON.stringify({ - moduleShortid: this.moduleShortid, - currentRevision: this.revision, - currentState: this.state.name, - operation, - })}`, - }); - - super.applyClient(operation); - } - - applyServer(operation: TextOperation) { - logBreadcrumb({ - category: 'ot', - message: `Apply Server ${JSON.stringify({ - moduleShortid: this.moduleShortid, - currentRevision: this.revision, - currentState: this.state.name, - operation, - })}`, - }); - - super.applyServer(operation); - } - - serverReconnect() { - super.serverReconnect(); - } - - resync(operation: TextOperation, newRevision: number) { - this.applyServer(operation); - this.revision = newRevision; - } -} - -export class CodesandboxOTClientsManager { - private modules = new Map(); - private sendOperation: SendOperation; - private applyOperation: ApplyOperation; - constructor(sendOperation: SendOperation, applyOperation: ApplyOperation) { - this.sendOperation = sendOperation; - this.applyOperation = applyOperation; - } - - getAll() { - return Array.from(this.modules.values()); - } - - has(moduleShortid: string) { - return this.modules.has(moduleShortid); - } - - get(moduleShortid, revision = 0, force = false) { - let client = this.modules.get(moduleShortid); - - if (!client || force) { - client = this.create(moduleShortid, revision); - } - - return client!; - } - - create(moduleShortid, initialRevision) { - const client = new CodeSandboxOTClient( - initialRevision || 0, - moduleShortid, - (revision, operation) => - this.sendOperation(moduleShortid, revision, operation), - operation => { - this.applyOperation(moduleShortid, operation); - } - ); - this.modules.set(moduleShortid, client); - - return client; - } - - reset(moduleShortid, revision) { - this.modules.delete(moduleShortid); - this.create(moduleShortid, revision); - } - - clear() { - this.modules.clear(); - } -} diff --git a/packages/app/src/app/overmind/effects/live/index.ts b/packages/app/src/app/overmind/effects/live/index.ts index f6cf5f2ed42..f4ecb40b79e 100755 --- a/packages/app/src/app/overmind/effects/live/index.ts +++ b/packages/app/src/app/overmind/effects/live/index.ts @@ -1,38 +1,17 @@ import { - Directory, IModuleStateModule, LiveMessageEvent, - Module, RoomInfo, - UserSelection, - UserViewRange, } from '@codesandbox/common/lib/types'; -import { blocker } from 'app/utils/blocker'; -import { - captureException, - logBreadcrumb, -} from '@codesandbox/common/lib/utils/analytics/sentry'; import _debug from '@codesandbox/common/lib/utils/debug'; import VERSION from '@codesandbox/common/lib/version'; import { camelizeKeys } from 'humps'; -import { SerializedTextOperation, TextOperation } from 'ot'; -import { Channel, Presence, Socket } from 'phoenix'; -import * as uuid from 'uuid'; +import { Channel, Socket } from 'phoenix'; import { AxiosError } from 'axios'; -import { OPTIMISTIC_ID_PREFIX } from '../utils'; -import { CodesandboxOTClientsManager, SendOperationResponse } from './clients'; type Options = { - onApplyOperation(args: { - moduleShortid: string; - operation: TextOperation; - }): void; provideJwtToken(): Promise; - onOperationError(payload: { - moduleShortid: string; - moduleInfo: IModuleStateModule; - }): void; }; type JoinChannelResponse = { @@ -57,82 +36,13 @@ declare global { class Live { public socket: Socket; - private identifier = uuid.v4(); private pendingMessages = new Map(); private debug = _debug('cs:socket'); private channel: Channel | null; - private messageIndex = 0; - private clientsManager: CodesandboxOTClientsManager; - private presence: Presence; private provideJwtToken: () => Promise; - private onApplyOperation: (moduleShortid: string, operation: any) => void; - private onOperationError: (payload: { - moduleShortid: string; - moduleInfo: IModuleStateModule; - }) => void; - - private liveInitialized = blocker(); - - private connectionsCount = 0; - - private onSendOperation = async ( - moduleShortid: string, - revision: number, - operation: TextOperation - ) => { - logBreadcrumb({ - category: 'ot', - message: `Sending ${JSON.stringify({ - moduleShortid, - revision, - operation, - })}`, - }); - - return this.send( - 'operation', - { - moduleShortid, - operation: operation.toJSON(), - revision, - }, - 45000 - ).catch(error => { - logBreadcrumb({ - category: 'ot', - message: `ERROR ${JSON.stringify({ - moduleShortid, - revision, - operation, - message: error.message, - })}`, - }); - - captureException(error); - if (error.module_state) { - this.onOperationError({ - moduleInfo: error.module_state[moduleShortid], - moduleShortid, - }); - } - throw new Error( - 'The code was out of sync with the server, we had to reset the file' - ); - }); - }; initialize(options: Options) { this.provideJwtToken = options.provideJwtToken; - this.onOperationError = options.onOperationError; - this.onApplyOperation = (moduleShortid, operation) => - options.onApplyOperation({ - moduleShortid, - operation, - }); - this.clientsManager = new CodesandboxOTClientsManager( - this.onSendOperation, - this.onApplyOperation - ); } getSocket() { @@ -254,7 +164,6 @@ class Live { this.channel.onMessage = d => d; this.channel = null; this.pendingMessages.clear(); - this.messageIndex = 0; return resolve(resp); }) @@ -286,11 +195,6 @@ class Live { reconnect_token: result.reconnectToken, }); - this.presence = new Presence(this.channel!); - this.presence.onSync(() => { - this.connectionsCount = this.presence.list().length; - }); - resolve(result); }) .receive('error', (resp: JoinChannelErrorResponse) => { @@ -341,273 +245,6 @@ class Live { return data; }; } - - private send(event, payload: any = {}, timeout = 10000): Promise { - const _messageId = this.identifier + this.messageIndex++; - // eslint-disable-next-line - payload._messageId = _messageId; - this.pendingMessages.set(_messageId, payload); - - return new Promise((resolve, reject) => { - if (this.channel) { - this.channel - .push(event, payload, timeout) - .receive('ok', resolve) - .receive('error', reject) - .receive('timeout', () => { - const error = new Error(); - error.name = 'live-timeout'; - error.message = `Live timeout on '${event}'`; - reject(error); - }); - } else { - // we might try to send messages even when not on live, just - // ignore it - // @ts-ignore this is probably not safe but whatever - resolve(undefined); - } - }); - } - - awaitModuleSynced(moduleShortid: string) { - return Promise.resolve( - this.clientsManager.get(moduleShortid).awaitSynchronized?.promise - ); - } - - sendModuleUpdate(module: Module) { - return this.send('module:updated', { - type: 'module', - moduleShortid: module.shortid, - module, - }); - } - - sendDirectoryUpdate(directory: Directory) { - return this.send('directory:updated', { - type: 'directory', - directoryShortid: directory.shortid, - module: directory, - }); - } - - sendCodeUpdate(moduleShortid: string, operation: TextOperation) { - if (!operation) { - return; - } - - if (operation.ops.length === 1) { - const [op] = operation.ops; - if (typeof op === 'number' && op >= 0) { - // Useless to send a single retain operation, ignore - return; - } - } - - if (moduleShortid.startsWith(OPTIMISTIC_ID_PREFIX)) { - // Module is an optimistic module, we will send a full code update - // once the module has been created, until then, send nothing! - return; - } - - try { - this.clientsManager.get(moduleShortid).applyClient(operation); - } catch (e) { - e.name = 'OperationFailure'; - captureException(e); - // Something went wrong, probably a sync mismatch. Request new version - this.send('live:module_state', {}); - } - } - - sendExternalResourcesChanged(externalResources: string[]) { - return this.send('sandbox:external-resources', { - externalResources, - }); - } - - sendUserCurrentModule(moduleShortid: string) { - return this.send('user:current-module', { - moduleShortid, - }); - } - - sendDirectoryCreated(directory: Directory) { - return this.send('directory:created', { - type: 'directory', - module: directory, - }); - } - - sendDirectoryDeleted(directoryShortid: string) { - this.send('directory:deleted', { - type: 'directory', - directoryShortid, - }); - } - - sendModuleCreated(module: Module) { - return this.send('module:created', { - type: 'module', - moduleShortid: module.shortid, - module, - }); - } - - sendModuleDeleted(moduleShortid: string) { - return this.send('module:deleted', { - type: 'module', - moduleShortid, - }); - } - - sendMassCreatedModules(modules: Module[], directories: Directory[]) { - return this.send('module:mass-created', { - directories, - modules, - }); - } - - sendLiveMode(mode: RoomInfo['mode']) { - return this.send('live:mode', { - mode, - }); - } - - sendEditorAdded(liveUserId: string) { - return this.send('live:add-editor', { - editor_user_id: liveUserId, - }); - } - - sendEditorRemoved(liveUserId: string) { - return this.send('live:remove-editor', { - editor_user_id: liveUserId, - }); - } - - sendModuleSaved(module: Module) { - return this.send('module:saved', { - type: 'module', - module, - moduleShortid: module.shortid, - }); - } - - sendClosed() { - return this.send('live:close', {}); - } - - sendChat(message: string) { - return this.send('chat', { - message, - }); - } - - sendChatEnabled(enabled: boolean) { - return this.send('live:chat_enabled', { enabled }); - } - - sendModuleStateSyncRequest() { - return this.send('live:module_state', {}); - } - - sendUserViewRange( - moduleShortid: string | null, - liveUserId: string, - viewRange: UserViewRange - ) { - if (this.connectionsCount === 1) { - return Promise.resolve(); - } - - return this.send('user:view-range', { - liveUserId, - moduleShortid, - viewRange, - }); - } - - sendUserSelection( - moduleShortid: string | null, - liveUserId: string, - selection: UserSelection - ) { - if (this.connectionsCount === 1) { - return Promise.resolve(); - } - - return this.send('user:selection', { - liveUserId, - moduleShortid, - selection, - }); - } - - async saveModule(module: Module) { - const client = this.clientsManager.get(module.shortid); - await client.awaitSynchronized?.promise; - - return this.send<{ - saved_code: string; - updated_at: string; - inserted_at: string; - version: number; - }>('save', { - path: module.path, - revision: client.revision - 1, - }); - } - - waitForLiveReady() { - return this.liveInitialized.promise; - } - - markLiveReady() { - this.liveInitialized.resolve(undefined); - } - - reset() { - this.clientsManager.clear(); - this.liveInitialized.reject(undefined); - this.liveInitialized = blocker(); - } - - resetClient(moduleShortid: string, revision: number) { - this.clientsManager.reset(moduleShortid, revision); - } - - hasClient(moduleShortid: string) { - return this.clientsManager.has(moduleShortid); - } - - getClient(moduleShortid: string) { - return this.clientsManager.get(moduleShortid); - } - - getAllClients() { - return this.clientsManager.getAll(); - } - - applyClient(moduleShortid: string, operation: SerializedTextOperation) { - return this.clientsManager - .get(moduleShortid) - .applyClient(TextOperation.fromJSON(operation)); - } - - applyServer(moduleShortid: string, operation: SerializedTextOperation) { - return this.clientsManager - .get(moduleShortid) - .applyServer(TextOperation.fromJSON(operation)); - } - - serverAck(moduleShortid: string) { - return this.clientsManager.get(moduleShortid).serverAck(); - } - - createClient(moduleShortid: string, revision: number) { - return this.clientsManager.create(moduleShortid, revision); - } } export default new Live(); diff --git a/packages/app/src/app/overmind/effects/live/ot/client.ts b/packages/app/src/app/overmind/effects/live/ot/client.ts deleted file mode 100644 index 3f9b6c0a8dd..00000000000 --- a/packages/app/src/app/overmind/effects/live/ot/client.ts +++ /dev/null @@ -1,208 +0,0 @@ -// translation of https://github.com/djspiewak/cccp/blob/master/agent/src/main/scala/com/codecommit/cccp/agent/state.scala -/* eslint-disable react/no-access-state-in-setstate */ - -import { TextOperation } from 'ot'; - -interface IState { - name: string; - applyClient(client: OTClient, operation: TextOperation): IState; - applyServer(client: OTClient, operation: TextOperation): IState; - serverAck(client: OTClient): IState; - transformSelection(selection: T): T; - resend?(client: OTClient): void; -} - -// In the 'Synchronized' state, there is no pending operation that the client -// has sent to the server. -class Synchronized implements IState { - name = 'Synchronized'; - - applyClient(client: OTClient, operation: TextOperation) { - // When the user makes an edit, send the operation to the server and - // switch to the 'AwaitingConfirm' state - client.sendOperation(client.revision, operation); - return new AwaitingConfirm(operation); - } - - applyServer(client: OTClient, operation: TextOperation) { - // When we receive a new operation from the server, the operation can be - // simply applied to the current document - client.applyOperation(operation); - return this; - } - - serverAck(client: OTClient): IState { - throw new Error('There is no pending operation.'); - } - - // Nothing to do because the latest server state and client state are the same. - transformSelection(x) { - return x; - } -} - -// Singleton -export const synchronized_ = new Synchronized(); - -// In the 'AwaitingConfirm' state, there's one operation the client has sent -// to the server and is still waiting for an acknowledgement. -class AwaitingConfirm implements IState { - outstanding: TextOperation; - name = 'AwaitingConfirm'; - - constructor(outstanding: TextOperation) { - // Save the pending operation - this.outstanding = outstanding; - } - - applyClient(client: OTClient, operation: TextOperation) { - // When the user makes an edit, don't send the operation immediately, - // instead switch to 'AwaitingWithBuffer' state - return new AwaitingWithBuffer(this.outstanding, operation); - } - - applyServer(client: OTClient, operation: TextOperation) { - // This is another client's operation. Visualization: - // - // /\ - // this.outstanding / \ operation - // / \ - // \ / - // pair[1] \ / pair[0] (new outstanding) - // (can be applied \/ - // to the client's - // current document) - const pair = TextOperation.transform(this.outstanding, operation); - client.applyOperation(pair[1]); - return new AwaitingConfirm(pair[0]); - } - - serverAck(client: OTClient) { - // The client's operation has been acknowledged - // => switch to synchronized state - return synchronized_; - } - - transformSelection(selection: any) { - return selection.transform(this.outstanding); - } - - resend(client: OTClient) { - // The confirm didn't come because the client was disconnected. - // Now that it has reconnected, we resend the outstanding operation. - client.sendOperation(client.revision, this.outstanding); - } -} - -// In the 'AwaitingWithBuffer' state, the client is waiting for an operation -// to be acknowledged by the server while buffering the edits the user makes -class AwaitingWithBuffer implements IState { - outstanding: TextOperation; - buffer: TextOperation; - - name = 'AwaitingWithBuffer'; - - constructor(outstanding: TextOperation, buffer: TextOperation) { - // Save the pending operation and the user's edits since then - this.outstanding = outstanding; - this.buffer = buffer; - } - - applyClient(client: OTClient, operation: TextOperation) { - // Compose the user's changes onto the buffer - const newBuffer = this.buffer.compose(operation); - return new AwaitingWithBuffer(this.outstanding, newBuffer); - } - - applyServer(client: OTClient, operation: TextOperation) { - // Operation comes from another client - // - // /\ - // this.outstanding / \ operation - // / \ - // /\ / - // this.buffer / \* / pair1[0] (new outstanding) - // / \/ - // \ / - // pair2[1] \ / pair2[0] (new buffer) - // the transformed \/ - // operation -- can - // be applied to the - // client's current - // document - // - // * pair1[1] - const transform = TextOperation.transform; - const pair1 = transform(this.outstanding, operation); - const pair2 = transform(this.buffer, pair1[1]); - client.applyOperation(pair2[1]); - return new AwaitingWithBuffer(pair1[0], pair2[0]); - } - - serverAck(client: OTClient) { - // The pending operation has been acknowledged - // => send buffer - client.sendOperation(client.revision, this.buffer); - return new AwaitingConfirm(this.buffer); - } - - transformSelection(selection: any) { - return selection.transform(this.outstanding).transform(this.buffer); - } - - resend(client: OTClient) { - // The confirm didn't come because the client was disconnected. - // Now that it has reconnected, we resend the outstanding operation. - client.sendOperation(client.revision, this.outstanding); - } -} - -export abstract class OTClient { - revision: number; - state: IState; - - constructor(revision: number) { - this.revision = revision; - this.state = synchronized_; - } - - setState(state: IState) { - this.state = state; - } - - // Call this method when the user changes the document. - applyClient(operation: TextOperation) { - this.setState(this.state.applyClient(this, operation)); - } - - // Call this method with a new operation from the server - applyServer(operation: TextOperation) { - this.revision++; - this.setState(this.state.applyServer(this, operation)); - } - - serverAck() { - this.revision++; - this.setState(this.state.serverAck(this)); - } - - serverReconnect() { - if (typeof this.state.resend === 'function') { - this.state.resend(this); - } - } - - // Transforms a selection from the latest known server state to the current - // client state. For example, if we get from the server the information that - // another user's cursor is at position 3, but the server hasn't yet received - // our newest operation, an insertion of 5 characters at the beginning of the - // document, the correct position of the other user's cursor in our current - // document is 8. - transformSelection(selection) { - return this.state.transformSelection(selection); - } - - abstract sendOperation(revision: number, operation: TextOperation): void; - - abstract applyOperation(operation: TextOperation): void; -} diff --git a/packages/app/src/app/overmind/effects/moduleRecover.ts b/packages/app/src/app/overmind/effects/moduleRecover.ts deleted file mode 100644 index b26aad0a1de..00000000000 --- a/packages/app/src/app/overmind/effects/moduleRecover.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Module } from '@codesandbox/common/lib/types'; - -const getKey = (id: string, moduleShortid: string) => - `recover:${id}:${moduleShortid}:code`; - -export type RecoverData = { - code: string; - version: number; - timestamp: number; - sandboxId: string; -}; - -export default { - save(sandboxId: string, version: number, module: Module) { - try { - localStorage.setItem( - getKey(sandboxId, module.shortid), - JSON.stringify({ - code: module.code, - version, - timestamp: new Date().getTime(), - sandboxId, - }) - ); - } catch (e) { - // Too bad - } - }, - - get(sandboxId: string, moduleShortid: string): RecoverData | null { - return JSON.parse( - localStorage.getItem(getKey(sandboxId, moduleShortid)) || 'null' - ); - }, - - remove(sandboxId: string, module: Module) { - try { - const recoverData = this.get(sandboxId, module.shortid); - if (recoverData && recoverData.code === module.code) { - localStorage.removeItem(getKey(sandboxId, module.shortid)); - } - } catch (e) { - // Too bad - } - }, - - clearSandbox(sandboxId: string) { - try { - Object.keys(localStorage) - .filter(key => key.startsWith(`recover:${sandboxId}`)) - .forEach(key => { - localStorage.removeItem(key); - }); - } catch (e) { - // Too bad - } - }, - - getRecoverList(sandboxId: string, modules: Module[]) { - const localKeys = Object.keys(localStorage).filter(key => - key.startsWith(`recover:${sandboxId}`) - ); - - return modules - .filter(m => localKeys.includes(getKey(sandboxId, m.shortid))) - .map(module => { - const key = getKey(sandboxId, module.shortid); - - try { - const recoverData: RecoverData = JSON.parse( - localStorage.getItem(key) || 'null' - ); - - if (recoverData) { - return { recoverData, module }; - } - } catch (e) { - // Too bad - } - - return null; - }) - .filter(Boolean) as Array<{ recoverData: RecoverData; module: Module }>; - }, -}; diff --git a/packages/app/src/app/overmind/effects/prettyfier.ts b/packages/app/src/app/overmind/effects/prettyfier.ts deleted file mode 100755 index 41a1f18a311..00000000000 --- a/packages/app/src/app/overmind/effects/prettyfier.ts +++ /dev/null @@ -1,23 +0,0 @@ -import prettify from 'app/utils/prettify'; -import { PrettierConfig, Module } from '@codesandbox/common/lib/types'; - -type Options = { - getPrettierConfig(): PrettierConfig; - getCurrentModule(): Module; -}; - -let _options: Options; - -export default { - initialize(options: Options) { - _options = options; - }, - prettify(moduleId: string, title: string, code: string): Promise { - return prettify( - title, - () => code, - _options.getPrettierConfig(), - () => _options.getCurrentModule().id === moduleId - ); - }, -}; diff --git a/packages/app/src/app/overmind/effects/preview.ts b/packages/app/src/app/overmind/effects/preview.ts deleted file mode 100644 index cfc908bd383..00000000000 --- a/packages/app/src/app/overmind/effects/preview.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { dispatch, listen } from 'codesandbox-api'; -import BasePreview from '@codesandbox/common/lib/components/Preview'; -import { blocker } from 'app/utils/blocker'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { Sandbox } from '@codesandbox/common/lib/types'; - -let _preview = blocker(); - -export default { - initialize() {}, - initializePreview(preview: any) { - _preview.resolve(preview); - return () => { - _preview = blocker(); - }; - }, - async executeCodeImmediately({ - initialRender = false, - showFullScreen = false, - } = {}) { - const preview = await _preview.promise; - preview.executeCodeImmediately(initialRender, showFullScreen); - }, - async executeCode() { - const preview = await _preview.promise; - preview.executeCode(); - }, - async refresh() { - const preview = await _preview.promise; - preview.handleRefresh(); - }, - async updateAddressbarUrl() { - const preview = await _preview.promise; - preview.updateAddressbarUrl(); - }, - async getIframeBoundingRect() { - const preview = await _preview.promise; - - return preview.element.getBoundingClientRect(); - }, - async getPreviewPath() { - const preview = await _preview.promise; - - let path = preview.state.urlInAddressBar; - - path = path.replace('http://', '').replace('https://', ''); - - return path.substr(path.indexOf('/')); - }, - takeExtensionScreenshot(): Promise { - return new Promise((resolve, reject) => { - let waitForExtension; - const extensionListener = event => { - if (event.data.type === 'extension-screenshot-taken') { - clearTimeout(waitForExtension); - window.removeEventListener('message', extensionListener); - resolve(event.data.url); - } - }; - waitForExtension = setTimeout(() => { - reject( - new Error( - 'Extension did not create screenshot, please try to refresh browser' - ) - ); - }, 3000); - window.addEventListener('message', extensionListener); - window.postMessage( - { - type: 'extension-screenshot', - }, - '*' - ); - }); - }, - takeScreenshot(isPrivateSandbox: boolean): Promise { - return new Promise((resolve, reject) => { - let timeout; - const start = Date.now(); - - const disposeListener = listen((data: any) => { - if (data.type === 'screenshot-generated') { - clearTimeout(timeout); - const waitAtLeastMs = 250; - const waitedMs = Date.now() - start; - - setTimeout( - () => resolve(data.screenshot), - waitedMs > waitAtLeastMs ? 0 : waitAtLeastMs - waitedMs - ); - } - }); - - timeout = setTimeout(() => { - disposeListener(); - reject(new Error('Creating screenshot timed out')); - }, 3000); - - dispatch({ - type: 'take-screenshot', - data: { - isPrivateSandbox, - }, - }); - }); - }, - showCommentCursor() { - dispatch({ - type: 'show-screenshot-cursor', - }); - }, - hideCommentCursor() { - dispatch({ - type: 'hide-screenshot-cursor', - }); - }, - createScreenshot({ - screenshotSource, - bubbleSource, - cropWidth, - cropHeight, - x, - y, - scale, - }: { - screenshotSource: string; - bubbleSource: string; - cropWidth: number; - cropHeight: number; - x: number; - y: number; - scale: number; - }): Promise { - const screenshotResolver = new Promise(resolve => { - const image = new Image(); - image.onload = () => { - resolve(image); - }; - image.src = screenshotSource; - }); - - const bubbleResolver = new Promise(resolve => { - const image = new Image(); - image.onload = () => { - resolve(image); - }; - image.src = bubbleSource; - }); - - return Promise.all([screenshotResolver, bubbleResolver]).then( - ([screenshot, bubble]) => { - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d')!; - const dpr = window.devicePixelRatio || 1; - const scaledX = (x / scale) * dpr; - const scaledY = (y / scale) * dpr; - const scaledHalfCropWidth = (cropWidth / 2) * dpr; - const scaledHalfCropHeight = (cropHeight / 2) * dpr; - - const rightSideSpace = Math.min( - screenshot.width - scaledX, - scaledHalfCropWidth - ); - const bottomSideSpace = Math.min( - screenshot.height - scaledY, - scaledHalfCropHeight - ); - const leftSideSpace = Math.min(scaledX, scaledHalfCropWidth); - const topSideSpace = Math.min(scaledY, scaledHalfCropHeight); - - // Make sure we don't make our canvas bigger than our screenshot can fill - const width = Math.min( - leftSideSpace + rightSideSpace, - screenshot.width - ); - const height = Math.min( - bottomSideSpace + topSideSpace, - screenshot.height - ); - - // Offset the screenshot enough to show the comment bubble, but not more - // than possible before we end up with transparent pixels - const sx = Math.min(scaledX - leftSideSpace, screenshot.width - width); - const sy = Math.min(scaledY - topSideSpace, screenshot.height - height); - - const bubbleX = scaledX - sx; - const bubbleY = scaledY - sy; - - canvas.width = width; - canvas.height = height; - - ctx.drawImage(screenshot, sx, sy, width, height, 0, 0, width, height); - ctx.restore(); - ctx.drawImage(bubble, bubbleX, bubbleY); - - return canvas.toDataURL(); - } - ); - }, - canAddComments(currentSandbox: Sandbox) { - return Boolean( - currentSandbox.featureFlags.comments && - hasPermission(currentSandbox.authorization, 'comment') - ); - }, -}; diff --git a/packages/app/src/app/overmind/effects/router.ts b/packages/app/src/app/overmind/effects/router.ts index b13fa31e766..91b7223d356 100755 --- a/packages/app/src/app/overmind/effects/router.ts +++ b/packages/app/src/app/overmind/effects/router.ts @@ -1,90 +1,10 @@ -import { GitInfo } from '@codesandbox/common/lib/types'; -import { getSandboxOptions } from '@codesandbox/common/lib/url'; -import { sandboxUrl } from '@codesandbox/common/lib/utils/url-generator'; import history from '../../utils/history'; export default new (class RouterEffect { - replaceSandboxUrl({ - id, - alias, - git, - isV2, - }: { - id?: string | null; - alias?: string | null; - git?: GitInfo | null; - isV2?: boolean; - }) { - window.history.replaceState({}, '', sandboxUrl({ id, alias, git, isV2 })); - } - - updateSandboxUrl( - { - id, - alias, - git, - v2, - isSse, - }: { - id?: string | null; - alias?: string | null; - git?: GitInfo | null; - v2?: boolean; - isSse?: boolean; - }, - { - openInNewWindow = false, - hasBetaEditorExperiment = false, - }: { openInNewWindow?: boolean; hasBetaEditorExperiment?: boolean } = {} - ) { - const url = sandboxUrl( - { id, alias, isV2: v2, isSse }, - hasBetaEditorExperiment - ); - - if (openInNewWindow) { - window.open(url, '_blank'); - } else if (v2 || (!isSse && hasBetaEditorExperiment)) { - window.location.href = url; - } else { - history.push(url); - } - } - - redirectToNewSandbox() { - history.push('/s/new'); - } - - redirectToSandboxWizard() { - history.replace('/s/'); - } - redirectToDashboard() { history.replace('/dashboard/home'); } - getSandboxOptions() { - return getSandboxOptions(decodeURIComponent(document.location.href)); - } - - getCommentId() { - return this.getParameter('comment'); - } - - createCommentUrl(id: string) { - return `${window.location.origin}${window.location.pathname}?comment=${id}`; - } - - replace(url: string) { - const origin = new URL(url).origin; - history.replace(url.replace(origin, '')); - } - - getParameter(key: string): string | null { - const currentUrl = new URL(location.href); - return currentUrl.searchParams.get(key); - } - clearWorkspaceId(): void { const searchParams = new URLSearchParams(location.search); searchParams.delete('workspace'); diff --git a/packages/app/src/app/overmind/effects/sse.ts b/packages/app/src/app/overmind/effects/sse.ts deleted file mode 100755 index 77d34c521a9..00000000000 --- a/packages/app/src/app/overmind/effects/sse.ts +++ /dev/null @@ -1,13 +0,0 @@ -import store from 'store/dist/store.modern'; - -export default { - get() { - return store.get('sse'); - }, - set(sse) { - return store.set('sse', sse); - }, - reset() { - return store.set('sse', null); - }, -}; diff --git a/packages/app/src/app/overmind/effects/stripe.ts b/packages/app/src/app/overmind/effects/stripe.ts deleted file mode 100644 index 5122a2946ec..00000000000 --- a/packages/app/src/app/overmind/effects/stripe.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { STRIPE_API_KEY } from '@codesandbox/common/lib/utils/config'; - -declare let Stripe: any; - -function loadScript(path: string) { - return new Promise(resolve => { - if (typeof document !== 'undefined') { - const script = document.createElement('script'); - script.onload = resolve; - script.async = true; - script.type = 'text/javascript'; - script.src = path; - document.head.appendChild(script); - } - }); -} - -let localStripeVar; -const getStripe = async (): Promise => { - if (typeof Stripe === 'undefined') { - await loadScript('https://js.stripe.com/v3/'); - } - - if (!localStripeVar) { - localStripeVar = Stripe(STRIPE_API_KEY); - } - - return localStripeVar; -}; - -export default { - handleCardPayment: async (paymentIntent: string) => { - const stripe = await getStripe(); - - return stripe.handleCardPayment(paymentIntent); - }, -}; diff --git a/packages/app/src/app/overmind/effects/themes.ts b/packages/app/src/app/overmind/effects/themes.ts deleted file mode 100755 index 3c18334ef80..00000000000 --- a/packages/app/src/app/overmind/effects/themes.ts +++ /dev/null @@ -1,3 +0,0 @@ -import themes from '@codesandbox/common/lib/themes'; - -export default themes; diff --git a/packages/app/src/app/overmind/effects/utils/greensock.ts b/packages/app/src/app/overmind/effects/utils/greensock.ts deleted file mode 100644 index 2cb5c97c1f3..00000000000 --- a/packages/app/src/app/overmind/effects/utils/greensock.ts +++ /dev/null @@ -1,60 +0,0 @@ -const GREENSOCK_KEYWORDS = [ - 'animate', - 'animation', - 'cssrule', - 'cssruleplugin', - 'customease', - 'draggable', - 'easelplugin', - 'flip', - 'flipplugin', - 'gasp', - 'greensock', - 'gsap', - 'modifiersplugin', - 'motionpathplugin', - 'pixiplugin', - 'scrollintent', - 'scrollsmoother', - 'scrollto', - 'scrolltoplugin', - 'scrolltrigger', - 'textplugin', - 'timeline', - 'tween', -]; - -const GREENSOCK_MEMBER_KEYWORDS = [ - 'custombounce', - 'customwiggle', - 'drawsvg', - 'drawsvgplugin', - 'gsdevtools', - 'inertia', - 'inertiaplugin', - 'morph', - 'morphsvg', - 'morphsvgplugin', - 'motionpathhelper', - 'physics2d', - 'physics2dplugin', - 'physicsprops', - 'physicspropsplugin', - 'scrambletext', - 'scrambletextplugin', - 'splittext', -]; - -export const GREENSOCK_ALIASES = ['gsap', 'gsap-trial']; - -export function getGreensockAlias(name: string): string | undefined { - const lowercaseName = name.toLocaleLowerCase(); - if (GREENSOCK_KEYWORDS.includes(lowercaseName)) { - return 'gsap'; - } - if (GREENSOCK_MEMBER_KEYWORDS.includes(lowercaseName)) { - return 'gsap-trial'; - } - - return undefined; -} diff --git a/packages/app/src/app/overmind/effects/vscode/Linter.ts b/packages/app/src/app/overmind/effects/vscode/Linter.ts deleted file mode 100644 index 2b4fdfd8205..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/Linter.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { actions, dispatch } from 'codesandbox-api'; -import debounce from 'lodash-es/debounce'; -// @ts-ignore -// eslint-disable-next-line -import LinterWorker from 'worker-loader?publicPath=/&name=monaco-linter.[hash:8].worker.js!./LinterWorker'; - -import { getCurrentModelPath } from './utils'; - -const requireAMDModule = paths => - new Promise(resolve => (window as any).require(paths, () => resolve())); - -export class Linter { - private worker: LinterWorker; - private editor; - private monaco; - private isDisposed: boolean = false; - constructor(editor, monaco) { - this.editor = editor; - this.monaco = monaco; - this.worker = new LinterWorker(); - - // This should be disposed? - this.worker.addEventListener('message', this.onMessage); - } - - dispose(): null { - this.clearErrors(); - this.worker.removeEventListener('message', this.onMessage); - this.worker.terminate(); - this.isDisposed = true; - - return null; - } - - private clearErrors = () => { - dispatch( - actions.correction.clear(getCurrentModelPath(this.editor), 'eslint') - ); - }; - - private onMessage = event => { - const { markers, version } = event.data; - const activeEditor = this.editor.getActiveCodeEditor(); - - if (activeEditor && activeEditor.getModel()) { - this.clearErrors(); - - if (version === activeEditor.getModel().getVersionId()) { - markers.forEach(marker => { - dispatch( - actions.correction.show(marker.message, { - line: marker.startLineNumber, - column: marker.startColumn, - lineEnd: marker.endLineNumber, - columnEnd: marker.endColumn, - source: 'eslint', - severity: marker.severity === 2 ? 'warning' : 'notice', - path: getCurrentModelPath(this.editor), - }) - ); - }); - } - } - }; - - lint = debounce( - async ( - code: string, - title: string, - version: number, - template: string, - dependencies: { [dep: string]: string } - ) => { - if (!title || this.isDisposed) { - return; - } - - const mode = (await this.getMonacoMode(title)) || ''; - - if ( - ['javascript', 'typescript', 'typescriptreact', 'vue'].includes(mode) - ) { - this.worker.postMessage({ - code, - title, - version, - template, - dependencies, - }); - } - }, - 100 - ); - - private async getMonacoMode(title: string) { - if (title == null) return 'javascript'; - - const kind = title.match(/\.([^.]*)$/); - - if (kind) { - if (kind[1] === 'css') return 'css'; - if (kind[1] === 'scss') return 'scss'; - if (kind[1] === 'json') return 'json'; - if (kind[1] === 'html') return 'html'; - if (kind[1] === 'svelte') return 'html'; - if (kind[1] === 'vue') { - if ( - this.monaco.languages.getLanguages && - !this.monaco.languages.getLanguages().find(l => l.id === 'vue') - ) { - await requireAMDModule(['vs/language/vue/monaco.contribution']); - } - return 'vue'; - } - if (kind[1] === 'less') return 'less'; - if (kind[1] === 'md') return 'markdown'; - if (/jsx?$/.test(kind[1])) return 'javascript'; - if (/tsx?$/.test(kind[1])) return 'typescript'; - } - - return undefined; - } -} diff --git a/packages/app/src/app/overmind/effects/vscode/ModelsHandler.ts b/packages/app/src/app/overmind/effects/vscode/ModelsHandler.ts deleted file mode 100644 index 8a85867eb21..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/ModelsHandler.ts +++ /dev/null @@ -1,905 +0,0 @@ -import { resolveModule } from '@codesandbox/common/lib/sandbox/modules'; -import { - EditorSelection, - Module, - Sandbox, - UserSelection, -} from '@codesandbox/common/lib/types'; -import { getTextOperation } from '@codesandbox/common/lib/utils/diff'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { indexToLineAndColumn } from 'app/overmind/utils/common'; -import { actions, dispatch } from 'codesandbox-api'; -import { css } from 'glamor'; -import { TextOperation } from 'ot'; - -import { getCurrentModelPath } from './utils'; - -// @ts-ignore -const fadeIn = css.keyframes('fadeIn', { - // optional name - '0%': { opacity: 0 }, - '100%': { opacity: 1 }, -}); - -// @ts-ignore -const fadeOut = css.keyframes('fadeOut', { - // optional name - '0%': { opacity: 1 }, - '100%': { opacity: 0 }, -}); - -export type OnFileChangeData = { - moduleShortid: string; - title: string; - code: string; - event: any; - model: any; -}; - -export type onSelectionChangeData = UserSelection; - -export type OnOperationAppliedData = { - moduleShortid: string; - operation: TextOperation; - title: string; - code: string; - model: any; -}; - -export type OnFileChangeCallback = (data: OnFileChangeData) => void; - -export type OnOperationAppliedCallback = (data: OnOperationAppliedData) => void; - -export type ModuleModel = { - changeListener: { dispose: Function } | null; - currentLine: number; - path: string; - model: null | any; - comments: Array<{ commentId: string; range: [number, number] }>; - currentCommentDecorations: string[]; -}; - -export class ModelsHandler { - public isApplyingOperation: boolean = false; - private moduleModels: { [path: string]: ModuleModel } = {}; - private modelAddedListener: { dispose: Function }; - private modelRemovedListener: { dispose: Function }; - private onChangeCallback: OnFileChangeCallback; - private onOperationAppliedCallback: OnOperationAppliedCallback; - private sandbox: Sandbox; - private editorApi; - private monaco; - private userClassesGenerated = {}; - private userSelectionDecorations = {}; - private currentCommentThreadId: string | null = null; - constructor( - editorApi, - monaco, - sandbox: Sandbox, - onChangeCallback: OnFileChangeCallback, - onOperationAppliedCallback: OnOperationAppliedCallback - ) { - this.editorApi = editorApi; - this.monaco = monaco; - this.sandbox = sandbox; - this.onChangeCallback = onChangeCallback; - this.onOperationAppliedCallback = onOperationAppliedCallback; - this.listenForChanges(); - } - - public dispose(): null { - this.modelAddedListener.dispose(); - this.modelRemovedListener.dispose(); - Object.keys(this.moduleModels).forEach(path => { - const changeListener = this.moduleModels[path].changeListener; - if (changeListener) { - changeListener.dispose(); - } - }); - this.moduleModels = {}; - - return null; - } - - public async syncModule(module: Module) { - const fileModel = this.editorApi.textFileService - .getFileModels() - .find( - fileModelItem => - fileModelItem.resource.path === '/sandbox' + module.path - ); - - if (fileModel) { - this.isApplyingOperation = true; - fileModel.revert(); - this.isApplyingOperation = false; - } - } - - private getModuleModelByPath(path: string): ModuleModel | undefined { - const fullPath = '/sandbox' + path; - - return this.moduleModels[fullPath]; - } - - private getOrCreateModuleModelByPath(path: string): ModuleModel { - const fullPath = '/sandbox' + path; - this.moduleModels[fullPath] = this.getModuleModelByPath(path) || { - changeListener: null, - model: null, - currentLine: 0, - path: fullPath, - comments: [], - currentCommentDecorations: [], - }; - - return this.moduleModels[fullPath]; - } - - public updateLineCommentIndication(model: any, lineNumber: number) { - const moduleModel = this.moduleModels[model.uri.path]; - - moduleModel.currentLine = lineNumber; - - const newDecorationComments = this.createCommentDecorations( - moduleModel.comments, - model, - this.currentCommentThreadId, - moduleModel.currentLine - ); - moduleModel.currentCommentDecorations = model.deltaDecorations( - moduleModel.currentCommentDecorations, - newDecorationComments - ); - } - - public clearComments() { - Object.values(this.moduleModels).forEach(moduleModel => { - if (!moduleModel.model) { - return; - } - moduleModel.comments = []; - moduleModel.currentCommentDecorations = moduleModel.model.deltaDecorations( - moduleModel.currentCommentDecorations, - [] - ); - }); - } - - public isModuleOpened(module: Module) { - const moduleModel = this.getModuleModelByPath(module.path); - return Boolean(moduleModel?.model); - } - - public changeModule = async (module: Module) => { - const moduleModel = this.getOrCreateModuleModelByPath(module.path); - - if (getCurrentModelPath(this.editorApi) !== module.path) { - await this.editorApi.openFile(module.path); - } - - moduleModel.model = await this.editorApi.textFileService.models - .loadOrCreate(this.monaco.Uri.file('/sandbox' + module.path)) - .then(textFileEditorModel => textFileEditorModel.load()) - .then(textFileEditorModel => textFileEditorModel.textEditorModel); - - const model = moduleModel.model; - - if (this.sandbox.featureFlags.comments) { - const newDecorationComments = this.createCommentDecorations( - moduleModel.comments, - model, - this.currentCommentThreadId, - moduleModel.currentLine - ); - moduleModel.currentCommentDecorations = model.deltaDecorations( - moduleModel.currentCommentDecorations, - newDecorationComments - ); - } - - return moduleModel.model; - }; - - public async applyComments( - commentThreadsByPath: { - [path: string]: Array<{ - commentId: string; - range: [number, number]; - }>; - }, - currentCommentThreadId: string | null - ) { - // We keep a local reference to the current commentThread id, - // because when opening modules we want to highlight any currently - // selected comment - this.currentCommentThreadId = currentCommentThreadId; - - // Ensure we have the moduleModels - Object.keys(commentThreadsByPath).forEach(path => { - this.getOrCreateModuleModelByPath(path).comments = - commentThreadsByPath[path]; - }); - - // Apply the decorations - Object.keys(this.moduleModels).forEach(path => { - const moduleModel = this.moduleModels[path]; - const model = moduleModel.model; - - if (!model) { - return; - } - - // Clean out any removed comments - const currentCommentIds = ( - commentThreadsByPath[path.replace('/sandbox', '')] || [] - ).map(comment => comment.commentId); - moduleModel.comments = moduleModel.comments.filter(comment => - currentCommentIds.includes(comment.commentId) - ); - - const existingDecorationComments = moduleModel.currentCommentDecorations; - const newDecorationComments = this.createCommentDecorations( - moduleModel.comments, - model, - currentCommentThreadId, - moduleModel.currentLine - ); - - moduleModel.currentCommentDecorations = model.deltaDecorations( - existingDecorationComments, - newDecorationComments - ); - }); - } - - public async updateTabsPath(oldPath: string, newPath: string) { - const oldModelPath = '/sandbox' + oldPath; - const newModelPath = '/sandbox' + newPath; - - return Promise.all( - Object.keys(this.moduleModels).map(path => { - if (oldModelPath === path && this.moduleModels[path].model) { - const model = this.moduleModels[path].model; - - // This runs remove/add automatically - return this.editorApi.textFileService.move( - model.uri, - this.monaco.Uri.file(newModelPath) - ); - } - - return Promise.resolve(); - }) - ); - } - - public async applyOperation(moduleShortid: string, operation: TextOperation) { - const module = this.sandbox.modules.find(m => m.shortid === moduleShortid); - - if (!module) { - return; - } - - const moduleModel = this.getOrCreateModuleModelByPath(module.path); - - const modelEditor = this.editorApi.editorService.editors.find( - editor => editor.resource && editor.resource.path === moduleModel.path - ); - - // We keep a reference to the model on our own. We keep it as a - // promise, because there might be multiple operations fired before - // the model is actually resolved. This creates a "natural" queue - if (!moduleModel.model) { - if (modelEditor) { - moduleModel.model = await modelEditor.textModelReference.then( - ref => ref.object.textEditorModel - ); - } else { - moduleModel.model = await this.editorApi.textFileService.models - .loadOrCreate(this.monaco.Uri.file(moduleModel.path)) - .then(model => model.textEditorModel); - } - } - - const model = moduleModel.model; - - this.isApplyingOperation = true; - this.applyOperationToModel(operation, false, model); - this.isApplyingOperation = false; - this.onOperationAppliedCallback({ - code: model.getValue(), - operation, - moduleShortid: module.shortid, - title: module.title, - model, - }); - } - - /** - * Sets the code of a model in VSCode. This means that we directly change the in-memory - * model and the user will immediately see the code. - * @param module The module to apply the changes of - * @param triggerChangeEvent Whether we should trigger this event to listeners listening to the model (for eg. live) - */ - public setModuleCode(module: Module, triggerChangeEvent = false) { - const moduleModel = this.getModuleModelByPath(module.path); - const model = moduleModel?.model; - - if (!model) { - return; - } - - const oldCode = model.getValue(); - const changeOperation = getTextOperation(oldCode, module.code); - if (!triggerChangeEvent) { - this.isApplyingOperation = true; - } - this.applyOperationToModel(changeOperation, false, model); - if (!triggerChangeEvent) { - this.isApplyingOperation = false; - } - } - - public clearUserSelections(userId: string) { - const decorations = Object.keys(this.userSelectionDecorations).filter(d => - d.startsWith(userId) - ); - Object.keys(this.moduleModels).forEach(key => { - const moduleModel = this.moduleModels[key]; - - if (!moduleModel?.model) { - return; - } - - const model = moduleModel.model; - - decorations.forEach(decorationId => { - const userDecorationIdPrefix = this.getSelectionDecorationId( - userId, - model.id - ); - if (decorationId.startsWith(userDecorationIdPrefix)) { - this.userSelectionDecorations[decorationId] = model.deltaDecorations( - this.userSelectionDecorations[decorationId] || [], - [] - ); - } - }); - }); - } - - private getSelectionDecorationId = ( - userId: string, - modelId: string = '', - shortid: string = '' - ) => [userId, modelId, shortid].join('|').replace(/\|\|$/, '|'); - - private cleanUserSelections = ( - model: any, - moduleShortid: string, - userSelectionsToKeep: EditorSelection[] - ) => { - const existingSelectionDecorations = Object.keys( - this.userSelectionDecorations - ).filter(s => s.endsWith([model.id, moduleShortid].join('|'))); - - const newUserSelections = {}; - for (let i = 0; i < userSelectionsToKeep.length; i++) { - newUserSelections[userSelectionsToKeep[i].userId] = - userSelectionsToKeep[i]; - } - - existingSelectionDecorations.forEach(existingDecorationId => { - const userId = existingDecorationId.split('|')[0]; - if (!newUserSelections[userId]) { - const decorationId = this.getSelectionDecorationId( - userId, - model.id, - moduleShortid - ); - this.userSelectionDecorations[decorationId] = model.deltaDecorations( - this.userSelectionDecorations[decorationId] || [], - [] - ); - } - }); - }; - - nameTagTimeouts: { [name: string]: number } = {}; - - public updateUserSelections( - module: Module, - userSelections: EditorSelection[], - showNameTag = true - ) { - const moduleModel = this.getModuleModelByPath(module.path); - - if (!moduleModel?.model) { - return; - } - - const model = moduleModel.model; - const lines = model.getLinesContent() || []; - - this.cleanUserSelections(model, module.shortid, userSelections); - - userSelections.forEach((data: EditorSelection) => { - const { userId } = data; - - const decorationId = this.getSelectionDecorationId( - userId, - model.id, - module.shortid - ); - if (data.selection === null) { - this.userSelectionDecorations[decorationId] = model.deltaDecorations( - this.userSelectionDecorations[decorationId] || [], - [] - ); - - return; - } - - const decorations: Array<{ - range: any; - options: { - className: string; - }; - }> = []; - const { selection, color, name } = data; - - const getCursorDecoration = (position, className) => { - const cursorPos = indexToLineAndColumn(lines, position); - - return { - range: new this.monaco.Range( - cursorPos.lineNumber, - cursorPos.column, - cursorPos.lineNumber, - cursorPos.column - ), - options: { - className: `${this.userClassesGenerated[className]}`, - }, - }; - }; - - const getSelectionDecoration = (start, end, className) => { - const from = indexToLineAndColumn(lines, start); - const to = indexToLineAndColumn(lines, end); - - return { - range: new this.monaco.Range( - from.lineNumber, - from.column, - to.lineNumber, - to.column - ), - options: { - className: this.userClassesGenerated[className], - stickiness: 3, // GrowsOnlyWhenTypingAfter - }, - }; - }; - const prefix = color.join('-') + userId; - const cursorClassName = prefix + '-cursor'; - const nameTagClassName = prefix + '-nametag'; - const secondaryCursorClassName = prefix + '-secondary-cursor'; - const selectionClassName = prefix + '-selection'; - const secondarySelectionClassName = prefix + '-secondary-selection'; - - if (selection && color) { - const nameStyles = { - content: name, - position: 'absolute', - bottom: '100%', - backgroundColor: `rgb(${color[0]}, ${color[1]}, ${color[2]})`, - zIndex: 200, - color: - (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000 > 128 - ? 'rgba(0, 0, 0, 0.8)' - : 'white', - padding: '0 4px', - borderRadius: 2, - borderBottomLeftRadius: 0, - fontSize: '.75rem', - fontWeight: 600, - userSelect: 'none', - pointerEvents: 'none', - width: 'max-content', - fontFamily: 'MonoLisa, Menlo, monospace', - }; - if (!this.userClassesGenerated[cursorClassName]) { - this.userClassesGenerated[cursorClassName] = `${css({ - display: 'inherit', - position: 'absolute', - backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.8)`, - width: '2px !important', - height: '100%', - cursor: 'text', - zIndex: 200, - ':hover': { - ':before': { - animation: `${fadeIn} 0.3s`, - animationFillMode: 'forwards', - opacity: 0, - ...nameStyles, - }, - }, - })}`; - } - - if (!this.userClassesGenerated[nameTagClassName]) { - this.userClassesGenerated[nameTagClassName] = `${css({ - ':before': { - animation: `${fadeOut} 0.3s`, - animationDelay: '1s', - animationFillMode: 'forwards', - opacity: 1, - ...nameStyles, - }, - })}`; - } - - if (!this.userClassesGenerated[secondaryCursorClassName]) { - this.userClassesGenerated[secondaryCursorClassName] = `${css({ - backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.6)`, - width: '2px !important', - })}`; - } - - if (!this.userClassesGenerated[selectionClassName]) { - this.userClassesGenerated[selectionClassName] = `${css({ - backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.3)`, - borderRadius: '3px', - minWidth: 7.6, - })}`; - } - - if (!this.userClassesGenerated[secondarySelectionClassName]) { - this.userClassesGenerated[secondarySelectionClassName] = `${css({ - backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.2)`, - borderRadius: '3px', - minWidth: 7.6, - })}`; - } - - decorations.push( - getCursorDecoration(selection.primary.cursorPosition, cursorClassName) - ); - - if (selection.primary.selection.length) { - decorations.push( - getSelectionDecoration( - // @ts-ignore - selection.primary.selection[0], - // @ts-ignore - selection.primary.selection[1], - selectionClassName - ) - ); - } - - if (selection.secondary.length) { - selection.secondary.forEach(s => { - decorations.push( - getCursorDecoration(s.cursorPosition, secondaryCursorClassName) - ); - - if (s.selection.length) { - decorations.push( - getSelectionDecoration( - s.selection[0], - s.selection[1], - secondarySelectionClassName - ) - ); - } - }); - } - } - - this.userSelectionDecorations[decorationId] = model.deltaDecorations( - this.userSelectionDecorations[decorationId] || [], - decorations - ); - - if (this.nameTagTimeouts[decorationId]) { - clearTimeout(this.nameTagTimeouts[decorationId]); - } - // We don't want to show the nametag when the cursor changed, because - // another user changed the code on top of it. Otherwise it would get - // messy very fast. - if (showNameTag && selection.source !== 'modelChange') { - const decoration = model.deltaDecorations( - [], - [ - getCursorDecoration( - selection.primary.cursorPosition, - nameTagClassName - ), - ] - ); - this.userSelectionDecorations[decorationId].push(decoration); - this.nameTagTimeouts[decorationId] = window.setTimeout(() => { - if (!model.isDisposed()) { - // And now hide the nametag after 1.5s - model.deltaDecorations([decoration], []); - } - }, 1500); - } - }); - } - - private applyOperationToModel( - operation: TextOperation, - pushStack = false, - model = this.editorApi.getActiveCodeEditor().getModel() - ) { - const results: Array<{ - range: unknown; - text: string; - forceMoveMarkers?: boolean; - }> = []; - let index = 0; - const currentEOLLength = model.getEOL().length; - let eolChanged = false; - const modelCode = model.getValue(); - - if (operation.baseLength !== modelCode.length) { - throw new Error( - "The base length of the operation doesn't match the length of the code" - ); - } - - for (let i = 0; i < operation.ops.length; i++) { - const op = operation.ops[i]; - if (TextOperation.isRetain(op)) { - index += op as number; - } else if (TextOperation.isInsert(op)) { - const textOp = op as string; - const { lineNumber, column } = indexToLineAndColumn( - model.getValue().split(/\n/) || [], - index - ); - const range = new this.monaco.Range( - lineNumber, - column, - lineNumber, - column - ); - - // if there's a new line - if (/\n/.test(textOp)) { - const eol = /\r\n/.test(textOp) ? 2 : 1; - if (eol !== currentEOLLength) { - // With this insert the EOL of the document changed on the other side. We need - // to accomodate our EOL to it. - eolChanged = true; - } - } - - results.push({ - range, - text: textOp, - forceMoveMarkers: true, - }); - } else if (TextOperation.isDelete(op)) { - const delOp = op as number; - const lines = model.getValue().split(/\n/) || []; - const from = indexToLineAndColumn(lines, index); - const to = indexToLineAndColumn(lines, index - delOp); - results.push({ - range: new this.monaco.Range( - from.lineNumber, - from.column, - to.lineNumber, - to.column - ), - text: '', - }); - index -= delOp; - } - } - - if (eolChanged) { - const newEolMode = currentEOLLength === 2 ? 0 : 1; - model.setEOL(newEolMode); - } - - if (pushStack) { - model.pushEditOperations([], results); - } else { - model.applyEdits(results); - } - } - - private listenForChanges() { - this.modelAddedListener = this.editorApi.textFileService.modelService.onModelAdded( - model => { - try { - const module = resolveModule( - model.uri.path.replace(/^\/sandbox/, ''), - this.sandbox.modules, - this.sandbox.directories - ); - - const moduleModel = this.getOrCreateModuleModelByPath(module.path); - - moduleModel.model = model; - moduleModel.changeListener = this.getModelContentChangeListener( - this.sandbox, - model - ); - } catch (e) { - // File does not exist anymore for some reason - } - } - ); - - this.modelRemovedListener = this.editorApi.textFileService.modelService.onModelRemoved( - model => { - if (this.moduleModels[model.uri.path]) { - const changeListener = this.moduleModels[model.uri.path] - .changeListener; - if (changeListener) { - changeListener.dispose(); - } - - const csbPath = model.uri.path.replace('/sandbox', ''); - dispatch(actions.correction.clear(csbPath, 'eslint')); - - // We only delete the model, because the state contains things - // like comments, which we want to keep - delete this.moduleModels[model.uri.path].model; - } - } - ); - } - - private getModelContentChangeListener(sandbox: Sandbox, model) { - return model.onDidChangeContent(e => { - if (this.isApplyingOperation) { - return; - } - - const { path } = model.uri; - try { - const module = resolveModule( - path.replace(/^\/sandbox/, ''), - sandbox.modules, - sandbox.directories - ); - - this.onChangeCallback({ - moduleShortid: module.shortid, - title: module.title, - code: model.getValue(), - event: e, - model, - }); - } catch (err) { - // This can throw when a file is deleted and you add new code to it. When - // saving it a new file is created - } - }); - } - - private createCommentDecorations( - commentThreadDecorations: Array<{ - commentId: string; - range: [number, number]; - }>, - model: any, - currentCommentThreadId: string | null, - currentLineNumber: number - ) { - if ( - !hasPermission(this.sandbox.authorization, 'comment') || - !this.sandbox.featureFlags.comments - ) { - return []; - } - const commentDecorationsByLineNumber = commentThreadDecorations.reduce<{ - [lineNumber: string]: Array<{ - commentId: string; - range: [number, number]; - }>; - }>((aggr, commentDecoration) => { - const { lineNumber } = indexToLineAndColumn( - model.getLinesContent() || [], - commentDecoration.range[0] - ); - - if (!aggr[lineNumber]) { - aggr[lineNumber] = []; - } - - aggr[lineNumber].push(commentDecoration); - - return aggr; - }, {}); - - const initialDecorations: any[] = - currentLineNumber === -1 || - currentLineNumber in commentDecorationsByLineNumber - ? [] - : [ - { - range: new this.monaco.Range( - currentLineNumber, - 1, - currentLineNumber, - 1 - ), - options: { - isWholeLine: true, - glyphMarginClassName: `editor-comments-glyph editor-comments-multi editor-comments-add`, - }, - }, - ]; - - return Object.keys(commentDecorationsByLineNumber).reduce( - (aggr, lineNumberKey) => { - const lineCommentDecorations = - commentDecorationsByLineNumber[lineNumberKey]; - const lineNumber = Number(lineNumberKey); - const activeCommentDecoration = lineCommentDecorations.find( - commentDecoration => - commentDecoration.commentId === currentCommentThreadId - ); - const ids = lineCommentDecorations.map( - commentDecoration => commentDecoration.commentId - ); - const commentRange = activeCommentDecoration - ? [ - indexToLineAndColumn( - model.getLinesContent() || [], - activeCommentDecoration.range[0] - ), - indexToLineAndColumn( - model.getLinesContent() || [], - activeCommentDecoration.range[1] - ), - ] - : null; - - return aggr.concat( - { - range: new this.monaco.Range(lineNumber, 1, lineNumber, 1), - options: { - // comment-id- class needs to be the LAST class! - glyphMarginClassName: `editor-comments-glyph ${ - activeCommentDecoration ? 'editor-comments-active ' : '' - }${ - ids.length > 1 - ? `editor-comments-multi editor-comments-multi-${ids.length} ` - : '' - }editor-comments-ids-${ids.join('_')}`, - }, - }, - commentRange - ? { - range: new this.monaco.Range( - commentRange[0].lineNumber, - commentRange[0].column, - commentRange[1].lineNumber, - commentRange[1].column - ), - options: { - isWholeLine: - commentRange[0].lineNumber === commentRange[1].lineNumber && - commentRange[0].column === commentRange[1].column, - className: activeCommentDecoration - ? 'editor-comments-highlight' - : undefined, - }, - } - : [] - ); - }, - initialDecorations - ); - } -} diff --git a/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/index.ts b/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/index.ts deleted file mode 100644 index c2595cfd337..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/index.ts +++ /dev/null @@ -1,595 +0,0 @@ -import browserFs from 'fs'; -import { dirname, join } from 'path'; - -import { - getDirectoryPath, - getModulePath, -} from '@codesandbox/common/lib/sandbox/modules'; -import { - Directory, - Module, - Sandbox, - SandboxFs, -} from '@codesandbox/common/lib/types'; -import delay from '@codesandbox/common/lib/utils/delay'; -import { getAbsoluteDependency } from '@codesandbox/common/lib/utils/dependencies'; -import { getGlobal } from '@codesandbox/common/lib/utils/global'; -import { protocolAndHost } from '@codesandbox/common/lib/utils/url-generator'; -import { getSavedCode } from 'app/overmind/utils/sandbox'; -import { isPrivateScope } from 'app/utils/private-registry'; -import { json } from 'overmind'; - -import { WAIT_INITIAL_TYPINGS_MS } from '../constants'; -import { appendFile, mkdir, rename, rmdir, unlink, writeFile } from './utils'; -import { fetchPrivateDependency } from './private-type-fetch'; - -const global = getGlobal() as Window & { BrowserFS: any }; - -const SERVICE_URL = 'https://ata.codesandbox.io/api/v8'; -const FALLBACK_SERVICE_URL = 'https://typings.csb.dev/api/v8'; -const BUCKET_URL = 'https://prod-packager-packages.codesandbox.io/v1/typings'; - -async function callApi(url: string, method = 'GET') { - const response = await fetch(url, { - method, - }); - - if (!response.ok) { - const error = new Error(response.statusText || '' + response.status); - const message = await response.json(); - // @ts-ignore - error.response = message; - // @ts-ignore - error.statusCode = response.status; - throw error; - } - - return response.json(); -} - -async function requestPackager(url: string, retryCount = 0, method = 'GET') { - let retries = 0; - - // eslint-disable-next-line no-constant-condition - while (true) { - try { - const manifest = await callApi(url, method); // eslint-disable-line no-await-in-loop - - return manifest; - } catch (e) { - if (e.response && e.statusCode !== 504) { - throw new Error(e.response.error); - } - // 403 status code means the bundler is still bundling - if (retries < retryCount) { - retries += 1; - await delay(1000 * 2); // eslint-disable-line no-await-in-loop - } else { - throw e; - } - } - } -} - -type SandboxFsSyncOptions = { - getSandboxFs: () => SandboxFs; - getCurrentSandbox: () => Sandbox | null; -}; - -class SandboxFsSync { - private options: SandboxFsSyncOptions; - private types: { - [packageName: string]: { - [typeName: string]: string; - }; - } = {}; - - private deps: { [path: string]: string } = {}; - - private workerIds: string[] = []; - - private isDisposed = false; - private typesInfo: Promise; - constructor(options: SandboxFsSyncOptions) { - this.options = options; - self.addEventListener('message', this.onWorkerMessage); - } - - public sync(cb: () => void) { - this.send('reset', {}); - this.syncDependencyTypings(); - setTimeout(() => { - if (!this.isDisposed) { - cb(); - } - }, WAIT_INITIAL_TYPINGS_MS); - } - - public getTypes() { - return Object.keys(this.types).reduce( - (aggr, key) => Object.assign(aggr, this.types[key]), - {} - ); - } - - public dispose() { - this.isDisposed = true; - self.removeEventListener('message', this.onWorkerMessage); - this.clearSandboxFiles(); - } - - public create(sandbox: Sandbox): SandboxFs { - const sandboxFs = {}; - - sandbox.directories.forEach(d => { - const path = getDirectoryPath(sandbox.modules, sandbox.directories, d.id); - - d.path = path; - // If this is a single directory with no children - if (!Object.keys(sandboxFs).some(p => dirname(p) === path)) { - sandboxFs[path] = { ...d, type: 'directory' }; - } - - browserFs.mkdir(join('/sandbox', path), () => {}); - }); - - sandbox.modules.forEach(m => { - const path = getModulePath(sandbox.modules, sandbox.directories, m.id); - if (path) { - m.path = path; - sandboxFs[path] = { - ...m, - type: 'file', - }; - - browserFs.writeFile(join('/sandbox', path), m.code, () => {}); - } - }); - - return sandboxFs; - } - - public appendFile(fs: SandboxFs, module: Module) { - const copy = json(module); - - appendFile(fs, copy); - this.send('append-file', copy); - - const savedCode = getSavedCode(module.code, module.savedCode); - browserFs.appendFile(join('/sandbox', module.path!), savedCode, () => {}); - } - - public writeFile(fs: SandboxFs, module: Module) { - const copy = json(module); - - writeFile(fs, copy); - this.send('write-file', copy); - - const savedCode = getSavedCode(module.code, module.savedCode); - browserFs.writeFile(join('/sandbox', module.path!), savedCode, () => {}); - - if (module.title === 'package.json') { - this.syncDependencyTypings(); - } - } - - public rename(fs: SandboxFs, fromPath: string, toPath: string) { - rename(fs, fromPath, toPath); - this.send('rename', { - fromPath, - toPath, - }); - browserFs.rename( - join('/sandbox', fromPath), - join('/sandbox', toPath), - () => {} - ); - } - - public rmdir(fs: SandboxFs, directory: Directory) { - const copy = json(directory); - - rmdir(fs, copy); - this.send('rmdir', copy); - browserFs.rmdir(join('/sandbox', directory.path!), () => {}); - } - - public unlink(fs: SandboxFs, module: Module) { - const copy = json(module); - - unlink(fs, copy); - this.send('unlink', copy); - browserFs.unlink(join('/sandbox', module.path!), () => {}); - } - - public mkdir(fs: SandboxFs, directory: Directory) { - const copy = json(directory); - - mkdir(fs, copy); - this.send('mkdir', copy); - browserFs.mkdir(join('/sandbox', directory.path!), () => {}); - } - - private onWorkerMessage = evt => { - if (evt.data.$fs_id && !this.workerIds.includes(evt.data.$fs_id)) { - this.workerIds.push(evt.data.$fs_id); - } - - if (evt.data.$type === 'sync-sandbox') { - this.syncSandbox(this.options.getSandboxFs(), evt.data.$fs_id); - } - - if (evt.data.$type === 'sync-types') { - this.send('types-sync', this.getTypes(), evt.data.$fs_id); - } - }; - - // We sync the FS whenever we create a new one, which happens in different - // scenarios. If a worker is requesting a sync we grab the existing FS from - // the state - private syncSandbox(sandboxFs, id?) { - this.send('sandbox-fs', json(sandboxFs), id); - } - - private send(type: string, data: any, id?: string) { - global.postMessage( - { - $broadcast: true, - $fs_ids: typeof id === 'undefined' ? this.workerIds : [id], - $type: type, - $data: data, - }, - protocolAndHost() - ); - } - - // We pass in either existing or new syncId. This allows us to evaluate - // if we are just going to pass existing types or start up a new round - // to fetch types - private async syncDependencyTypings(fsId?: string) { - try { - this.typesInfo = await this.getTypesInfo(); - const syncDetails = await this.getDependencyTypingsSyncDetails(); - - if (syncDetails) { - const newDeps = syncDetails.dependencies; - const { added, removed } = this.getDepsChanges(newDeps); - - this.deps = newDeps; - - added.forEach(dep => { - this.fetchDependencyTyping(dep, syncDetails.autoInstall); - }); - - if (removed.length) { - const removedTypings = {}; - - // We go through removed deps to figure out what typings packages - // has been removed, then delete the from our types as well - removed.forEach(removedDep => { - const typings = this.types[removedDep.name] || {}; - - Object.assign(removedTypings, typings); - - this.fetchedPrivateDependencies.delete(removedDep.name); - - delete this.types[removedDep.name]; - }); - - this.send('types-remove', removedTypings, fsId); - } - } - } catch (error) { - // eslint-disable-next-line - console.error(error); - } - } - - private getDepsChanges(newDeps) { - const added: Array<{ name: string; version: string }> = []; - const removed: Array<{ name: string; version: string }> = []; - const newDepsKeys = Object.keys(newDeps); - const currentDepsKeys = Object.keys(this.deps); - - if (currentDepsKeys.length === 0) { - added.push({ - name: '@types/jest', - version: 'latest', - }); - } - - newDepsKeys.forEach(newDepKey => { - if ( - !this.deps[newDepKey] || - this.deps[newDepKey] !== newDeps[newDepKey] - ) { - added.push({ - name: newDepKey, - version: newDeps[newDepKey], - }); - } - }); - - currentDepsKeys.forEach(currentDepKey => { - if (currentDepKey !== '@types/jest' && !newDeps[currentDepKey]) { - removed.push({ - name: currentDepKey, - version: this.deps[currentDepKey], - }); - } - }); - - return { added, removed }; - } - - private async getDependencyTypingsSyncDetails(): Promise<{ - dependencies: { [name: string]: string }; - autoInstall: boolean; - } | null> { - return new Promise((resolve, reject) => { - try { - browserFs.stat('/sandbox/package.json', (packageJsonError, stat) => { - if (packageJsonError) { - resolve(null); - return; - } - - browserFs.readFile( - '/sandbox/package.json', - async (packageJsonReadError, rv) => { - if (packageJsonReadError) { - resolve(null); - return; - } - - browserFs.stat( - '/sandbox/tsconfig.json', - (tsConfigError, result) => { - // If tsconfig exists we want to sync the typesp - try { - const packageJson = JSON.parse(rv.toString()); - resolve({ - dependencies: { - ...packageJson.dependencies, - ...packageJson.devDependencies, - }, - autoInstall: Boolean(tsConfigError) || !result, - }); - } catch (error) { - reject(new Error('TYPINGS: Could not parse package.json')); - } - } - ); - } - ); - }); - } catch (e) { - resolve(null); - // Do nothing - } - }); - } - - /** - * Gets all entries of dependencies -> @types/ version - */ - private getTypesInfo() { - if (this.typesInfo) { - return this.typesInfo; - } - - this.typesInfo = fetch( - 'https://unpkg.com/types-registry@latest/index.json', - // This falls back to etag caching, ensuring we always have latest version - // https://hacks.mozilla.org/2016/03/referrer-and-cache-control-apis-for-fetch/ - { cache: 'no-cache' } - ) - .then(x => x.json()) - .then(x => x.entries); - - return this.typesInfo; - } - - // We send new packages to all registered workers - private setAndSendPackageTypes( - name: string, - types: { [name: string]: { module: { code: string } } } - ) { - if (!this.isDisposed) { - if (!this.types[name]) { - this.types[name] = {}; - } - - const existingDeps = Object.keys(this.types); - /* - We have to ensure that a dependency does not override the types of the main - package if it is installed (their versions might differ) - */ - const filteredTypes = Object.keys(types).reduce((aggr, newTypePath) => { - const alreadyExists = existingDeps.reduce((subAggr, depName) => { - // If we have already installed the typing from the main package - if ( - subAggr || - (depName !== name && - this.types[depName][newTypePath] && - newTypePath.startsWith('/' + depName + '/')) - ) { - return true; - } - - return subAggr; - }, false); - - if (!alreadyExists) { - aggr[newTypePath] = types[newTypePath]; - } - - return aggr; - }, {}); - - Object.assign(this.types[name], filteredTypes); - this.send('package-types-sync', filteredTypes); - } - } - - private async fetchDependencyTyping( - dep: { name: string; version: string }, - autoInstallTypes: boolean - ) { - try { - try { - const files = await this.fetchDependencyTypingFiles( - dep.name, - dep.version - ); - const hasTypes = Boolean( - Object.keys(files).some( - key => key.startsWith('/' + dep.name) && key.endsWith('.d.ts') - ) - ); - - if ( - !hasTypes && - autoInstallTypes && - this.typesInfo[dep.name] && - !dep.name.startsWith('@types/') - ) { - const name = `@types/${dep.name}`; - this.fetchDependencyTypingFiles(name, this.typesInfo[dep.name].latest) - .then(typesFiles => { - this.setAndSendPackageTypes(dep.name, typesFiles); - }) - .catch(e => { - if (process.env.NODE_ENV === 'development') { - console.warn('Trouble fetching types for ' + name); - } - }); - } else { - this.setAndSendPackageTypes(dep.name, files); - } - } catch (e) { - if (process.env.NODE_ENV === 'development') { - console.warn('Trouble fetching types for ' + dep.name); - } - } - } catch (e) { - /* ignore */ - } - } - - private fetchedPrivateDependencies = new Set(); - /** - * Fetch the dependency typings of a private package. We have different behaviour here, - * instead of fetching directly from the type fetcher we fetch the tar directly, and - * extract the `.d.ts` & `.ts` files from it. This doesn't account for all transient dependencies - * of this dependency though, that's why we also download the "subdependencies" using the normal - * fetching strategy (`fetchDependencyTypingFiles`). - * - * There's a risk we'll run in a deadlock in this approach; if `a` needs `b` and `b` needs `a`, we'll - * recursively keep calling the same functions. To prevent this we keep track of which dependencies have - * been processed and skip if those have been added already. - */ - private async fetchPrivateDependencyTypingsFiles( - name: string, - version: string - ): Promise<{ [f: string]: { module: { code: string } } }> { - if (this.fetchedPrivateDependencies.has(name)) { - return {}; - } - - const { dtsFiles, dependencies } = await fetchPrivateDependency( - this.options.getCurrentSandbox()!, - name, - version - ); - - this.fetchedPrivateDependencies.add(name); - - const totalFiles: { [f: string]: { module: { code: string } } } = dtsFiles; - dependencies.map(async dep => { - const files = await this.fetchDependencyTypingFiles( - dep.name, - dep.version - ); - Object.keys(files).forEach(f => { - totalFiles[f] = files[f]; - }); - }); - - return totalFiles; - } - - private async fetchDependencyTypingFiles( - originalName: string, - originalVersion: string - ): Promise<{ [path: string]: { module: { code: string } } }> { - const sandbox = this.options.getCurrentSandbox(); - const isPrivatePackage = sandbox && isPrivateScope(sandbox, originalName); - - if (isPrivatePackage) { - return this.fetchPrivateDependencyTypingsFiles( - originalName, - originalVersion - ); - } - - const { name, version } = await getAbsoluteDependency( - originalName, - originalVersion - ); - const dependencyQuery = encodeURIComponent(`${name}@${version}`); - - try { - const url = `${BUCKET_URL}/${name}/${version}.json`; - return await requestPackager(url, 0).then(x => x.files); - } catch (e) { - // Hasn't been generated - } - - try { - const { files } = await requestPackager( - `${SERVICE_URL}/${dependencyQuery}.json`, - 3 - ); - - return files; - } catch { - const { files } = await requestPackager( - `${FALLBACK_SERVICE_URL}/${dependencyQuery}.json`, - 3 - ); - - return files; - } - } - - private clearSandboxFiles(dir = '/sandbox') { - if (dir === '/sandbox/node_modules') { - return; - } - - const kids = browserFs.readdirSync(dir); - - kids.forEach(kid => { - const path = join(dir, kid); - - try { - const lstat = browserFs.lstatSync(path); - - if (lstat.isDirectory()) { - this.clearSandboxFiles(path); - } else { - browserFs.unlinkSync(path); - } - } catch { - // Do nothing - } - }); - - if (dir !== '/sandbox') { - browserFs.rmdirSync(dir); - } - } -} - -export default SandboxFsSync; diff --git a/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/private-type-fetch.ts b/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/private-type-fetch.ts deleted file mode 100644 index 89c254e608f..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/private-type-fetch.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Sandbox } from '@codesandbox/common/lib/types'; -import { NpmRegistryFetcher } from 'sandpack-core/lib/npm/dynamic/fetch-protocols/npm-registry'; - -type Output = { - dtsFiles: { [p: string]: { module: { code: string } } }; - dependencies: Array<{ name: string; version: string }>; -}; - -/** - * Fetches the tar of the private dependency from the server, extracts it in memory and filters out the type - * files. - */ -export async function fetchPrivateDependency( - sandbox: Sandbox, - name: string, - version: string -): Promise { - // TODO: add support for multiple registries - const cleanUrl = sandbox.npmRegistries[0].registryUrl.replace(/\/$/, ''); - - const fetcher = new NpmRegistryFetcher(cleanUrl, { - // With our custom proxy on the server we want to handle downloading - // the tarball. So we proxy it. - provideTarballUrl: (n: string, v: string) => - `${cleanUrl}/${n.replace('/', '%2f')}/${v}`, - }); - - const meta = await fetcher.meta(name, version); - const validFilePaths = Object.keys(meta).filter( - file => file.endsWith('.ts') || file.endsWith('/package.json') - ); - const validFiles = {}; - - await Promise.all( - validFilePaths.map(async filePath => { - const file = await fetcher.file(name, version, filePath); - validFiles[`/${name}${filePath}`] = { module: { code: file } }; - }) - ); - - const packageJSONPath = `/package.json`; - const packageJSONCode = await fetcher.file(name, version, packageJSONPath); - const deps: { [dep: string]: string } = JSON.parse(packageJSONCode) - .dependencies; - const depArray = Object.keys(deps).map(depName => ({ - name: depName, - version: deps[depName], - })); - - return { dtsFiles: validFiles, dependencies: depArray }; -} diff --git a/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/utils.ts b/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/utils.ts deleted file mode 100644 index 730668d8635..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/SandboxFsSync/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Directory, Module, SandboxFs } from '@codesandbox/common/lib/types'; -import { json } from 'overmind'; - -export const appendFile = (fs: SandboxFs, module: Module) => { - fs[module.path!] = module; -}; - -export const writeFile = (fs: SandboxFs, module: Module) => { - fs[module.path!] = module; -}; - -export const rename = (fs: SandboxFs, fromPath: string, toPath: string) => { - Object.keys(fs).forEach(path => { - if (path.startsWith(fromPath + '/') || path === fromPath) { - const newPath = path.replace(fromPath, toPath); - const module = fs[path]; - - delete fs[path]; - fs[newPath] = json(module); - } - }); -}; - -export const rmdir = (fs: SandboxFs, directory: Directory) => { - Object.keys(fs).forEach(path => { - if (path.startsWith(directory.path!)) { - delete fs[path]; - } - }); -}; - -export const unlink = (fs: SandboxFs, module: Module) => { - delete fs[module.path!]; -}; - -export const mkdir = (fs: SandboxFs, directory: Directory) => { - fs[directory.path!] = directory; -}; diff --git a/packages/app/src/app/overmind/effects/vscode/Workbench.ts b/packages/app/src/app/overmind/effects/vscode/Workbench.ts deleted file mode 100644 index 201068eb9b2..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/Workbench.ts +++ /dev/null @@ -1,512 +0,0 @@ -import { notificationState } from '@codesandbox/common/lib/utils/notifications'; -import { - NotificationMessage, - NotificationStatus, -} from '@codesandbox/notifications/lib/state'; - -import { KeyCode, KeyMod } from './keyCodes'; - -// Copied from 'common/actions' in vscode -export enum MenuId { - CommandPalette, - DebugBreakpointsContext, - DebugCallStackContext, - DebugConsoleContext, - DebugVariablesContext, - DebugWatchContext, - DebugToolbar, - EditorContext, - EditorTitle, - EditorTitleContext, - EmptyEditorGroupContext, - ExplorerContext, - MenubarAppearanceMenu, - MenubarDebugMenu, - MenubarEditMenu, - MenubarFileMenu, - MenubarGoMenu, - MenubarHelpMenu, - MenubarLayoutMenu, - MenubarNewBreakpointMenu, - MenubarPreferencesMenu, - MenubarRecentMenu, - MenubarSelectionMenu, - MenubarSwitchEditorMenu, - MenubarSwitchGroupMenu, - MenubarTerminalMenu, - MenubarViewMenu, - OpenEditorsContext, - ProblemsPanelContext, - SCMChangeContext, - SCMResourceContext, - SCMResourceGroupContext, - SCMSourceControl, - SCMTitle, - SearchContext, - TouchBarContext, - ViewItemContext, - ViewTitle, - Root, -} - -export class Workbench { - monaco: any; - controller: { getState: any; getSignal: any }; - runCommand: (id: string) => Promise; - constructor( - monaco: any, - controller: { getState: any; getSignal: any }, - runCommand: (id: string) => Promise - ) { - this.monaco = monaco; - this.controller = controller; - this.runCommand = runCommand; - } - - public addWorkbenchActions() { - this.addWorkbenchAction({ - id: 'workbench.action.toggleStatusbarVisibility', - label: 'Toggle Status Bar Visibility', - commandLabel: 'Toggle Status Bar Visibility', - category: 'View', - run: () => { - this.controller.getSignal('editor.toggleStatusBar')(); - }, - }); - - this.addWorkbenchAction({ - id: 'view.preview.flip', - label: 'Flip Preview Layout', - commandLabel: 'Toggle Vertical/Horizontal Preview Layout', - category: 'View', - run: () => { - this.runCommand('workbench.action.toggleEditorGroupLayout'); - this.controller.getSignal('editor.toggleEditorPreviewLayout')(); - }, - keybindings: { - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_9, - mac: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_9, - }, - }, - }); - - this.addWorkbenchAction({ - id: 'codesandbox.sandbox.new', - label: 'New Sandbox...', - category: 'Sandbox', - run: () => { - this.controller.getSignal('openCreateSandboxModal')({}); - }, - }); - - this.addWorkbenchAction({ - id: 'codesandbox.sandbox.fork', - label: 'Fork Sandbox', - category: 'Sandbox', - run: () => { - this.controller.getSignal('editor.forkSandboxClicked')({}); - }, - }); - - this.addWorkbenchAction({ - id: 'codesandbox.sandbox.exportzip', - label: 'Export To ZIP', - category: 'Sandbox', - run: () => { - this.controller.getSignal('editor.createZipClicked')(); - }, - }); - - this.addWorkbenchAction({ - id: 'codesandbox.preferences', - label: 'Open CodeSandbox Settings', - category: 'Preferences', - run: () => { - this.controller.getSignal('modalOpened')({ modal: 'preferences' }); - }, - }); - - this.addWorkbenchAction({ - id: 'codesandbox.preview.refresh', - label: 'Refresh Preview', - category: 'View', - run: () => { - this.controller.getSignal('editor.refreshPreview')(); - }, - keybindings: { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R, - }, - }); - - this.addWorkbenchAction({ - id: 'codesandbox.dashboard', - label: 'Dashboard', - category: 'Root', - run: () => { - // hard link - window.location.pathname = '/dashboard/home'; - }, - keybindings: { - primary: KeyMod.CtrlCmd | KeyCode.Escape, - mac: { primary: KeyMod.CtrlCmd | KeyCode.Escape }, - }, - }); - - this.addWorkbenchAction({ - id: 'codesandbox.homepage', - label: 'Homepage', - category: 'Root', - run: () => { - /** - * It creates a fake link, once the history object - * is used by the react router and it doesn't route to the proper place - */ - const fakeLink = document.createElement('a'); - fakeLink.href = `${window.location.origin}/?from-app=1`; - document.body.appendChild(fakeLink); - fakeLink.click(); - }, - }); - - this.addWorkbenchAction({ - id: 'view.fullscreen', - label: 'Toggle Fullscreen', - category: 'View', - run: () => { - if (document.fullscreenElement) { - document.exitFullscreen(); - } else { - if (document.documentElement.requestFullscreen) { - document.documentElement.requestFullscreen(); - // @ts-ignore - safari has this under a webkit flag - } else if (document.documentElement.webkitRequestFullscreen) { - // @ts-ignore - safari has this under a webkit flag - document.documentElement.webkitRequestFullscreen(); - } - - this.addNotification({ - title: 'Fullscreen', - message: - 'You are now in fullscreen mode. Press and hold ESC to exit', - status: NotificationStatus.NOTICE, - timeAlive: 5000, - }); - - if ('keyboard' in navigator) { - // @ts-ignore - keyboard locking is experimental api - navigator.keyboard.lock([ - 'Escape', - 'KeyW', - 'KeyN', - 'KeyT', - 'ArrowLeft', - 'ArrowRight', - ]); - } - } - }, - keybindings: { - primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_F, - }, - }); - - if ( - this.controller.getState().editor.currentSandbox?.featureFlags.comments - ) { - this.addWorkbenchAction({ - id: 'comments.add', - label: 'Comment on code', - category: 'Comments', - run: () => { - this.controller.getSignal('comments.createCodeComment')(); - }, - }); - } - - this.appendMenuItem(MenuId.Root, { - group: '1_workspace', - order: 1, - command: { - id: 'codesandbox.dashboard', - title: 'Dashboard', - }, - }); - - this.appendMenuItem(MenuId.Root, { - group: '3_open', - order: 1, - command: { - id: 'workbench.action.showCommands', - title: '&&Command Palette', - }, - }); - - this.appendMenuItem(MenuId.MenubarFileMenu, { - group: '1_new', - order: 1, - command: { - id: 'codesandbox.sandbox.new', - title: 'New Sandbox...', - }, - }); - - this.appendMenuItem(MenuId.Root, { - group: '3_support', - order: 1, - command: { - id: 'codesandbox.homepage', - title: 'Go to Homepage', - }, - }); - - this.appendMenuItem(MenuId.MenubarFileMenu, { - // Z to be the last item after vscode group 4 - group: '4_zsandbox', - order: 1, - command: { - id: 'codesandbox.sandbox.fork', - title: '&&Fork Sandbox', - }, - }); - - if ( - this.controller.getState().editor?.currentSandbox?.permissions - .preventSandboxExport - ) { - // don't add the option to add export - } else { - this.appendMenuItem(MenuId.MenubarFileMenu, { - group: '4_zsandbox', - order: 2, - command: { - id: 'codesandbox.sandbox.exportzip', - title: 'Export to ZIP', - }, - }); - } - - this.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '1_settings', - order: 1, - command: { - id: 'codesandbox.preferences', - title: 'CodeSandbox Settings', - }, - }); - - this.appendMenuItem(MenuId.MenubarLayoutMenu, { - group: 'z_flip', - command: { - id: 'view.preview.flip', - title: 'Flip Full Layout', - }, - order: 2, - }); - - this.appendMenuItem(MenuId.MenubarViewMenu, { - group: '1_toggle_view', - order: 0, - command: { - id: 'view.fullscreen', - title: 'Toggle Fullscreen', - }, - }); - - const addBrowserNavigationCommand = ( - id: string, - label: string, - url: string - ) => { - this.addWorkbenchAction({ - id, - label, - category: 'Help', - run: () => { - window.open(url, '_blank'); - }, - }); - }; - - addBrowserNavigationCommand( - 'codesandbox.help.documentation', - 'Documentation', - 'https://codesandbox.io/docs' - ); - addBrowserNavigationCommand( - 'codesandbox.explore', - 'Explore Sandboxes', - 'https://codesandbox.io/explore' - ); - addBrowserNavigationCommand( - 'codesandbox.search', - 'Search', - 'https://codesandbox.io/search' - ); - addBrowserNavigationCommand( - 'codesandbox.help.open-issue', - 'Open Issue on GitHub', - 'https://github.com/codesandbox/codesandbox-client/issues' - ); - addBrowserNavigationCommand( - 'codesandbox.help.github', - 'Open Our GitHub Repository', - 'https://github.com/codesandbox/codesandbox-client' - ); - addBrowserNavigationCommand( - 'codesandbox.help.twitter', - 'Follow Us on Twitter', - 'https://twitter.com/codesandbox' - ); - addBrowserNavigationCommand( - 'codesandbox.help.discord', - 'Join our discord server', - 'https://discord.gg/C6vfhW3H6e' - ); - - this.addWorkbenchAction({ - id: 'codesandbox.help.feedback', - label: 'Submit Feedback...', - category: 'Help', - run: () => { - this.controller.getSignal('modalOpened')({ modal: 'feedback' }); - }, - }); - - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_resources', - order: 1, - command: { - id: 'codesandbox.help.documentation', - title: '&&Documentation', - }, - }); - - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_resources', - order: 2, - command: { - id: 'codesandbox.explore', - title: '&&Explore Sandboxes', - }, - }); - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_resources', - order: 3, - command: { - id: 'codesandbox.search', - title: '&&Search', - }, - }); - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_resources', - order: 4, - command: { - id: 'codesandbox.dashboard', - title: 'D&&ashboard', - }, - }); - - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_help', - order: 1, - command: { - id: 'codesandbox.help.open-issue', - title: '&&Open Issue on GitHub', - }, - }); - - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_help', - order: 2, - command: { - id: 'codesandbox.help.feedback', - title: 'Submit &&Feedback...', - }, - }); - - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_social', - order: 1, - command: { - id: 'codesandbox.help.github', - title: '&&GitHub Repository', - }, - }); - - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_social', - order: 2, - command: { - id: 'codesandbox.help.twitter', - title: 'Follow Us on &&Twitter', - }, - }); - - this.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_social', - order: 3, - command: { - id: 'codesandbox.help.discord', - title: 'Get Help on &&Discord', - }, - }); - - if ( - this.controller.getState().editor.currentSandbox?.featureFlags.comments - ) { - this.appendMenuItem(MenuId.EditorContext, { - group: '0_comments', - order: 0, - command: { - id: 'comments.add', - title: 'Comment on code', - }, - }); - } - } - - private addWorkbenchAction({ - id, - label, - commandLabel = label, - category = 'CodeSandbox', - run, - keybindings = { primary: 0 }, - }: { - id: string; - label: string; - commandLabel?: string; // Name of the command - category?: string; - run: () => any | Promise; - keybindings?: { primary: number; mac?: { primary: number } }; - }) { - this.monaco.editor.addWorkbenchActions({ - id, - label, - commandLabel, - run, - category, - keybindings, - }); - } - - private appendMenuItem( - menubarId: number, - item: { - group: string; - command: { - id: string; - title: string; - toggled?: boolean; - }; - order: number; - } - ) { - this.monaco.editor.appendMenuItem(menubarId, item); - } - - private addNotification(notification: NotificationMessage) { - notificationState.addNotification(notification); - } -} diff --git a/packages/app/src/app/overmind/effects/vscode/composeMenuAppTree.ts b/packages/app/src/app/overmind/effects/vscode/composeMenuAppTree.ts deleted file mode 100644 index ac0567aa1cc..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/composeMenuAppTree.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { MenuId } from './Workbench'; - -export type MenuAppItems = Array<{ - group: string; - order: number; - command?: { - id: string; - title: string; - when?: { key: string }; - precondition?: { key: string }; - toggled?: { key: string }; - }; - title?: string; - when?: { key: string }; - submenu?: MenuAppItems; - submenuId?: number; -}>; - -const DEFAULT_ITEMS = [ - { - title: '&&File', - submenu: MenuId.MenubarFileMenu, - group: '2_root', - order: 1, - }, - { - title: '&&Edit', - submenu: MenuId.MenubarEditMenu, - group: '2_root', - order: 2, - }, - { - title: '&&Selection', - submenu: MenuId.MenubarSelectionMenu, - group: '2_root', - order: 3, - }, - { - title: '&&View', - submenu: MenuId.MenubarViewMenu, - group: '2_root', - order: 4, - }, - { - title: '&&Go', - submenu: MenuId.MenubarGoMenu, - group: '2_root', - order: 5, - }, - { - title: '&&Help', - submenu: MenuId.MenubarHelpMenu, - group: '3_support', - order: 2, - }, -]; - -type InternalMenuType = Array<{ submenu?: string }>; -export const composeMenuAppTree = ( - getVsCodeMenuItems: (id: string | MenuId) => InternalMenuType -): MenuAppItems => { - const rootMenu = [...getVsCodeMenuItems(MenuId.Root), ...DEFAULT_ITEMS]; - - const getSubmenu = (menu: InternalMenuType) => - menu.map(item => { - const submenuId = item.submenu; - - if (submenuId) { - const submenu = getVsCodeMenuItems(submenuId); - return { ...item, submenuId, submenu: getSubmenu(submenu) }; - } - - return item; - }); - - return getSubmenu((rootMenu as unknown) as InternalMenuType); -}; diff --git a/packages/app/src/app/overmind/effects/vscode/constants.ts b/packages/app/src/app/overmind/effects/vscode/constants.ts deleted file mode 100644 index d4db2d4c8ce..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const EXTENSIONS_LOCATION = process.env.VSCODE - ? '/vscode/extensions-bundle' - : '/public/vscode-extensions/v21'; - -export const VIM_EXTENSION_ID = 'vscodevim.vim'; - -export const WAIT_INITIAL_TYPINGS_MS = 1000; diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/client-ext-host.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/client-ext-host.ts deleted file mode 100644 index 771f6c49342..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/client-ext-host.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as childProcess from 'node-services/lib/child_process'; -// @ts-ignore -import DefaultWorkLoader from 'worker-loader?publicPath=/&name=dynamic-worker.[hash:8].worker.js!./generic-1'; -// @ts-ignore -import SvelteWorker from 'worker-loader?publicPath=/&name=svelte-worker.[hash:8].worker.js!./svelte-worker'; -// @ts-ignore -import TSWorker from 'worker-loader?publicPath=/&name=typescript-worker.[hash:8].worker.js!./ts-extension'; -// @ts-ignore -import VueWorker from 'worker-loader?publicPath=/&name=vue-worker.[hash:8].worker.js!./vue-worker'; - -import { initializeAll } from '../common/global'; - -childProcess.addDefaultForkHandler(DefaultWorkLoader); - -childProcess.addForkHandler( - '/extensions/node_modules/typescript/lib/tsserver.js', - TSWorker -); -childProcess.addForkHandler( - '/extensions/octref.vetur-0.28.0/server/dist/vueServerMain.js', - VueWorker -); -childProcess.addForkHandler( - '/extensions/jamesbirtles.svelte-vscode-0.7.1/node_modules/svelte-language-server/bin/server.js', - SvelteWorker -); - -initializeAll().then(() => { - // Preload the TS worker for fast init - childProcess.preloadForkHandler( - '/extensions/node_modules/typescript/lib/tsserver.js' - ); - - // eslint-disable-next-line - import('../workers/ext-host-worker'); -}); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/container-ext-host.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/container-ext-host.ts deleted file mode 100644 index 2325406d2a6..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/container-ext-host.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as childProcess from 'node-services/lib/child_process'; -// @ts-ignore -import DefaultWorkLoader from 'worker-loader?publicPath=/&name=dynamic-worker.[hash:8].worker.js!./generic-1'; -// @ts-ignore -import SvelteWorker from 'worker-loader?publicPath=/&name=svelte-worker.[hash:8].worker.js!./svelte-worker'; -// @ts-ignore -import VueWorker from 'worker-loader?publicPath=/&name=vue-worker.[hash:8].worker.js!./vue-worker'; - -import { initializeAll } from '../common/global'; -import { WebsocketLSP } from '../services/WebsocketLSP'; - -childProcess.addDefaultForkHandler(DefaultWorkLoader); - -childProcess.addForkHandler( - '/extensions/node_modules/typescript/lib/tsserver.js', - () => new WebsocketLSP() -); -childProcess.addForkHandler( - '/extensions/octref.vetur-0.28.0/server/dist/vueServerMain.js', - VueWorker -); -childProcess.addForkHandler( - '/extensions/jamesbirtles.svelte-vscode-0.7.1/node_modules/svelte-language-server/bin/server.js', - SvelteWorker -); - -initializeAll().then(() => { - // Preload the TS worker for fast init - childProcess.preloadForkHandler( - '/extensions/node_modules/typescript/lib/tsserver.js' - ); - - // eslint-disable-next-line - import('../workers/ext-host-worker'); -}); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/generic-1.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/generic-1.ts deleted file mode 100644 index 5ae0c21cea6..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/generic-1.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as childProcess from 'node-services/lib/child_process'; -// @ts-ignore -import SubWorkLoader from 'worker-loader?publicPath=/&name=sub-dynamic-worker.[hash:8].worker.js!./generic-2'; - -import { initializeAll } from '../common/global'; - -childProcess.addDefaultForkHandler(SubWorkLoader); - -initializeAll().then(() => { - // Use require so that it only starts executing the chunk with all globals specified. - // eslint-disable-next-line - require('../workers/generic-worker').start({ - syncSandbox: true, - syncTypes: true, - }); -}); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/generic-2.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/generic-2.ts deleted file mode 100644 index 9552df2d6a9..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/generic-2.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as childProcess from 'node-services/lib/child_process'; - -import { initializeAll } from '../common/global'; - -childProcess.addDefaultForkHandler(false); - -initializeAll().then(() => { - // Use require so that it only starts executing the chunk with all globals specified. - // eslint-disable-next-line - require('../workers/generic-worker').start({ - syncSandbox: true, - syncTypes: false, - }); -}); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/svelte-worker.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/svelte-worker.ts deleted file mode 100644 index 226f4f06aff..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/svelte-worker.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as childProcess from 'node-services/lib/child_process'; - -// @ts-ignore -import SubWorkLoader from 'worker-loader?publicPath=/&name=sub-dynamic-worker.[hash:8].worker.js!./generic-2'; - -import { EXTENSIONS_LOCATION } from '../../constants'; - -import { initializeAll } from '../common/global'; - -declare const __DEV__: boolean; - -childProcess.addDefaultForkHandler(SubWorkLoader); - -initializeAll().then(async () => { - // Use require so that it only starts executing the chunk with all globals specified. - // eslint-disable-next-line - require('../workers/generic-worker').start({ - syncSandbox: true, - syncTypes: true, - extraMounts: { - '/extensions': { - fs: 'BundledHTTPRequest', - options: { - index: EXTENSIONS_LOCATION + '/extensions/index.json', - baseUrl: EXTENSIONS_LOCATION + '/extensions', - bundle: EXTENSIONS_LOCATION + '/bundles/svelte.0.7.1.min.json', - logReads: __DEV__, - }, - }, - }, - }); -}); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/ts-extension.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/ts-extension.ts deleted file mode 100644 index 10575155d4f..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/ts-extension.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as childProcess from 'node-services/lib/child_process'; -// @ts-ignore -import SubWorkLoader from 'worker-loader?publicPath=/&name=sub-dynamic-worker.[hash:8].worker.js!./generic-2'; -import { initializeAll } from '../common/global'; -import { EXTENSIONS_LOCATION } from '../../constants'; - -declare const __DEV__: boolean; -childProcess.addDefaultForkHandler(SubWorkLoader); - -initializeAll().then(() => { - // Use require so that it only starts executing the chunk with all globals specified. - // eslint-disable-next-line - require('../workers/generic-worker').start({ - syncSandbox: true, - syncTypes: true, - extraMounts: { - '/extensions': { - fs: 'BundledHTTPRequest', - options: { - index: EXTENSIONS_LOCATION + '/extensions/index.json', - baseUrl: EXTENSIONS_LOCATION + '/extensions', - bundle: EXTENSIONS_LOCATION + '/bundles/ts.min.json', - logReads: __DEV__, - }, - }, - }, - }); -}); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/vue-worker.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/vue-worker.ts deleted file mode 100644 index a753cf32253..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/bootstrappers/vue-worker.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as childProcess from 'node-services/lib/child_process'; - -// @ts-ignore -import SubWorkLoader from 'worker-loader?publicPath=/&name=sub-dynamic-worker.[hash:8].worker.js!./generic-2'; - -import { EXTENSIONS_LOCATION } from '../../constants'; - -import { initializeAll } from '../common/global'; - -declare const __DEV__: boolean; - -childProcess.addDefaultForkHandler(SubWorkLoader); - -initializeAll().then(async () => { - // Use require so that it only starts executing the chunk with all globals specified. - // eslint-disable-next-line - require('../workers/generic-worker').start({ - syncSandbox: true, - syncTypes: true, - extraMounts: { - '/extensions': { - fs: 'BundledHTTPRequest', - options: { - index: EXTENSIONS_LOCATION + '/extensions/index.json', - baseUrl: EXTENSIONS_LOCATION + '/extensions', - bundle: EXTENSIONS_LOCATION + '/bundles/vetur.0.28.0.min.json', - logReads: __DEV__, - }, - }, - }, - }); -}); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/common/fs.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/common/fs.ts deleted file mode 100644 index 7aca22cf93a..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/common/fs.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { - commonPostMessage, - getGlobal, -} from '@codesandbox/common/lib/utils/global'; -import * as uuid from 'uuid'; - -import { FileSystemConfiguration } from '../../../../../../../../../standalone-packages/codesandbox-browserfs'; -import { IModule } from '../../../../../../../../../standalone-packages/codesandbox-browserfs/dist/node/backend/CodeSandboxFS'; -import { EXTENSIONS_LOCATION } from '../../constants'; -import { - appendFile, - mkdir, - rename, - rmdir, - unlink, - writeFile, -} from '../../SandboxFsSync/utils'; - -const global = getGlobal(); - -export const BROWSER_FS_CONFIG: FileSystemConfiguration = { - fs: 'MountableFileSystem', - options: { - '/': { fs: 'InMemory', options: {} }, - '/tmp': { fs: 'InMemory', options: {} }, - '/sandbox': { - fs: 'InMemory', - }, - '/vscode': { - fs: 'InMemory', - options: {}, - }, - '/extensions': { - fs: 'BundledHTTPRequest', - options: { - index: EXTENSIONS_LOCATION + '/extensions/index.json', - baseUrl: EXTENSIONS_LOCATION + '/extensions', - logReads: process.env.NODE_ENV === 'development', - }, - }, - // '/vscode': { - // fs: 'AsyncMirror', - // options: { - // sync: { - // fs: 'InMemory', - // }, - // async: { - // fs: 'IndexedDB', - // options: { - // storeName: 'VSCode', - // }, - // }, - // }, - // }, - }, -}; - -export async function initializeBrowserFS({ - syncSandbox = false, - syncTypes = false, - extraMounts = {}, -} = {}) { - let isInitialSync = true; - let types: { - [path: string]: { - module: IModule; - }; - } = {}; - - return new Promise(resolve => { - const config = { ...BROWSER_FS_CONFIG }; - let currentSandboxFs = {}; - - let fsId = uuid.v4(); - - if (syncSandbox) { - if (syncTypes) { - config.options['/sandbox/node_modules'] = { - fs: 'CodeSandboxFS', - options: { - manager: { - getTranspiledModules: () => types, - addModule(module: IModule) {}, - removeModule(module: IModule) {}, - moveModule(module: IModule, newPath) {}, - updateModule(module: IModule) {}, - }, - }, - }; - - config.options['/extensions/node_modules/typescript'] = { - fs: 'JSDelivrRequest', - options: { - dependency: 'typescript', - version: '5.2.2', - }, - }; - } - - config.options['/sandbox'] = { - fs: 'CodeSandboxEditorFS', - options: { - api: { - getSandboxFs: () => currentSandboxFs, - getJwt: () => '', - }, - }, - }; - } - - config.options = { ...config.options, ...extraMounts }; - - function touchFileSystem() { - // This forces the file watchers to emit, which causes typescript to reload - global.BrowserFS.BFSRequire('fs').rename( - '/sandbox/node_modules', - '/sandbox/node_modules', - () => {} - ); - } - - global.BrowserFS.configure(config, e => { - if (e) { - console.error(e); - return; - } - - if (syncSandbox) { - // Resolve after 3s, if it doesn't resolve, vscode won't be able to resolve the ext host - // and it won't try to reconnect. - const timeout = setTimeout(() => { - resolve(); - }, 3000); - - const callResolve = () => { - clearTimeout(timeout); - resolve(); - }; - - self.addEventListener('message', evt => { - // Some messages are specific to this worker - if (!evt.data.$fs_ids || !evt.data.$fs_ids.includes(fsId)) { - return; - } - - switch (evt.data.$type) { - case 'reset': { - isInitialSync = true; - fsId = uuid.v4(); - types = {}; - currentSandboxFs = {}; - commonPostMessage({ - $broadcast: true, - $type: 'sync-sandbox', - $fs_id: fsId, - $data: {}, - }); - break; - } - case 'types-sync': { - types = evt.data.$data; - touchFileSystem(); - if (isInitialSync) { - isInitialSync = false; - callResolve(); - } - break; - } - case 'package-types-sync': { - Object.assign(types, evt.data.$data); - touchFileSystem(); - break; - } - case 'types-remove': { - const deps = evt.data.$data; - - Object.keys(deps).forEach(depKey => { - delete types[depKey]; - }); - touchFileSystem(); - break; - } - case 'sandbox-fs': { - currentSandboxFs = evt.data.$data; - if (isInitialSync) { - commonPostMessage({ - $broadcast: true, - $type: 'sync-types', - $fs_id: fsId, - $data: {}, - }); - } else { - callResolve(); - } - break; - } - case 'append-file': { - const module = evt.data.$data; - appendFile(currentSandboxFs, module); - break; - } - case 'write-file': { - const module = evt.data.$data; - writeFile(currentSandboxFs, module); - break; - } - case 'rename': { - const { fromPath, toPath } = evt.data.$data; - rename(currentSandboxFs, fromPath, toPath); - break; - } - case 'rmdir': { - const directory = evt.data.$data; - rmdir(currentSandboxFs, directory); - break; - } - case 'unlink': { - const module = evt.data.$data; - unlink(currentSandboxFs, module); - break; - } - case 'mkdir': { - const directory = evt.data.$data; - mkdir(currentSandboxFs, directory); - break; - } - } - }); - - commonPostMessage({ - $broadcast: true, - $type: 'sync-sandbox', - $fs_id: fsId, - $data: {}, - }); - } else { - resolve(); - } - }); - }); -} diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/common/global.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/common/global.ts deleted file mode 100644 index a5e7019bd09..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/common/global.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable global-require */ -import { EventEmitter } from 'events'; - -import requirePolyfills from '@codesandbox/common/lib/load-dynamic-polyfills'; - -const ctx: any = self as any; -declare const __DEV__: boolean; - -if (typeof Worker === 'undefined') { - ctx.importScripts('https://unpkg.com/subworkers@1.0.1/subworkers.js'); -} - -export const initializePolyfills = () => { - return requirePolyfills(); -}; - -export const loadBrowserFS = () => { - ctx.importScripts( - `${process.env.CODESANDBOX_HOST}/static/browserfs12/browserfs.min.js` - ); -}; - -export const initializeGlobals = () => { - // We need to initialize some node environment stubs - ctx.process = ctx.BrowserFS.BFSRequire('process'); - ctx.process.platform = 'linux'; - ctx.process.stdin = new EventEmitter(); - ctx.process.env.NODE_ENV = __DEV__ ? 'development' : 'production'; - ctx.Buffer = ctx.BrowserFS.BFSRequire('buffer').Buffer; - ctx.setTimeout = setTimeout.bind(ctx); - ctx.clearTimeout = clearTimeout.bind(ctx); - ctx.setImmediate = (func, delay) => setTimeout(func, delay); - ctx.clearImmediate = id => ctx.clearTimeout(id); -}; - -export function initializeAll() { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async resolve => { - await initializePolyfills(); - loadBrowserFS(); - initializeGlobals(); - - resolve(); - }); -} diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/services/WebsocketLSP.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/services/WebsocketLSP.ts deleted file mode 100644 index 7bf7d5e066a..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/services/WebsocketLSP.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { commonPostMessage } from '@codesandbox/common/lib/utils/global'; -import { - IForkHandler, - IForkHandlerCallback, -} from 'node-services/lib/child_process'; -import io from 'socket.io-client'; - -// We want to make sure that our pids are consistent. -// By counting pids based on endpoint we should consistently -// pass the correct pid number related to the language server, -// though it does require VSCode to always fire up the language -// servers in the same order. The pid count decreases as -// the WebsocketLSP is disposed -const pidCountByEndpoint: { - [endpoint: string]: number; -} = {}; - -export class WebsocketLSP implements IForkHandler { - io: typeof io.Socket; - listeners = new Set(); - - messagesQueue: any[] = []; - endpoint: string; - - constructor() { - const instance = this; - self.addEventListener('message', function listener(message) { - if (message.data.$type === 'respond_lsp_endpoint') { - self.removeEventListener('message', listener); - instance.connect(message.data.$data); - } - }); - commonPostMessage({ - $type: 'request_lsp_endpoint', - }); - } - - private connect(endpoint: string) { - if (!(endpoint in pidCountByEndpoint)) { - pidCountByEndpoint[endpoint] = 0; - } - - this.endpoint = endpoint; - this.io = io( - endpoint + `?type=language-server&pid=${pidCountByEndpoint[endpoint]++}` - ); - this.io.on('connect', () => { - this.messagesQueue.forEach(message => { - this.postMessage(message); - }); - }); - this.io.on('connect_error', error => { - // eslint-disable-next-line - console.log('WEBSOCKET_LSP - ERROR', error); - }); - this.io.on('disconnect', reason => { - console.error('WEBSOCKET_LSP - CLOSE', reason); - }); - - this.io.on('language-server', data => { - /* - const json = event.data.split('\n').find(line => line[0] === '{'); - console.log('OUT', JSON.stringify(JSON.parse(json), null, 2)); - */ - this.listeners.forEach(listener => { - listener({ - data: { - $data: `Content-Length: ${data.length + 1}\r\n\r\n${data}\n`, - $type: 'stdout', - }, - }); - }); - }); - } - - postMessage(message) { - if (message.$type === 'input-write') { - if (this.io.connected && message.$data) { - /* - console.log('IN', JSON.stringify(JSON.parse(message.$data), null, 2)); - */ - this.io.emit('language-server', message.$data); - } else if (message.$data) { - this.messagesQueue.push(message); - } - } - } - - // Since setting up the connection is ASYNC, we rather create a list - // of listeners. They all use "message" event anyways - addEventListener(_: string, callback: IForkHandlerCallback) { - this.listeners.add(callback); - } - - removeEventListener(_: string, callback: IForkHandlerCallback) { - this.listeners.delete(callback); - } - - terminate() { - // eslint-disable-next-line - console.log('WEBSOCKET_LSP - TERMINATE'); - pidCountByEndpoint[this.endpoint]--; - this.io.close(); - } -} diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/services/searchService.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/services/searchService.ts deleted file mode 100644 index c854ae412e3..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/services/searchService.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - initializeGlobals, - initializePolyfills, - loadBrowserFS, -} from '../common/global'; -import { initializeBrowserFS } from '../common/fs'; - -initializePolyfills(); -loadBrowserFS(); -initializeGlobals(); - -async function initialize() { - await initializeBrowserFS({ syncSandbox: true, syncTypes: true }); - - self.addEventListener('message', e => { - if (e.data.$type === 'input-write') { - const { $type, $data } = e.data.$data; - if ($type === 'file-search') { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { query } = $data; - } - } - }); -} - -initialize(); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/workers/ext-host-worker.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/workers/ext-host-worker.ts deleted file mode 100644 index bee971bec9a..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/workers/ext-host-worker.ts +++ /dev/null @@ -1,61 +0,0 @@ -// This is the base worker that launches the extension host - -import _debug from '@codesandbox/common/lib/utils/debug'; -import { commonPostMessage } from '@codesandbox/common/lib/utils/global'; - -import { EXTENSIONS_LOCATION } from '../../constants'; -import loader from '../../vscode-script-loader'; -import { initializeBrowserFS } from '../common/fs'; - -const debug = _debug('cs:cp-worker'); - -debug('Starting Extension Host Worker'); - -declare const __DEV__: boolean; - -const ctx: any = self; - -self.addEventListener('message', async e => { - const { data } = e; - - if (data.$type === 'worker-manager') { - if (data.$event === 'init') { - debug('Initializing BrowserFS'); - await initializeBrowserFS({ - syncSandbox: true, - syncTypes: true, - extraMounts: { - '/extensions': { - fs: 'BundledHTTPRequest', - options: { - index: EXTENSIONS_LOCATION + '/extensions/index.json', - baseUrl: EXTENSIONS_LOCATION + '/extensions', - bundle: EXTENSIONS_LOCATION + '/bundles/ext-host.min.json', - logReads: __DEV__, - }, - }, - }, - }); - debug('Initialized BrowserFS', data.data.env); - - const process = ctx.BrowserFS.BFSRequire('process'); - process.cwd = () => data.data.cwd || '/sandbox'; - process.env = data.data.env || {}; - process.env.HOME = '/home'; - - loader(true)(() => { - ctx.require( - ['vs/workbench/services/extensions/node/extensionHostProcess'], - () => { - commonPostMessage({ - $type: 'worker-client', - $event: 'initialized', - }); - } - ); - }); - } - } -}); - -commonPostMessage({ $type: 'ready' }); diff --git a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/workers/generic-worker.ts b/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/workers/generic-worker.ts deleted file mode 100644 index ba7bfc0526d..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/extensionHostWorker/workers/generic-worker.ts +++ /dev/null @@ -1,139 +0,0 @@ -// This is the default worker that will be called if no worker is specified. -// It's function is to execute the code of the path that's given to it. - -import { basename } from 'path'; - -import _debug from '@codesandbox/common/lib/utils/debug'; -import { commonPostMessage } from '@codesandbox/common/lib/utils/global'; -import { default as Module } from 'node-services/lib/module'; -import resolve from 'resolve'; - -import { initializeBrowserFS } from '../common/fs'; - -export function start({ - syncSandbox = true, - syncTypes = true, - extraMounts = {}, -} = {}) { - const DEBUG_NAME = `cs:cp-bootstrap-worker`; - let debug = _debug(DEBUG_NAME); - - debug('Starting Worker'); - - const ctx = self as any; - - const pendingMessages: any[] = []; - let initialized = false; - - const { log } = console; - - function processMessage(data) { - const process = ctx.BrowserFS.BFSRequire('process'); - const { $data, $type } = data; - - if (!data.$broadcast) { - debug('message', data); - } - - if ($type === 'message') { - process.emit('message', JSON.parse($data), undefined); - } else if ($data && $data.$type) { - process.stdin.emit('data', $data.$data); - } else if ($type === 'input-write') { - process.stdin.emit('data', $data); - } - } - - const initializeProcess = (process, data) => { - process.send = (message, _a, _b, callback) => { - const m = { - $type: 'message', - $data: JSON.stringify(message), - }; - - debug('process.send', m); - - commonPostMessage(m); - - if (typeof _a === 'function') { - _a(null); - } else if (typeof _b === 'function') { - _b(null); - } else if (typeof callback === 'function') { - callback(null); - } - }; - - process.stdout = { - write: (message, callback) => { - const m = { - $type: 'stdout', - $data: message, - }; - - debug('process.stdout.write', m); - - commonPostMessage(m); - - if (callback) { - callback(null, null); - } - }, - }; - - process.env = data.data.env || {}; - process.env.HOME = '/home'; - process.cwd = () => data.data.cwd || '/sandbox'; - process.argv = ['node', data.data.entry, ...data.data.argv] || []; - }; - - self.addEventListener('message', async e => { - const { data } = e; - - if (data.$type === 'worker-manager') { - if (data.$event === 'init') { - debug = _debug(`${DEBUG_NAME}:${basename(data.data.entry)}`); - debug('Initializing BrowserFS'); - await initializeBrowserFS({ syncSandbox, syncTypes, extraMounts }); - debug('Initialized BrowserFS', data); - - const process = ctx.BrowserFS.BFSRequire('process'); - initializeProcess(process, data); - - if (data.data.entry) { - const resolvedPath = resolve.sync(data.data.entry, { - basedir: '/', - }); - - try { - debug('Loading module...', resolvedPath); - const module = new Module(resolvedPath); - module.load(resolvedPath); - - initialized = true; - - // Sometimes the module overwrites console.log for the VSCode output channel, we don't want this with - // debugging - // eslint-disable-next-line - console.log = log; - - debug( - 'Loaded module, now evaluating remaining messages', - pendingMessages - ); - - pendingMessages.forEach(processMessage); - } catch (error) { - console.error(error); - } - } - } - } else if (!initialized) { - pendingMessages.push(data); - } else { - processMessage(data); - } - }); -} - -commonPostMessage({ $type: 'ready' }); diff --git a/packages/app/src/app/overmind/effects/vscode/index.ts b/packages/app/src/app/overmind/effects/vscode/index.ts deleted file mode 100644 index 02fc4db57a6..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/index.ts +++ /dev/null @@ -1,1443 +0,0 @@ -import DEFAULT_PRETTIER_CONFIG from '@codesandbox/common/lib/prettify-default-config'; -import { resolveModule } from '@codesandbox/common/lib/sandbox/modules'; -import { IReaction, json } from 'overmind'; -import getTemplate from '@codesandbox/common/lib/templates'; -import { - CurrentUser, - EditorSelection, - Module, - ModuleCorrection, - ModuleError, - Sandbox, - SandboxFs, - Settings, - UserViewRange, -} from '@codesandbox/common/lib/types'; -import { notificationState } from '@codesandbox/common/lib/utils/notifications'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { - NotificationMessage, - NotificationStatus, -} from '@codesandbox/notifications/lib/state'; -import { CodeReferenceMetadata } from 'app/graphql/types'; -import { Context } from 'app/overmind'; -import { indexToLineAndColumn } from 'app/overmind/utils/common'; -import prettify from 'app/src/app/utils/prettify'; -import { blocker } from 'app/utils/blocker'; -import { listen } from 'codesandbox-api'; -import { debounce } from 'lodash-es'; -import * as childProcess from 'node-services/lib/child_process'; -import { TextOperation } from 'ot'; - -import FontFaceObserver from 'fontfaceobserver'; -import io from 'socket.io-client'; - -import { EXTENSIONS_LOCATION, VIM_EXTENSION_ID } from './constants'; -import { - initializeCodeSandboxTheme, - initializeCustomTheme, - initializeExtensionsFolder, - initializeSettings, - initializeSnippetDirectory, - initializeThemeCache, -} from './initializers'; -import { Linter } from './Linter'; -import { - ModelsHandler, - OnFileChangeData, - OnOperationAppliedData, - onSelectionChangeData, -} from './ModelsHandler'; -import SandboxFsSync from './SandboxFsSync'; -import { getSelection } from './utils'; -import loadScript from './vscode-script-loader'; -import { Workbench } from './Workbench'; -import { composeMenuAppTree, MenuAppItems } from './composeMenuAppTree'; - -export type VsCodeOptions = { - getCurrentSandbox: () => Sandbox | null; - getCurrentModule: () => Module | null; - getSandboxFs: () => SandboxFs; - getCurrentUser: () => CurrentUser | null; - onCodeChange: (data: OnFileChangeData) => void; - onOperationApplied: (data: OnOperationAppliedData) => void; - onSelectionChanged: (selection: onSelectionChangeData) => void; - onViewRangeChanged: (viewRange: UserViewRange) => void; - onCommentClick: (payload: { - commentIds: string[]; - bounds: { - left: number; - top: number; - bottom: number; - right: number; - }; - }) => void; - reaction: IReaction; - // These two should be removed - getSignal: any; - getState: any; -}; - -declare global { - interface Window { - CSEditor: any; - monaco: any; - } -} - -/** - * Responsible for rendering React components for files that are supported - */ -export interface ICustomEditorApi { - getCustomEditor( - modulePath: string - ): false | ((container: HTMLElement, extraProps: object) => void) | null; -} - -const context: any = window; - -/** - * Handles the VSCode instance for the whole app. The goal is to deprecate/remove this service at one point - * and let the VSCode codebase handle the initialization of all elements. We are going for a gradual approach though, - * that's why in the first phase we let the CodeSandbox application handle all the initialization of the VSCode - * parts. - */ -export class VSCodeEffect { - public initialized: Promise; - public sandboxFsSync: SandboxFsSync; - private mountableFilesystem: any; - - private monaco: any; - private editorApi: any; - private clientExtensionHost: any; - private containerExtensionHost: any; - private options: VsCodeOptions; - private controller: any; - private commandService = blocker(); - private extensionService = blocker(); - private extensionEnablementService = blocker(); - private workbench: Workbench; - private settings: Settings; - private linter: Linter | null; - private modelsHandler: ModelsHandler; - private modelSelectionListener: { dispose: Function }; - private modelCursorPositionListener: { dispose: Function }; - private modelViewRangeListener: { dispose: Function }; - private readOnly: boolean; - private elements = { - editor: document.createElement('div'), - editorPart: document.createElement('div'), - statusbar: document.createElement('div'), - }; - - private menuAppItems: MenuAppItems = []; - - private customEditorApi: ICustomEditorApi = { - getCustomEditor: () => null, - }; - - onSelectionChangeDebounced: VsCodeOptions['onSelectionChanged'] & { - cancel(): void; - }; - - /** - * Look up the preferred (last defined) keybinding for a command. - * @returns {ResolvedKeybinding} The preferred keybinding or null if the command is not bound. - */ - public lookupKeybinding: ( - commandId: string - ) => { getLabel(): string | null } | null; - - /** - * Extract `contextMatchesRules` method from ContextKeyService - * to match rules and conditionals in the editor - */ - public contextMatchesRules: (rules: any | undefined) => boolean; - - public initialize(options: VsCodeOptions) { - this.options = options; - this.controller = { - getState: options.getState, - getSignal: options.getSignal, - }; - this.onSelectionChangeDebounced = debounce(options.onSelectionChanged, 200); - - this.prepareElements(); - - this.options.reaction( - state => ({ - fileComments: json(state.comments.fileComments), - currentCommentId: state.comments.currentCommentId, - }), - ({ fileComments, currentCommentId }) => { - if (this.modelsHandler) { - this.modelsHandler.applyComments(fileComments, currentCommentId); - } - } - ); - this.listenToCommentClick(); - - // We instantly create a sandbox sync, as we want our - // extension host to get its messages handled to initialize - // correctly - this.sandboxFsSync = new SandboxFsSync({ - getSandboxFs: () => ({}), - getCurrentSandbox: () => null, - }); - - import( - // @ts-ignore - 'worker-loader?publicPath=/&name=client-ext-host-worker.[hash:8].worker.js!./extensionHostWorker/bootstrappers/client-ext-host' - ).then(ExtHostWorkerLoader => { - this.clientExtensionHost = ExtHostWorkerLoader.default; - }); - - import( - // @ts-ignore - 'worker-loader?publicPath=/&name=container-ext-host-worker.[hash:8].worker.js!./extensionHostWorker/bootstrappers/container-ext-host' - ).then(ExtHostWorkerLoader => { - this.containerExtensionHost = ExtHostWorkerLoader.default; - }); - - this.initialized = this.initializeFileSystem().then(mfs => { - this.mountableFilesystem = mfs; - // We want to initialize before VSCode, but after browserFS is configured - // For first-timers initialize a theme in the cache so it doesn't jump colors - initializeExtensionsFolder(); - initializeCodeSandboxTheme(); - initializeCustomTheme(); - initializeThemeCache(); - initializeSettings(); - initializeSnippetDirectory(); - - this.setVimExtensionEnabled( - localStorage.getItem('settings.vimmode') === 'true' - ); - - return new FontFaceObserver('MonoLisa').load(); - }); - - // Only set the read only state when the editor is initialized. - this.initialized.then(() => { - // ReadOnly mode is derivative, it's based on a couple conditions. The most important - // one is the `restricted` flag. The second most important one is live. - // If you're in a classroom live session as spectator, you should not be allowed to edit. - - options.reaction( - state => { - const restricted = state.editor.currentSandbox?.restricted; - - if (restricted) { - // Returns canEdit, false always - return false; - } - - const hasGitInfo = Boolean(state.editor.currentSandbox?.git); - const isNotLive = !state.live.isLive; - const isOpenRoom = state.live.roomInfo?.mode === 'open'; - const isClassroomAndCurrentEditor = - state.live.roomInfo?.mode === 'classroom' && - state.live.isCurrentEditor; - - const canEdit = - hasGitInfo || - isNotLive || - isOpenRoom || - isClassroomAndCurrentEditor; - - return canEdit; - }, - canEdit => { - this.setReadOnly(!canEdit); - } - ); - }); - - return this.initialized; - } - - public isModuleOpened(module: Module) { - return this.modelsHandler.isModuleOpened(module); - } - - public async getCodeReferenceBoundary( - commentId: string, - reference: CodeReferenceMetadata - ) { - this.revealPositionInCenterIfOutsideViewport(reference.anchor, 1); - - return new Promise((resolve, reject) => { - let checkCount = 0; - function findActiveComment() { - checkCount++; - - if (checkCount === 20) { - reject(new Error('Could not find the comment glyph')); - return; - } - - setTimeout(() => { - const commentGlyphs = document.querySelectorAll( - '.editor-comments-glyph' - ); - const el = Array.from(commentGlyphs).find(glyphEl => - glyphEl.className.includes(commentId) - ); - - if (el) { - resolve(el.getBoundingClientRect()); - } else { - findActiveComment(); - } - }, 10); - } - findActiveComment(); - }); - } - - public getEditorElement( - getCustomEditor: ICustomEditorApi['getCustomEditor'] - ) { - this.customEditorApi.getCustomEditor = getCustomEditor; - return this.elements.editor; - } - - public getMenuAppItems(): MenuAppItems { - return this.menuAppItems; - } - - public getStatusbarElement() { - return this.elements.statusbar; - } - - public runCommand = async (id: string, ...args: any[]) => { - const commandService = await this.commandService.promise; - - return commandService.executeCommand(id, ...args); - }; - - public callCallbackError(id: string, message?: string) { - // @ts-ignore - if (window.cbs && window.cbs[id]) { - const errorMessage = - message || 'Something went wrong while saving the file.'; - // @ts-ignore - window.cbs[id](new Error(errorMessage), undefined); - // @ts-ignore - delete window.cbs[id]; - } - } - - public callCallback(id: string) { - // @ts-ignore - if (window.cbs && window.cbs[id]) { - // @ts-ignore - window.cbs[id](undefined, undefined); - // @ts-ignore - delete window.cbs[id]; - } - } - - public setVimExtensionEnabled(enabled: boolean) { - if (enabled) { - this.enableExtension(VIM_EXTENSION_ID); - } else { - // Auto disable vim extension - if ( - ([null, undefined] as Array).includes( - localStorage.getItem('vs-global://extensionsIdentifiers/disabled') - ) - ) { - localStorage.setItem( - 'vs-global://extensionsIdentifiers/disabled', - '[{"id":"vscodevim.vim"}]' - ); - } - - this.disableExtension(VIM_EXTENSION_ID); - } - } - - public syncModule(module: Module) { - this.modelsHandler.syncModule(module); - } - - public async applyOperation(moduleShortid: string, operation: TextOperation) { - if (!this.modelsHandler) { - return; - } - - await this.modelsHandler.applyOperation(moduleShortid, operation); - } - - public updateOptions(options: { readOnly: boolean }) { - if (this.editorApi) { - const editor = this.editorApi.getActiveCodeEditor(); - - if (editor) { - editor.updateOptions(options); - } - } - } - - public clearUserSelections(userId: string) { - if (!this.modelsHandler) { - return; - } - - this.modelsHandler.clearUserSelections(userId); - } - - public updateUserSelections( - module: Module, - userSelections: EditorSelection[] - ) { - if (!this.modelsHandler) { - return; - } - - this.modelsHandler.updateUserSelections(module, userSelections); - } - - public setReadOnly(enabled: boolean) { - this.readOnly = enabled; - this.updateOptions({ readOnly: enabled }); - } - - public updateLayout = (width: number, height: number) => { - if (this.editorApi) { - this.editorApi.editorPart.layout(width, height); - } - }; - - public resetLayout() { - if (this.editorApi) { - // We have to wait for the layout to actually update in the DOM - requestAnimationFrame(() => { - const rootEl = document.querySelector('#vscode-container'); - if (rootEl) { - const boundingRect = rootEl.getBoundingClientRect(); - - this.editorApi.editorPart.layout( - boundingRect.width, - boundingRect.height - ); - } - }); - } - } - - /* - We need to use a callback to set the sandbox-fs into the state of Overmind. The reason - is that we internally read from this state to get information about the files. It is really - messy, but we will move to a completely internal filesystem soon - */ - public async changeSandbox(sandbox: Sandbox, setFs: (fs: SandboxFs) => void) { - await this.initialized; - - const isFirstLoad = !this.modelsHandler; - - const { isServer } = getTemplate(sandbox.template); - - try { - this.mountableFilesystem.umount('/root/.cache'); - } catch { - // - } - try { - this.mountableFilesystem.umount('/sandbox/node_modules'); - } catch { - // - } - try { - // After navigation, this mount is already mounted and throws error, - // which cause that Phonenix is not reconnected, so the file's content cannot be seen - // https://github.com/codesandbox/codesandbox-client/issues/4143 - this.mountableFilesystem.umount('/home/sandbox/.cache'); - } catch { - // - } - - if (isServer && this.options.getCurrentUser()?.experiments.containerLsp) { - childProcess.addDefaultForkHandler(this.createContainerForkHandler()); - const socket = this.createWebsocketFSRequest(); - const cache = await this.createFileSystem('WebsocketFS', { - socket, - }); - const nodeModules = await this.createFileSystem('WebsocketFS', { - socket, - }); - - this.mountableFilesystem.mount('/home/sandbox/.cache', cache); - this.mountableFilesystem.mount('/sandbox/node_modules', nodeModules); - } else { - childProcess.addDefaultForkHandler(this.clientExtensionHost); - const nodeModules = await this.createFileSystem('CodeSandboxFS', { - manager: { - getTranspiledModules: () => this.sandboxFsSync.getTypes(), - addModule() {}, - removeModule() {}, - moveModule() {}, - updateModule() {}, - }, - }); - this.mountableFilesystem.mount('/sandbox/node_modules', nodeModules); - } - - if (isFirstLoad) { - const container = this.elements.editor; - - await new Promise(resolve => { - loadScript(true, ['vs/editor/codesandbox.editor.main'])(resolve); - }).then(() => this.loadEditor(window.monaco, container)); - } - - if (!isFirstLoad) { - this.modelsHandler.dispose(); - this.sandboxFsSync.dispose(); - } - - this.modelsHandler = new ModelsHandler( - this.editorApi, - this.monaco, - sandbox, - this.onFileChange, - this.onOperationApplied - ); - this.sandboxFsSync = new SandboxFsSync(this.options); - - setFs(this.sandboxFsSync.create(sandbox)); - - if (isFirstLoad) { - this.sandboxFsSync.sync(() => { - // Once we have synced the fs, reload the TS project, as some new dependencies might have been added - this.runCommand('typescript.reloadProjects'); - }); - } else { - this.editorApi.extensionService.stopExtensionHost(); - this.sandboxFsSync.sync(() => { - this.editorApi.extensionService.startExtensionHost(); - }); - } - } - - public setModuleCode(module: Module, triggerChangeEvent = false) { - if (!this.modelsHandler) { - return; - } - - this.modelsHandler.setModuleCode(module, triggerChangeEvent); - } - - public async closeAllTabs() { - if (this.editorApi) { - const groupsToClose = this.editorApi.editorService.editorGroupService.getGroups(); - - await Promise.all( - groupsToClose.map(group => - Promise.all([ - group.closeAllEditors(), - this.editorApi.editorService.editorGroupService.removeGroup(group), - ]) - ) - ); - } - } - - public async updateTabsPath(oldPath: string, newPath: string) { - return this.modelsHandler.updateTabsPath(oldPath, newPath); - } - - public async openModule(module: Module) { - await this.initialized; - - // We use an animation frame here, because we want the rest of the logic to finish running, - // allowing for a paint, like selections in explorer. For this to work we have to ensure - // that we are actually indeed still trying to open this file, as we might have changed - // the file - return new Promise(resolve => { - requestAnimationFrame(async () => { - const currentModule = this.options.getCurrentModule(); - if (currentModule && module.id === currentModule.id) { - try { - const model = await this.modelsHandler.changeModule(module); - this.lint(module.title, model); - resolve(); - } catch (error) { - // We might try to open a module that is not actually opened in the editor, - // but the configuration wizard.. currently this throws an error as there - // is really no good way to identify when it happen. This needs to be - // improved in next version - } - } - }); - }); - } - - public setErrors = (errors: ModuleError[]) => { - const activeEditor = this.editorApi.getActiveCodeEditor(); - - if (activeEditor) { - if (errors.length > 0) { - const currentPath = this.getCurrentModelPath(); - const thisModuleErrors = errors.filter( - error => error.path === currentPath - ); - const errorMarkers = thisModuleErrors - .map(error => { - if (error) { - return { - severity: this.monaco.MarkerSeverity.Error, - startColumn: 1, - startLineNumber: error.line, - endColumn: error.column, - endLineNumber: error.line + 1, - message: error.message, - }; - } - - return null; - }) - .filter(x => x); - - // use raf to make sure that when we spam the editor, we don't clog the ui thread - requestAnimationFrame(() => { - this.monaco.editor.setModelMarkers( - activeEditor.getModel(), - 'error', - errorMarkers - ); - }); - } else { - // use raf to make sure that when we spam the editor, we don't clog the ui thread - requestAnimationFrame(() => { - this.monaco.editor.setModelMarkers( - activeEditor.getModel(), - 'error', - [] - ); - }); - } - } - }; - - public async openDiff(sandboxId: string, module: Module, oldCode: string) { - if (!module.path) { - return; - } - - const recoverPath = `/recover/${sandboxId}/recover-${module.path.replace( - /\//g, - ' ' - )}`; - const filePath = `/sandbox${module.path}`; - const fileSystem = window.BrowserFS.BFSRequire('fs'); - - // We have to write a recover file to the filesystem, we save it behind - // the sandboxId - if (!fileSystem.existsSync(`/recover/${sandboxId}`)) { - fileSystem.mkdirSync(`/recover/${sandboxId}`); - } - // We write the recover file with the old code, as the new code is already applied - fileSystem.writeFileSync(recoverPath, oldCode); - - // We open a conflict resolution editor for the files - this.editorApi.editorService.openEditor({ - leftResource: this.monaco.Uri.from({ - scheme: 'conflictResolution', - path: recoverPath, - }), - rightResource: this.monaco.Uri.file(filePath), - label: `Recover - ${module.path}`, - options: { - pinned: true, - }, - }); - } - - public clearComments() { - this.modelsHandler.clearComments(); - } - - public setCorrections = (corrections: ModuleCorrection[]) => { - const activeEditor = this.editorApi.getActiveCodeEditor(); - if (activeEditor) { - if (corrections.length > 0) { - const currentPath = this.getCurrentModelPath(); - const correctionMarkers = corrections - .filter(correction => correction.path === currentPath) - .map(correction => { - if (correction) { - return { - severity: - correction.severity === 'warning' - ? this.monaco.MarkerSeverity.Warning - : this.monaco.MarkerSeverity.Notice, - startColumn: correction.column, - startLineNumber: correction.line, - endColumn: correction.columnEnd || 1, - endLineNumber: correction.lineEnd || correction.line + 1, - message: correction.message, - source: correction.source, - }; - } - - return null; - }) - .filter(x => x); - - this.monaco.editor.setModelMarkers( - activeEditor.getModel(), - 'correction', - correctionMarkers - ); - } else { - this.monaco.editor.setModelMarkers( - activeEditor.getModel(), - 'correction', - [] - ); - } - } - }; - - /** - * Reveal position in editor - * @param scrollType 0 = smooth, 1 = immediate - */ - revealPositionInCenterIfOutsideViewport(pos: number, scrollType: 0 | 1 = 0) { - const activeEditor = this.editorApi.getActiveCodeEditor(); - - if (activeEditor) { - const model = activeEditor.getModel(); - - if (model) { - const lineColumnPos = indexToLineAndColumn( - model.getLinesContent() || [], - pos - ); - - activeEditor.revealPositionInCenterIfOutsideViewport( - lineColumnPos, - scrollType - ); - } - } - } - - /** - * Reveal line in editor - * @param scrollType 0 = smooth, 1 = immediate - */ - revealLine(lineNumber: number, scrollType: 0 | 1 = 0) { - const activeEditor = this.editorApi.getActiveCodeEditor(); - - if (activeEditor) { - activeEditor.revealLine(lineNumber, scrollType); - } - } - - /** - * Reveal revealLine in editor - * @param scrollType 0 = smooth, 1 = immediate - */ - revealRange(range: UserViewRange, scrollType: 0 | 1 = 0) { - const activeEditor = this.editorApi.getActiveCodeEditor(); - - if (activeEditor) { - activeEditor.revealRange(range, scrollType); - } - } - - /** - * Set the selection inside the editor - * @param head Start of the selection - * @param anchor End of the selection - */ - setSelection(head: number, anchor: number) { - const activeEditor = this.editorApi.getActiveCodeEditor(); - if (!activeEditor) { - return; - } - - const model = activeEditor.getModel(); - if (!model) { - return; - } - - const headPos = indexToLineAndColumn(model.getLinesContent() || [], head); - const anchorPos = indexToLineAndColumn( - model.getLinesContent() || [], - anchor - ); - const range = new this.monaco.Range( - headPos.lineNumber, - headPos.column, - anchorPos.lineNumber, - anchorPos.column - ); - - this.revealRange(range); - activeEditor.setSelection(range); - } - - // Communicates the endpoint for the WebsocketLSP - private createContainerForkHandler() { - return () => { - const host = this.containerExtensionHost(); - host.addEventListener('message', event => { - if (event.data.$type === 'request_lsp_endpoint') { - event.target.postMessage({ - $type: 'respond_lsp_endpoint', - $data: this.getLspEndpoint(), - }); - } - }); - return host; - }; - } - - private getLspEndpoint() { - // return 'ws://localhost:1023'; - // TODO: merge host logic with executor-manager - const sseHost = process.env.ENDPOINT || 'https://codesandbox.io'; - return sseHost.replace( - 'https://', - `wss://${this.options.getCurrentSandbox()?.id}-lsp.sse.` - ); - } - - private createFileSystem(type: string, options: any) { - return new Promise((resolve, reject) => { - window.BrowserFS.FileSystem[type].Create(options, (error, fs) => { - if (error) { - reject(error); - } else { - resolve(fs); - } - }); - }); - } - - private createWebsocketFSRequest() { - const socket = io(`${this.getLspEndpoint()}?type=go-to-definition`); - return { - emit: (data, cb) => { - socket.emit('go-to-definition', data, cb); - }, - dispose: () => { - socket.close(); - }, - }; - } - - private async disableExtension(id: string) { - const extensionService = await this.extensionService.promise; - const extensionEnablementService = await this.extensionEnablementService - .promise; - - const extensionDescription = await extensionService.getExtension(id); - - if (extensionDescription) { - const { toExtension } = context.require( - 'vs/workbench/services/extensions/common/extensions' - ); - const extension = toExtension(extensionDescription); - extensionEnablementService.setEnablement([extension], 0); - } - } - - private async initializeFileSystem() { - const fileSystems = await Promise.all([ - this.createFileSystem('InMemory', {}), - this.createFileSystem('CodeSandboxEditorFS', { - api: { - getSandboxFs: this.options.getSandboxFs, - getJwt: () => this.options.getState().jwt, - }, - }), - this.createFileSystem('LocalStorage', {}), - this.createFileSystem('LocalStorage', {}), - Promise.resolve().then(() => - Promise.all([ - this.createFileSystem('InMemory', {}), - this.createFileSystem('BundledHTTPRequest', { - index: EXTENSIONS_LOCATION + '/extensions/index.json', - baseUrl: EXTENSIONS_LOCATION + '/extensions', - bundle: EXTENSIONS_LOCATION + '/bundles/main.min.json', - logReads: process.env.NODE_ENV === 'development', - }), - ]).then(([writableExtensions, readableExtensions]) => - this.createFileSystem('OverlayFS', { - writable: writableExtensions, - readable: readableExtensions, - }) - ) - ), - this.createFileSystem('InMemory', {}), - this.createFileSystem('InMemory', {}), - ]); - - const [ - root, - sandbox, - vscode, - home, - extensions, - customTheme, - recover, - ] = fileSystems; - - const mfs = (await this.createFileSystem('MountableFileSystem', { - '/': root, - '/sandbox': sandbox, - '/vscode': vscode, - '/home': home, - '/extensions': extensions, - '/extensions/custom-theme': customTheme, - '/recover': recover, - })) as any; - - window.BrowserFS.initialize(mfs); - - return mfs; - } - - private initializeReactions() { - const { reaction } = this.options; - - reaction(state => state.preferences.settings, this.changeSettings, { - nested: true, - immediate: true, - }); - } - - private async enableExtension(id: string) { - const extensionEnablementService = await this.extensionEnablementService - .promise; - const extensionIdentifier = ( - await extensionEnablementService.getDisabledExtensions() - ).find(ext => ext.id === id); - - if (extensionIdentifier) { - // Sadly we have to call a private api for this. Might change this once we have extension management - // built in. - extensionEnablementService._enableExtension(extensionIdentifier); - } - } - - private async loadEditor(monaco: any, container: HTMLElement) { - this.monaco = monaco; - this.workbench = new Workbench(monaco, this.controller, this.runCommand); - - if (localStorage.getItem('settings.vimmode') === 'true') { - this.enableExtension(VIM_EXTENSION_ID); - } - - this.workbench.addWorkbenchActions(); - - const r = window.require; - const [ - { IEditorService }, - { ICodeEditorService }, - { ITextFileService }, - { ILifecycleService }, - { IEditorGroupsService }, - { IStatusbarService }, - { IExtensionService }, - { CodeSandboxService }, - { CodeSandboxConfigurationUIService }, - { ICodeSandboxEditorConnectorService }, - { ICommandService }, - { SyncDescriptor }, - { IInstantiationService }, - { IExtensionEnablementService }, - { IContextViewService }, - { MenuRegistry }, - { IKeybindingService }, - { IContextKeyService }, - ] = [ - r('vs/workbench/services/editor/common/editorService'), - r('vs/editor/browser/services/codeEditorService'), - r('vs/workbench/services/textfile/common/textfiles'), - r('vs/platform/lifecycle/common/lifecycle'), - r('vs/workbench/services/editor/common/editorGroupsService'), - r('vs/platform/statusbar/common/statusbar'), - r('vs/workbench/services/extensions/common/extensions'), - r('vs/codesandbox/services/codesandbox/browser/codesandboxService'), - r('vs/codesandbox/services/codesandbox/configurationUIService'), - r( - 'vs/codesandbox/services/codesandbox/common/codesandboxEditorConnector' - ), - r('vs/platform/commands/common/commands'), - r('vs/platform/instantiation/common/descriptors'), - r('vs/platform/instantiation/common/instantiation'), - r('vs/platform/extensionManagement/common/extensionManagement'), - r('vs/platform/contextview/browser/contextView'), - r('vs/platform/actions/common/actions'), - r('vs/platform/keybinding/common/keybinding'), - r('vs/platform/contextkey/common/contextkey'), - ]; - - const { serviceCollection } = await new Promise(resolve => { - monaco.editor.create( - container, - { - codesandboxService: i => - new SyncDescriptor(CodeSandboxService, [this.controller, this]), - codesandboxConfigurationUIService: i => - new SyncDescriptor(CodeSandboxConfigurationUIService, [ - this.customEditorApi, - ]), - }, - resolve - ); - }); - - return new Promise(resolve => { - // It has to run the accessor within the callback - serviceCollection.get(IInstantiationService).invokeFunction(accessor => { - // Initialize these services - accessor.get(CodeSandboxConfigurationUIService); - accessor.get(ICodeSandboxEditorConnectorService); - - const statusbarPart = accessor.get(IStatusbarService); - const commandService = accessor.get(ICommandService); - const extensionService = accessor.get(IExtensionService); - const extensionEnablementService = accessor.get( - IExtensionEnablementService - ); - const keybindingService = accessor.get(IKeybindingService); - const contextKeyService = accessor.get(IContextKeyService); - - this.lookupKeybinding = id => keybindingService.lookupKeybinding(id); - this.contextMatchesRules = rules => - contextKeyService.contextMatchesRules(rules); - this.commandService.resolve(commandService); - this.extensionService.resolve(extensionService); - this.extensionEnablementService.resolve(extensionEnablementService); - - const editorPart = accessor.get(IEditorGroupsService); - const codeEditorService = accessor.get(ICodeEditorService); - const textFileService = accessor.get(ITextFileService); - const editorService = accessor.get(IEditorService); - const contextViewService = accessor.get(IContextViewService); - - contextViewService.setContainer(container); - - this.editorApi = { - openFile(path) { - return codeEditorService.openCodeEditor({ - resource: monaco.Uri.file('/sandbox' + path), - }); - }, - getActiveCodeEditor() { - return codeEditorService.getActiveCodeEditor(); - }, - textFileService, - editorPart, - editorService, - codeEditorService, - extensionService, - }; - - window.CSEditor = { - editor: this.editorApi, - monaco, - }; - - statusbarPart.create(this.elements.statusbar); - editorPart.create(this.elements.editorPart); - editorPart.layout(container.offsetWidth, container.offsetHeight); - - this.menuAppItems = composeMenuAppTree(id => - MenuRegistry.getMenuItems(id) - ); - - editorPart.parent = container; - - container.appendChild(this.elements.editorPart); - - this.initializeReactions(); - - this.configureMonacoLanguages(monaco); - - editorService.onDidActiveEditorChange(this.onActiveEditorChange); - this.initializeCodeSandboxAPIListener(); - - if (!this.linter && this.settings.lintEnabled) { - this.createLinter(); - } - - const lifecycleService = accessor.get(ILifecycleService); - - // Trigger all VSCode lifecycle listeners - lifecycleService.phase = 2; // Restoring - requestAnimationFrame(() => { - lifecycleService.phase = 3; // Running - }); - - resolve(); - }); - }); - } - - private _cachedDependencies = {}; - private _cachedDependenciesCode: string | undefined = undefined; - private getDependencies(sandbox: Sandbox): { [depName: string]: string } { - try { - const module = resolveModule( - '/package.json', - sandbox.modules, - sandbox.directories - ); - if (this._cachedDependenciesCode !== module.code) { - this._cachedDependenciesCode = module.code; - const parsedPkg = JSON.parse(module.code); - this._cachedDependencies = { - ...(parsedPkg.dependencies || {}), - ...(parsedPkg.devDependencies || {}), - }; - } - } catch (e) { - /* ignore */ - } - - return this._cachedDependencies; - } - - private prepareElements() { - this.elements.editor.className = 'monaco-workbench'; - this.elements.editor.style.width = '100%'; - this.elements.editor.style.height = '100%'; - - this.elements.statusbar.className = 'part statusbar'; - this.elements.statusbar.id = 'workbench.parts.statusbar'; - - this.elements.editorPart.id = 'vscode-editor'; - this.elements.editorPart.className = 'part editor has-watermark'; - this.elements.editorPart.style.width = '100%'; - this.elements.editorPart.style.height = '100%'; - } - - private configureMonacoLanguages(monaco) { - [ - 'typescript', - 'typescriptreact', - 'javascript', - 'javascriptreact', - 'css', - 'less', - 'sass', - 'graphql', - 'html', - 'markdown', - 'json', - ].forEach(language => { - monaco.languages.registerDocumentFormattingEditProvider(language, { - provideDocumentFormattingEdits: this.provideDocumentFormattingEdits, - }); - }); - } - - private provideDocumentFormattingEdits = (model, _, token) => - prettify( - model.uri.fsPath, - () => model.getValue(), - this.getPrettierConfig(), - () => false, - token - ).then(newCode => [ - { - range: model.getFullModelRange(), - text: newCode, - }, - ]); - - private changeSettings = (settings: Settings) => { - this.settings = settings; - - if (!this.linter && this.settings.lintEnabled) { - this.createLinter(); - } else if (this.linter && !this.settings.lintEnabled) { - this.linter = this.linter.dispose(); - } - }; - - private createLinter() { - this.linter = new Linter(this.editorApi, this.monaco); - } - - private getPrettierConfig = () => { - try { - const sandbox = this.options.getCurrentSandbox(); - if (!sandbox) { - return null; - } - const module = resolveModule( - '/.prettierrc', - sandbox.modules, - sandbox.directories - ); - - return JSON.parse(module.code || ''); - } catch (e) { - return this.settings.prettierConfig || DEFAULT_PRETTIER_CONFIG; - } - }; - - private onOperationApplied = (data: OnOperationAppliedData) => { - const currentModule = this.options.getCurrentModule(); - if (currentModule && data.moduleShortid === currentModule.shortid) { - this.lint(data.title, data.model); - } - - this.options.onOperationApplied(data); - }; - - private onFileChange = (data: OnFileChangeData) => { - this.lint(data.title, data.model); - this.options.onCodeChange(data); - }; - - private onActiveEditorChange = () => { - if (this.modelSelectionListener) { - this.modelSelectionListener.dispose(); - } - - if (this.modelCursorPositionListener) { - this.modelCursorPositionListener.dispose(); - } - - if (this.modelViewRangeListener) { - this.modelViewRangeListener.dispose(); - } - - const activeEditor = this.editorApi.getActiveCodeEditor(); - - if (activeEditor && activeEditor.getModel()) { - const modulePath = activeEditor.getModel().uri.path; - const currentModule = this.options.getCurrentModule(); - - activeEditor.updateOptions({ - readOnly: this.readOnly || currentModule?.isBinary, - }); - - if (!modulePath.startsWith('/sandbox')) { - return; - } - - const sandbox = this.options.getCurrentSandbox(); - if (this.linter && sandbox) { - this.linter.lint( - activeEditor.getModel().getValue(), - modulePath, - activeEditor.getModel().getVersionId(), - sandbox.template, - this.getDependencies(sandbox) - ); - } - - if ( - currentModule && - modulePath === `/sandbox${currentModule.path}` && - currentModule.code !== undefined && - activeEditor.getValue() !== currentModule.code && - !currentModule.isBinary - ) { - // This means that the file in Cerebral is dirty and has changed, - // VSCode only gets saved contents. In this case we manually set the value correctly. - - this.modelsHandler.isApplyingOperation = true; - const model = activeEditor.getModel(); - model.applyEdits([ - { - text: currentModule.code, - range: model.getFullModelRange(), - }, - ]); - this.modelsHandler.isApplyingOperation = false; - } - - let lastViewRange = null; - const isDifferentViewRange = (r1: UserViewRange, r2: UserViewRange) => - r1.startLineNumber !== r2.startLineNumber || - r1.startColumn !== r2.startColumn || - r1.endLineNumber !== r2.endLineNumber || - r1.endColumn !== r2.endColumn; - - this.modelViewRangeListener = activeEditor.onDidScrollChange(e => { - const [range] = activeEditor.getVisibleRanges(); - - if ( - lastViewRange == null || - (range && isDifferentViewRange(lastViewRange!, range)) - ) { - lastViewRange = range; - this.options.onViewRangeChanged(range); - } - }); - - this.modelCursorPositionListener = activeEditor.onDidChangeCursorPosition( - cursor => { - if ( - sandbox && - sandbox.featureFlags.comments && - hasPermission(sandbox.authorization, 'comment') - ) { - const model = activeEditor.getModel(); - - this.modelsHandler.updateLineCommentIndication( - model, - cursor.position.lineNumber - ); - } - } - ); - - this.modelSelectionListener = activeEditor.onDidChangeCursorSelection( - selectionChange => { - const model = activeEditor.getModel(); - const lines = model.getLinesContent() || []; - const data: onSelectionChangeData = { - primary: getSelection(lines, selectionChange.selection), - secondary: selectionChange.secondarySelections.map(s => - getSelection(lines, s) - ), - source: selectionChange.source, - }; - - if ( - selectionChange.reason === 3 || - /* alt + shift + arrow keys */ selectionChange.source === - 'moveWordCommand' || - /* click inside a selection */ selectionChange.source === 'api' - ) { - this.onSelectionChangeDebounced.cancel(); - this.options.onSelectionChanged(data); - } else { - // This is just on typing, we send a debounced selection update as a - // safeguard to make sure we are in sync - this.onSelectionChangeDebounced(data); - } - } - ); - } - }; - - private initializeCodeSandboxAPIListener() { - return listen(({ action, type, code, path, lineNumber, column }: any) => { - if (type === 'add-extra-lib') { - // TODO: bring this func back - // const dtsPath = `${path}.d.ts`; - // this.monaco.languages.typescript.typescriptDefaults._extraLibs[ - // `file:///${dtsPath}` - // ] = code; - // this.commitLibChanges(); - } else if (action === 'editor.open-module') { - const options: { - selection?: { startLineNumber: number; startColumn: number }; - } = {}; - - if (lineNumber || column) { - options.selection = { - startLineNumber: +lineNumber, - startColumn: +(column || 0), - }; - } - - this.editorApi.codeEditorService.openCodeEditor({ - resource: this.monaco.Uri.file('/sandbox' + path), - options, - }); - } - }); - } - - private lint(title: string, model: any) { - const sandbox = this.options.getCurrentSandbox(); - if (!sandbox || !this.linter) { - return; - } - - this.linter.lint( - model.getValue(), - title, - model.getVersionId(), - sandbox.template, - this.getDependencies(sandbox) - ); - } - - private getCurrentModelPath = () => { - const activeEditor = this.editorApi.getActiveCodeEditor(); - - if (!activeEditor) { - return undefined; - } - const model = activeEditor.getModel(); - if (!model) { - return undefined; - } - - return model.uri.path.replace(/^\/sandbox/, ''); - }; - - // This is used by the CodesandboxService internally - private addNotification( - message: string, - type: 'error' | 'info' | 'warning' | 'success', - options: { actions: NotificationMessage['actions']; sticky?: boolean } - ) { - const getStatus = () => { - switch (type) { - case 'error': - return NotificationStatus.ERROR; - case 'warning': - return NotificationStatus.WARNING; - case 'success': - return NotificationStatus.SUCCESS; - default: - return NotificationStatus.NOTICE; - } - }; - - notificationState.addNotification({ - message, - status: getStatus(), - sticky: options.sticky, - actions: options.actions, - }); - } - - private listenToCommentClick() { - window.addEventListener('click', event => { - const target = event.target as HTMLElement; - if (target.classList.contains('editor-comments-glyph')) { - /* - We grab the id of the commenthread by getting the last classname. - The last part of the classname is the id. - */ - const lastClass = Array.from(target.classList).pop(); - - if (lastClass) { - const commentIds = lastClass.startsWith('editor-comments-ids-') - ? (lastClass.split('editor-comments-ids-').pop() || '').split('_') - : []; - const boundingRect = target.getBoundingClientRect(); - this.options.onCommentClick({ - commentIds, - bounds: { - left: boundingRect.left, - top: boundingRect.top, - right: boundingRect.right, - bottom: boundingRect.bottom, - }, - }); - } - } - }); - } -} - -export default new VSCodeEffect(); diff --git a/packages/app/src/app/overmind/effects/vscode/initializers.ts b/packages/app/src/app/overmind/effects/vscode/initializers.ts deleted file mode 100644 index 0196d41ca5a..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/initializers.ts +++ /dev/null @@ -1,229 +0,0 @@ -import JSON5 from 'json5'; -import codeSandboxTheme from '@codesandbox/common/lib/themes/codesandbox.json'; -import codeSandboxBlackTheme from '@codesandbox/common/lib/themes/codesandbox-black'; -import { notificationState } from '@codesandbox/common/lib/utils/notifications'; -import { NotificationStatus } from '@codesandbox/notifications'; - -export function initializeThemeCache() { - try { - if (!localStorage.getItem('vs-global://colorThemeData')) { - import('./theme-cache').then(rawTheme => { - localStorage.setItem('vs-global://colorThemeData', rawTheme.default); - }); - } - } catch (e) { - console.error(e); - } -} - -export function initializeExtensionsFolder() { - // @ts-ignore - const fs = window.BrowserFS.BFSRequire('fs'); - - if (!fs.existsSync('/vscode/extensions')) { - fs.mkdirSync('/vscode/extensions'); - } -} - -export function initializeSettings() { - // @ts-ignore - const fs = window.BrowserFS.BFSRequire('fs'); - if (!fs.existsSync('/vscode/settings.json')) { - fs.writeFileSync( - '/vscode/settings.json', - JSON.stringify( - { - 'editor.formatOnSave': true, - 'editor.fontSize': 15, - 'editor.fontFamily': - "MonoLisa, Menlo, Monaco, 'Courier New', monospace", - 'editor.tabSize': 2, - 'editor.minimap.enabled': false, - 'workbench.editor.openSideBySideDirection': 'down', - 'svelte.plugin.typescript.diagnostics.enable': false, - 'typescript.locale': 'en', - 'relativeLineHeight.value': 1.6, - }, - null, - 2 - ) - ); - } - - try { - // We need to do this to prevent duplicate closing tags in live sessions. - // https://github.com/codesandbox/codesandbox-client/issues/3398 - // I haven't found another way to fix this, as the TS extension literally listens - // for edits and checks whether an edit ends with '>'. Then it asks the LSP for the changes - // and applies them 100ms later. There is no check for cursor or anything else. - // This doesn't happen in VSCode Live Share itself, because there they share the LSP between - // multiple users. This way the request is not duplicated among multiple users. - const settings = JSON5.parse( - fs.readFileSync('/vscode/settings.json').toString() - ); - - let settingsChanged = false; - const changeIfNeeded = (field: string, value: unknown) => { - if (settings[field] !== value) { - settings[field] = value; - return true; - } - return settingsChanged || false; - }; - - if ( - settings['editor.fontFamily'].startsWith('dm') || - settings['editor.fontFamily'].startsWith("'dm'") - ) { - settingsChanged = changeIfNeeded( - 'editor.fontFamily', - "MonoLisa, Menlo, Monaco, 'Courier New', monospace" - ); - } - - settingsChanged = changeIfNeeded('files.autoSave', 'off'); - settingsChanged = changeIfNeeded('javascript.autoClosingTags', false); - settingsChanged = changeIfNeeded('typescript.autoClosingTags', false); - settingsChanged = changeIfNeeded('html.autoClosingTags', false); - settingsChanged = changeIfNeeded('typescript.locale', 'en'); - settingsChanged = changeIfNeeded( - 'typescript.tsserver.useSeparateSyntaxServer', - false - ); - - if (!settings['workbench.colorTheme']) { - // if you have not changed the theme ever, - // we set codesandbox black as the theme for you - - settingsChanged = changeIfNeeded( - 'workbench.colorTheme', - 'CodeSandbox Black 2021' - ); - } - - if (settingsChanged) { - fs.writeFileSync( - '/vscode/settings.json', - JSON5.stringify(settings, { quote: '"', space: 2, replacer: null }) - ); - } - } catch (e) { - console.warn(e); - } -} - -export function initializeCodeSandboxTheme() { - // @ts-ignore - const fs = window.BrowserFS.BFSRequire('fs'); - - fs.writeFileSync( - '/extensions/ngryman.codesandbox-theme-0.0.1/themes/CodeSandbox-color-theme.json', - JSON.stringify(codeSandboxTheme) - ); - - fs.writeFileSync( - '/extensions/codesandbox-black-0.0.1/themes/codesandbox-black.json', - JSON.stringify(codeSandboxBlackTheme) - ); -} - -export function installCustomTheme(id: string, name: string, theme: string) { - let uiTheme: string; - try { - uiTheme = JSON5.parse(theme).type; - } catch { - uiTheme = 'dark'; - } - - const packageJSON = { - name: id, - displayName: name, - description: 'The Custom VSCode Theme', - version: '0.4.1', - publisher: 'CodeSandbox', - license: 'SEE LICENSE IN LICENSE.md', - repository: { - type: 'git', - url: 'https://github.com/codesandbox/codesandbox-client', - }, - keywords: [], - scripts: { - publish: 'vsce publish', - }, - galleryBanner: { - color: '#061526', - theme: uiTheme, - }, - engines: { - vscode: '^1.17.0', - }, - categories: ['Themes'], - contributes: { - themes: [ - { - label: name, - uiTheme: uiTheme === 'dark' ? 'vs-dark' : 'vs', - path: './themes/custom-color-theme.json', - }, - ], - }, - }; - - // @ts-ignore - const fs = window.BrowserFS.BFSRequire('fs'); - const extName = `${id}-theme`; - - const folder = `/extensions/${extName}`; - const folderExists = fs.existsSync(folder); - if (!folderExists) { - fs.mkdirSync(folder); - } - fs.writeFileSync( - `/extensions/${extName}/package.json`, - JSON.stringify(packageJSON) - ); - - fs.mkdirSync(`/extensions/${extName}/themes`); - fs.writeFileSync( - `/extensions/${extName}/themes/custom-color-theme.json`, - theme - ); -} - -/** - * This auto installs an extension with a custom theme specified in preferences. People can select - * it as "Custom Theme". - */ -export function initializeCustomTheme() { - const customTheme = localStorage.getItem('settings.manualCustomVSCodeTheme'); - - if (customTheme) { - try { - installCustomTheme('custom', 'Custom Theme', JSON.parse(customTheme)); - } catch (e) { - notificationState.addNotification({ - title: 'Something went wrong while installing the custom extension', - message: e.message, - status: NotificationStatus.ERROR, - actions: { - primary: { - label: 'Clear Custom Theme', - run: () => { - localStorage.removeItem('settings.manualCustomVSCodeTheme'); - }, - }, - }, - }); - } - } -} - -export function initializeSnippetDirectory() { - const fs = window.BrowserFS.BFSRequire('fs'); - - const folder = `/vscode/snippets`; - const folderExists = fs.existsSync(folder); - if (!folderExists) { - fs.mkdirSync(folder); - } -} diff --git a/packages/app/src/app/overmind/effects/vscode/keyCodes.ts b/packages/app/src/app/overmind/effects/vscode/keyCodes.ts deleted file mode 100644 index 8ee9a700c3e..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/keyCodes.ts +++ /dev/null @@ -1,201 +0,0 @@ -// Copied from keyCodes.ts from vscode - -/** - * Virtual Key Codes, the value does not hold any inherent meaning. - * Inspired somewhat from https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - * But these are "more general", as they should work across browsers & OS`s. - */ -export enum KeyCode { - /** - * Placed first to cover the 0 value of the enum. - */ - Unknown = 0, - - Backspace = 1, - Tab = 2, - Enter = 3, - Shift = 4, - Ctrl = 5, - Alt = 6, - PauseBreak = 7, - CapsLock = 8, - Escape = 9, - Space = 10, - PageUp = 11, - PageDown = 12, - End = 13, - Home = 14, - LeftArrow = 15, - UpArrow = 16, - RightArrow = 17, - DownArrow = 18, - Insert = 19, - Delete = 20, - - KEY_0 = 21, - KEY_1 = 22, - KEY_2 = 23, - KEY_3 = 24, - KEY_4 = 25, - KEY_5 = 26, - KEY_6 = 27, - KEY_7 = 28, - KEY_8 = 29, - KEY_9 = 30, - - KEY_A = 31, - KEY_B = 32, - KEY_C = 33, - KEY_D = 34, - KEY_E = 35, - KEY_F = 36, - KEY_G = 37, - KEY_H = 38, - KEY_I = 39, - KEY_J = 40, - KEY_K = 41, - KEY_L = 42, - KEY_M = 43, - KEY_N = 44, - KEY_O = 45, - KEY_P = 46, - KEY_Q = 47, - KEY_R = 48, - KEY_S = 49, - KEY_T = 50, - KEY_U = 51, - KEY_V = 52, - KEY_W = 53, - KEY_X = 54, - KEY_Y = 55, - KEY_Z = 56, - - Meta = 57, - ContextMenu = 58, - - F1 = 59, - F2 = 60, - F3 = 61, - F4 = 62, - F5 = 63, - F6 = 64, - F7 = 65, - F8 = 66, - F9 = 67, - F10 = 68, - F11 = 69, - F12 = 70, - F13 = 71, - F14 = 72, - F15 = 73, - F16 = 74, - F17 = 75, - F18 = 76, - F19 = 77, - - NumLock = 78, - ScrollLock = 79, - - /** - * Used for miscellaneous characters; it can vary by keyboard. - * For the US standard keyboard, the ';:' key - */ - US_SEMICOLON = 80, - /** - * For any country/region, the '+' key - * For the US standard keyboard, the '=+' key - */ - US_EQUAL = 81, - /** - * For any country/region, the ',' key - * For the US standard keyboard, the ',<' key - */ - US_COMMA = 82, - /** - * For any country/region, the '-' key - * For the US standard keyboard, the '-_' key - */ - US_MINUS = 83, - /** - * For any country/region, the '.' key - * For the US standard keyboard, the '.>' key - */ - US_DOT = 84, - /** - * Used for miscellaneous characters; it can vary by keyboard. - * For the US standard keyboard, the '/?' key - */ - US_SLASH = 85, - /** - * Used for miscellaneous characters; it can vary by keyboard. - * For the US standard keyboard, the '`~' key - */ - US_BACKTICK = 86, - /** - * Used for miscellaneous characters; it can vary by keyboard. - * For the US standard keyboard, the '[{' key - */ - US_OPEN_SQUARE_BRACKET = 87, - /** - * Used for miscellaneous characters; it can vary by keyboard. - * For the US standard keyboard, the '\|' key - */ - US_BACKSLASH = 88, - /** - * Used for miscellaneous characters; it can vary by keyboard. - * For the US standard keyboard, the ']}' key - */ - US_CLOSE_SQUARE_BRACKET = 89, - /** - * Used for miscellaneous characters; it can vary by keyboard. - * For the US standard keyboard, the ''"' key - */ - US_QUOTE = 90, - /** - * Used for miscellaneous characters; it can vary by keyboard. - */ - OEM_8 = 91, - /** - * Either the angle bracket key or the backslash key on the RT 102-key keyboard. - */ - OEM_102 = 92, - - NUMPAD_0 = 93, // VK_NUMPAD0, 0x60, Numeric keypad 0 key - NUMPAD_1 = 94, // VK_NUMPAD1, 0x61, Numeric keypad 1 key - NUMPAD_2 = 95, // VK_NUMPAD2, 0x62, Numeric keypad 2 key - NUMPAD_3 = 96, // VK_NUMPAD3, 0x63, Numeric keypad 3 key - NUMPAD_4 = 97, // VK_NUMPAD4, 0x64, Numeric keypad 4 key - NUMPAD_5 = 98, // VK_NUMPAD5, 0x65, Numeric keypad 5 key - NUMPAD_6 = 99, // VK_NUMPAD6, 0x66, Numeric keypad 6 key - NUMPAD_7 = 100, // VK_NUMPAD7, 0x67, Numeric keypad 7 key - NUMPAD_8 = 101, // VK_NUMPAD8, 0x68, Numeric keypad 8 key - NUMPAD_9 = 102, // VK_NUMPAD9, 0x69, Numeric keypad 9 key - - NUMPAD_MULTIPLY = 103, // VK_MULTIPLY, 0x6A, Multiply key - NUMPAD_ADD = 104, // VK_ADD, 0x6B, Add key - NUMPAD_SEPARATOR = 105, // VK_SEPARATOR, 0x6C, Separator key - NUMPAD_SUBTRACT = 106, // VK_SUBTRACT, 0x6D, Subtract key - NUMPAD_DECIMAL = 107, // VK_DECIMAL, 0x6E, Decimal key - NUMPAD_DIVIDE = 108, // VK_DIVIDE, 0x6F, - - /** - * Cover all key codes when IME is processing input. - */ - KEY_IN_COMPOSITION = 109, - - ABNT_C1 = 110, // Brazilian (ABNT) Keyboard - ABNT_C2 = 111, // Brazilian (ABNT) Keyboard - - /** - * Placed last to cover the length of the enum. - * Please do not depend on this value! - */ - MAX_VALUE, -} - -export enum KeyMod { - CtrlCmd = (1 << 11) >>> 0, - Shift = (1 << 10) >>> 0, - Alt = (1 << 9) >>> 0, - WinCtrl = (1 << 8) >>> 0, -} diff --git a/packages/app/src/app/overmind/effects/vscode/theme-cache.ts b/packages/app/src/app/overmind/effects/vscode/theme-cache.ts deleted file mode 100644 index 36c987c349a..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/theme-cache.ts +++ /dev/null @@ -1 +0,0 @@ -export default `{"id":"vs-dark codesandbox-codesandbox-black-themes-codesandbox-black-json","label":"CodeSandbox Black","settingsId":"CodeSandbox Black","selector":"vs-dark.codesandbox-codesandbox-black-themes-codesandbox-black-json","themeTokenColors":[{"settings":{"foreground":"#ffffffff","background":"#151515ff"}},{"name":"Comment","scope":["comment"],"settings":{"foreground":"#5C6370","fontStyle":"italic"}},{"name":"Comment Markup Link","scope":["comment markup.link"],"settings":{"foreground":"#5C6370"}},{"name":"Entity Name Type","scope":["entity.name.type"],"settings":{"foreground":"#E5C07B"}},{"name":"Entity Other Inherited Class","scope":["entity.other.inherited-class"],"settings":{"foreground":"#98C379"}},{"name":"Keyword","scope":["keyword"],"settings":{"foreground":"#C678DD"}},{"name":"Keyword Control","scope":["keyword.control"],"settings":{"foreground":"#C678DD"}},{"name":"Keyword Operator","scope":["keyword.operator"],"settings":{"foreground":"#ABB2BF"}},{"name":"Keyword Other Special Method","scope":["keyword.other.special-method"],"settings":{"foreground":"#61AFEF"}},{"name":"Keyword Other Unit","scope":["keyword.other.unit"],"settings":{"foreground":"#D19A66"}},{"name":"Storage","scope":["storage"],"settings":{"foreground":"#C678DD"}},{"name":"Storage Type Annotation,storage Type Primitive","scope":["storage.type.annotation","storage.type.primitive"],"settings":{"foreground":"#C678DD"}},{"name":"Storage Modifier Package,storage Modifier Import","scope":["storage.modifier.package","storage.modifier.import"],"settings":{"foreground":"#ABB2BF"}},{"name":"Constant","scope":["constant"],"settings":{"foreground":"#D19A66"}},{"name":"Constant Variable","scope":["constant.variable"],"settings":{"foreground":"#D19A66"}},{"name":"Constant Character Escape","scope":["constant.character.escape"],"settings":{"foreground":"#56B6C2"}},{"name":"Constant Numeric","scope":["constant.numeric"],"settings":{"foreground":"#D19A66"}},{"name":"Constant Other Color","scope":["constant.other.color"],"settings":{"foreground":"#56B6C2"}},{"name":"Constant Other Symbol","scope":["constant.other.symbol"],"settings":{"foreground":"#56B6C2"}},{"name":"Variable","scope":["variable"],"settings":{"foreground":"#E06C75"}},{"name":"Variable Interpolation","scope":["variable.interpolation"],"settings":{"foreground":"#BE5046"}},{"name":"Variable Parameter","scope":["variable.parameter"],"settings":{"foreground":"#ABB2BF"}},{"name":"String","scope":["string"],"settings":{"foreground":"#98C379"}},{"name":"String Regexp","scope":["string.regexp"],"settings":{"foreground":"#56B6C2"}},{"name":"String Regexp Source Ruby Embedded","scope":["string.regexp source.ruby.embedded"],"settings":{"foreground":"#E5C07B"}},{"name":"String Other Link","scope":["string.other.link"],"settings":{"foreground":"#E06C75"}},{"name":"Punctuation Definition Comment","scope":["punctuation.definition.comment"],"settings":{"foreground":"#5C6370"}},{"name":"Punctuation Definition Method Parameters,punctuation Definition Function Parameters,punctuation Definition Parameters,punctuation Definition Separator,punctuation Definition Seperator,punctuation Definition Array","scope":["punctuation.definition.method-parameters","punctuation.definition.function-parameters","punctuation.definition.parameters","punctuation.definition.separator","punctuation.definition.seperator","punctuation.definition.array"],"settings":{"foreground":"#ABB2BF"}},{"name":"Punctuation Definition Heading,punctuation Definition Identity","scope":["punctuation.definition.heading","punctuation.definition.identity"],"settings":{"foreground":"#61AFEF"}},{"name":"Punctuation Definition Bold","scope":["punctuation.definition.bold"],"settings":{"foreground":"#E5C07B","fontStyle":"bold"}},{"name":"Punctuation Definition Italic","scope":["punctuation.definition.italic"],"settings":{"foreground":"#C678DD","fontStyle":"italic"}},{"name":"Punctuation Section Embedded","scope":["punctuation.section.embedded"],"settings":{"foreground":"#BE5046"}},{"name":"Punctuation Section Method,punctuation Section Class,punctuation Section Inner Class","scope":["punctuation.section.method","punctuation.section.class","punctuation.section.inner-class"],"settings":{"foreground":"#ABB2BF"}},{"name":"Support Class","scope":["support.class"],"settings":{"foreground":"#E5C07B"}},{"name":"Support Type","scope":["support.type"],"settings":{"foreground":"#56B6C2"}},{"name":"Support Function","scope":["support.function"],"settings":{"foreground":"#56B6C2"}},{"name":"Support Function Any Method","scope":["support.function.any-method"],"settings":{"foreground":"#61AFEF"}},{"name":"Entity Name Function","scope":["entity.name.function"],"settings":{"foreground":"#61AFEF"}},{"name":"Entity Name Class,entity Name Type Class","scope":["entity.name.class","entity.name.type.class"],"settings":{"foreground":"#E5C07B"}},{"name":"Entity Name Section","scope":["entity.name.section"],"settings":{"foreground":"#61AFEF"}},{"name":"Entity Name Tag","scope":["entity.name.tag"],"settings":{"foreground":"#E06C75"}},{"name":"Entity Other Attribute Name","scope":["entity.other.attribute-name"],"settings":{"foreground":"#D19A66"}},{"name":"Entity Other Attribute Name Id","scope":["entity.other.attribute-name.id"],"settings":{"foreground":"#61AFEF"}},{"name":"Meta Class","scope":["meta.class"],"settings":{"foreground":"#E5C07B"}},{"name":"Meta Class Body","scope":["meta.class.body"],"settings":{"foreground":"#ABB2BF"}},{"name":"Meta Method Call,meta Method","scope":["meta.method-call","meta.method"],"settings":{"foreground":"#ABB2BF"}},{"name":"Meta Definition Variable","scope":["meta.definition.variable"],"settings":{"foreground":"#E06C75"}},{"name":"Meta Link","scope":["meta.link"],"settings":{"foreground":"#D19A66"}},{"name":"Meta Require","scope":["meta.require"],"settings":{"foreground":"#61AFEF"}},{"name":"Meta Selector","scope":["meta.selector"],"settings":{"foreground":"#C678DD"}},{"name":"Meta Separator","scope":["meta.separator"],"settings":{"background":"#373B41","foreground":"#ABB2BF"}},{"name":"Meta Tag","scope":["meta.tag"],"settings":{"foreground":"#ABB2BF"}},{"name":"Underline","scope":["underline"],"settings":{"text-decoration":"underline"}},{"name":"None","scope":["none"],"settings":{"foreground":"#ABB2BF"}},{"name":"Invalid Deprecated","scope":["invalid.deprecated"],"settings":{"foreground":"#523D14","background":"#E0C285"}},{"name":"Invalid Illegal","scope":["invalid.illegal"],"settings":{"foreground":"white","background":"#E05252"}},{"name":"Markup Bold","scope":["markup.bold"],"settings":{"foreground":"#D19A66","fontStyle":"bold"}},{"name":"Markup Changed","scope":["markup.changed"],"settings":{"foreground":"#C678DD"}},{"name":"Markup Deleted","scope":["markup.deleted"],"settings":{"foreground":"#E06C75"}},{"name":"Markup Italic","scope":["markup.italic"],"settings":{"foreground":"#C678DD","fontStyle":"italic"}},{"name":"Markup Heading","scope":["markup.heading"],"settings":{"foreground":"#E06C75"}},{"name":"Markup Heading Punctuation Definition Heading","scope":["markup.heading punctuation.definition.heading"],"settings":{"foreground":"#61AFEF"}},{"name":"Markup Link","scope":["markup.link"],"settings":{"foreground":"#C678DD"}},{"name":"Markup Inserted","scope":["markup.inserted"],"settings":{"foreground":"#98C379"}},{"name":"Markup Quote","scope":["markup.quote"],"settings":{"foreground":"#D19A66"}},{"name":"Markup Raw","scope":["markup.raw"],"settings":{"foreground":"#98C379"}},{"name":"Source C Keyword Operator","scope":["source.c keyword.operator"],"settings":{"foreground":"#C678DD"}},{"name":"Source Cpp Keyword Operator","scope":["source.cpp keyword.operator"],"settings":{"foreground":"#C678DD"}},{"name":"Source Cs Keyword Operator","scope":["source.cs keyword.operator"],"settings":{"foreground":"#C678DD"}},{"name":"Source Css Property Name,source Css Property Value","scope":["source.css property-name","source.css property-value"],"settings":{"foreground":"#828997"}},{"name":"Source Css Property Name Support,source Css Property Value Support","scope":["source.css property-name.support","source.css property-value.support"],"settings":{"foreground":"#ABB2BF"}},{"name":"Source Gfm Markup","scope":["source.gfm markup"],"settings":{"-webkit-font-smoothing":"auto"}},{"name":"Source Gfm Link Entity","scope":["source.gfm link entity"],"settings":{"foreground":"#61AFEF"}},{"name":"Source Go Storage Type String","scope":["source.go storage.type.string"],"settings":{"foreground":"#C678DD"}},{"name":"Source Ini Keyword Other Definition Ini","scope":["source.ini keyword.other.definition.ini"],"settings":{"foreground":"#E06C75"}},{"name":"Source Java Storage Modifier Import","scope":["source.java storage.modifier.import"],"settings":{"foreground":"#E5C07B"}},{"name":"Source Java Storage Type","scope":["source.java storage.type"],"settings":{"foreground":"#E5C07B"}},{"name":"Source Java Keyword Operator Instanceof","scope":["source.java keyword.operator.instanceof"],"settings":{"foreground":"#C678DD"}},{"name":"Source Java Properties Meta Key Pair","scope":["source.java-properties meta.key-pair"],"settings":{"foreground":"#E06C75"}},{"name":"Source Java Properties Meta Key Pair > Punctuation","scope":["source.java-properties meta.key-pair > punctuation"],"settings":{"foreground":"#ABB2BF"}},{"name":"Source Js Keyword Operator","scope":["source.js keyword.operator"],"settings":{"foreground":"#56B6C2"}},{"name":"Source Js Keyword Operator Delete,source Js Keyword Operator In,source Js Keyword Operator Of,source Js Keyword Operator Instanceof,source Js Keyword Operator New,source Js Keyword Operator Typeof,source Js Keyword Operator Void","scope":["source.js keyword.operator.delete","source.js keyword.operator.in","source.js keyword.operator.of","source.js keyword.operator.instanceof","source.js keyword.operator.new","source.js keyword.operator.typeof","source.js keyword.operator.void"],"settings":{"foreground":"#C678DD"}},{"name":"Source Json Meta Structure Dictionary Json > String Quoted Json","scope":["source.json meta.structure.dictionary.json > string.quoted.json"],"settings":{"foreground":"#E06C75"}},{"name":"Source Json Meta Structure Dictionary Json > String Quoted Json > Punctuation String","scope":["source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string"],"settings":{"foreground":"#E06C75"}},{"name":"Source Json Meta Structure Dictionary Json > Value Json > String Quoted Json,source Json Meta Structure Array Json > Value Json > String Quoted Json,source Json Meta Structure Dictionary Json > Value Json > String Quoted Json > Punctuation,source Json Meta Structure Array Json > Value Json > String Quoted Json > Punctuation","scope":["source.json meta.structure.dictionary.json > value.json > string.quoted.json","source.json meta.structure.array.json > value.json > string.quoted.json","source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation","source.json meta.structure.array.json > value.json > string.quoted.json > punctuation"],"settings":{"foreground":"#98C379"}},{"name":"Source Json Meta Structure Dictionary Json > Constant Language Json,source Json Meta Structure Array Json > Constant Language Json","scope":["source.json meta.structure.dictionary.json > constant.language.json","source.json meta.structure.array.json > constant.language.json"],"settings":{"foreground":"#56B6C2"}},{"name":"Source Ruby Constant Other Symbol > Punctuation","scope":["source.ruby constant.other.symbol > punctuation"],"settings":{"foreground":"inherit"}},{"name":"Source Python Keyword Operator Logical Python","scope":["source.python keyword.operator.logical.python"],"settings":{"foreground":"#C678DD"}},{"name":"Source Python Variable Parameter","scope":["source.python variable.parameter"],"settings":{"foreground":"#D19A66"}},{"name":"Meta Attribute Rust","scope":["meta.attribute.rust"],"settings":{"foreground":"#BCC199"}},{"name":"Storage Modifier Lifetime Rust,entity Name Lifetime Rust","scope":["storage.modifier.lifetime.rust","entity.name.lifetime.rust"],"settings":{"foreground":"#33E8EC"}},{"name":"Keyword Unsafe Rust","scope":["keyword.unsafe.rust"],"settings":{"foreground":"#CC6B73"}},{"name":"customrule","scope":"customrule","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Support Type Property Name","scope":"support.type.property-name","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Punctuation for Quoted String","scope":"string.quoted.double punctuation","settings":{"foreground":"#98C379"}},{"name":"[VSCODE-CUSTOM] Support Constant","scope":"support.constant","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] JSON Property Name","scope":"support.type.property-name.json","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JSON Punctuation for Property Name","scope":"support.type.property-name.json punctuation","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Punctuation for key-value","scope":["punctuation.separator.key-value.ts","punctuation.separator.key-value.js","punctuation.separator.key-value.tsx"],"settings":{"foreground":"#56B6C2"}},{"name":"[VSCODE-CUSTOM] JS/TS Embedded Operator","scope":["source.js.embedded.html keyword.operator","source.ts.embedded.html keyword.operator"],"settings":{"foreground":"#56B6C2"}},{"name":"[VSCODE-CUSTOM] JS/TS Variable Other Readwrite","scope":["variable.other.readwrite.js","variable.other.readwrite.ts","variable.other.readwrite.tsx"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Support Variable Dom","scope":["support.variable.dom.js","support.variable.dom.ts"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Support Variable Property Dom","scope":["support.variable.property.dom.js","support.variable.property.dom.ts"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Interpolation String Punctuation","scope":["meta.template.expression.js punctuation.definition","meta.template.expression.ts punctuation.definition"],"settings":{"foreground":"#BE5046"}},{"name":"[VSCODE-CUSTOM] JS/TS Punctuation Type Parameters","scope":["source.ts punctuation.definition.typeparameters","source.js punctuation.definition.typeparameters","source.tsx punctuation.definition.typeparameters"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Definition Block","scope":["source.ts punctuation.definition.block","source.js punctuation.definition.block","source.tsx punctuation.definition.block"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Punctuation Separator Comma","scope":["source.ts punctuation.separator.comma","source.js punctuation.separator.comma","source.tsx punctuation.separator.comma"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Variable Property","scope":["support.variable.property.js","support.variable.property.ts","support.variable.property.tsx"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Default Keyword","scope":["keyword.control.default.js","keyword.control.default.ts","keyword.control.default.tsx"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Instanceof Keyword","scope":["keyword.operator.expression.instanceof.js","keyword.operator.expression.instanceof.ts","keyword.operator.expression.instanceof.tsx"],"settings":{"foreground":"#C678DD"}},{"name":"[VSCODE-CUSTOM] JS/TS Of Keyword","scope":["keyword.operator.expression.of.js","keyword.operator.expression.of.ts","keyword.operator.expression.of.tsx"],"settings":{"foreground":"#C678DD"}},{"name":"[VSCODE-CUSTOM] JS/TS Braces/Brackets","scope":["meta.brace.round.js","meta.array-binding-pattern-variable.js","meta.brace.square.js","meta.brace.round.ts","meta.array-binding-pattern-variable.ts","meta.brace.square.ts","meta.brace.round.tsx","meta.array-binding-pattern-variable.tsx","meta.brace.square.tsx"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Punctuation Accessor","scope":["source.js punctuation.accessor","source.ts punctuation.accessor","source.tsx punctuation.accessor"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Punctuation Terminator Statement","scope":["punctuation.terminator.statement.js","punctuation.terminator.statement.ts","punctuation.terminator.statement.tsx"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Array variables","scope":["meta.array-binding-pattern-variable.js variable.other.readwrite.js","meta.array-binding-pattern-variable.ts variable.other.readwrite.ts","meta.array-binding-pattern-variable.tsx variable.other.readwrite.tsx"],"settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] JS/TS Support Variables","scope":["source.js support.variable","source.ts support.variable","source.tsx support.variable"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Support Variables","scope":["variable.other.constant.property.js","variable.other.constant.property.ts","variable.other.constant.property.tsx"],"settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] JS/TS Keyword New","scope":["keyword.operator.new.ts","keyword.operator.new.j","keyword.operator.new.tsx"],"settings":{"foreground":"#C678DD"}},{"name":"[VSCODE-CUSTOM] TS Keyword Operator","scope":["source.ts keyword.operator","source.tsx keyword.operator"],"settings":{"foreground":"#56B6C2"}},{"name":"[VSCODE-CUSTOM] JS/TS Punctuation Parameter Separator","scope":["punctuation.separator.parameter.js","punctuation.separator.parameter.ts","punctuation.separator.parameter.tsx "],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Import","scope":["constant.language.import-export-all.js","constant.language.import-export-all.ts"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JSX/TSX Import","scope":["constant.language.import-export-all.jsx","constant.language.import-export-all.tsx"],"settings":{"foreground":"#56B6C2"}},{"name":"[VSCODE-CUSTOM] JS/TS Keyword Control As","scope":["keyword.control.as.js","keyword.control.as.ts","keyword.control.as.jsx","keyword.control.as.tsx"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Variable Alias","scope":["variable.other.readwrite.alias.js","variable.other.readwrite.alias.ts","variable.other.readwrite.alias.jsx","variable.other.readwrite.alias.tsx"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Constants","scope":["variable.other.constant.js","variable.other.constant.ts","variable.other.constant.jsx","variable.other.constant.tsx"],"settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] JS/TS Export Variable","scope":["meta.export.default.js variable.other.readwrite.js","meta.export.default.ts variable.other.readwrite.ts"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JS/TS Template Strings Punctuation Accessor","scope":["source.js meta.template.expression.js punctuation.accessor","source.ts meta.template.expression.ts punctuation.accessor","source.tsx meta.template.expression.tsx punctuation.accessor"],"settings":{"foreground":"#98C379"}},{"name":"[VSCODE-CUSTOM] JS/TS Import equals","scope":["source.js meta.import-equals.external.js keyword.operator","source.jsx meta.import-equals.external.jsx keyword.operator","source.ts meta.import-equals.external.ts keyword.operator","source.tsx meta.import-equals.external.tsx keyword.operator"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Type Module","scope":"entity.name.type.module.js,entity.name.type.module.ts,entity.name.type.module.jsx,entity.name.type.module.tsx","settings":{"foreground":"#98C379"}},{"name":"[VSCODE-CUSTOM] JS/TS Meta Class","scope":"meta.class.js,meta.class.ts,meta.class.jsx,meta.class.tsx","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Property Definition Variable","scope":["meta.definition.property.js variable","meta.definition.property.ts variable","meta.definition.property.jsx variable","meta.definition.property.tsx variable"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Meta Type Parameters Type","scope":["meta.type.parameters.js support.type","meta.type.parameters.jsx support.type","meta.type.parameters.ts support.type","meta.type.parameters.tsx support.type"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Meta Tag Keyword Operator","scope":["source.js meta.tag.js keyword.operator","source.jsx meta.tag.jsx keyword.operator","source.ts meta.tag.ts keyword.operator","source.tsx meta.tag.tsx keyword.operator"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Meta Tag Punctuation","scope":["meta.tag.js punctuation.section.embedded","meta.tag.jsx punctuation.section.embedded","meta.tag.ts punctuation.section.embedded","meta.tag.tsx punctuation.section.embedded"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] JS/TS Meta Array Literal Variable","scope":["meta.array.literal.js variable","meta.array.literal.jsx variable","meta.array.literal.ts variable","meta.array.literal.tsx variable"],"settings":{"foreground":"#E5C07B"}},{"name":"[VSCODE-CUSTOM] JS/TS Module Exports","scope":["support.type.object.module.js","support.type.object.module.jsx","support.type.object.module.ts","support.type.object.module.tsx"],"settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] JSON Constants","scope":["constant.language.json"],"settings":{"foreground":"#56B6C2"}},{"name":"[VSCODE-CUSTOM] JS/TS Object Constants","scope":["variable.other.constant.object.js","variable.other.constant.object.jsx","variable.other.constant.object.ts","variable.other.constant.object.tsx"],"settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] JS/TS Properties Keyword","scope":["storage.type.property.js","storage.type.property.jsx","storage.type.property.ts","storage.type.property.tsx"],"settings":{"foreground":"#56B6C2"}},{"name":"[VSCODE-CUSTOM] JS/TS Single Quote Inside Templated String","scope":["meta.template.expression.js string.quoted punctuation.definition","meta.template.expression.jsx string.quoted punctuation.definition","meta.template.expression.ts string.quoted punctuation.definition","meta.template.expression.tsx string.quoted punctuation.definition"],"settings":{"foreground":"#98C379"}},{"name":"[VSCODE-CUSTOM] JS/TS Backtick inside Templated String","scope":["meta.template.expression.js string.template punctuation.definition.string.template","meta.template.expression.jsx string.template punctuation.definition.string.template","meta.template.expression.ts string.template punctuation.definition.string.template","meta.template.expression.tsx string.template punctuation.definition.string.template"],"settings":{"foreground":"#98C379"}},{"name":"[VSCODE-CUSTOM] JS/TS In Keyword for Loops","scope":["keyword.operator.expression.in.js","keyword.operator.expression.in.jsx","keyword.operator.expression.in.ts","keyword.operator.expression.in.tsx"],"settings":{"foreground":"#C678DD"}},{"name":"[VSCODE-CUSTOM] Python Constants Other","scope":"source.python constant.other","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Python Constants","scope":"source.python constant","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] Python Placeholder Character","scope":"constant.character.format.placeholder.other.python storage","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] Python Magic","scope":"support.variable.magic.python","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Python Meta Function Parameters","scope":"meta.function.parameters.python","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] Python Function Separator Annotation","scope":"punctuation.separator.annotation.python","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Python Function Separator Punctuation","scope":"punctuation.separator.parameters.python","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] CSharp Fields","scope":"entity.name.variable.field.cs","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] CSharp Keyword Operators","scope":"source.cs keyword.operator","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] CSharp Variables","scope":"variable.other.readwrite.cs","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] CSharp Variables Other","scope":"variable.other.object.cs","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] CSharp Property Other","scope":"variable.other.object.property.cs","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] CSharp Property","scope":"entity.name.variable.property.cs","settings":{"foreground":"#61AFEF"}},{"name":"[VSCODE-CUSTOM] CSharp Storage Type","scope":"storage.type.cs","settings":{"foreground":"#E5C07B"}},{"name":"[VSCODE-CUSTOM] Rust Unsafe Keyword","scope":"keyword.other.unsafe.rust","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Markdown Raw Block","scope":"markup.raw.block.markdown","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Shell Variables Punctuation Definition","scope":"punctuation.definition.variable.shell","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Css Support Constant Value","scope":"support.constant.property-value.css","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Css Punctuation Definition Constant","scope":"punctuation.definition.constant.css","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] Sass Punctuation for key-value","scope":"punctuation.separator.key-value.scss","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Sass Punctuation for constants","scope":"punctuation.definition.constant.scss","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] Sass Punctuation for key-value","scope":"meta.property-list.scss punctuation.separator.key-value.scss","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Java Storage Type Primitive Array","scope":"storage.type.primitive.array.java","settings":{"foreground":"#E5C07B"}},{"name":"[VSCODE-CUSTOM] Markdown headings","scope":"entity.name.section.markdown","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Markdown heading Punctuation Definition","scope":"punctuation.definition.heading.markdown","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Markdown heading setext","scope":"markup.heading.setext","settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Markdown Punctuation Definition Bold","scope":"punctuation.definition.bold.markdown","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] Markdown Inline Raw","scope":"markup.inline.raw.markdown","settings":{"foreground":"#98C379"}},{"name":"[VSCODE-CUSTOM] Markdown List Punctuation Definition","scope":"beginning.punctuation.definition.list.markdown","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Markdown Quote","scope":"markup.quote.markdown","settings":{"foreground":"#5C6370","fontStyle":"italic"}},{"name":"[VSCODE-CUSTOM] Markdown Punctuation Definition String","scope":["punctuation.definition.string.begin.markdown","punctuation.definition.string.end.markdown","punctuation.definition.metadata.markdown"],"settings":{"foreground":"#ABB2BF"}},{"name":"[VSCODE-CUSTOM] Markdown Punctuation Definition Link","scope":"punctuation.definition.metadata.markdown","settings":{"foreground":"#C678DD"}},{"name":"[VSCODE-CUSTOM] Markdown Underline Link/Image","scope":["markup.underline.link.markdown","markup.underline.link.image.markdown"],"settings":{"foreground":"#C678DD"}},{"name":"[VSCODE-CUSTOM] Markdown Link Title/Description","scope":["string.other.link.title.markdown","string.other.link.description.markdown"],"settings":{"foreground":"#61AFEF"}},{"name":"[VSCODE-CUSTOM] Ruby Punctuation Separator Variable","scope":"punctuation.separator.variable.ruby","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] Ruby Other Constant Variable","scope":"variable.other.constant.ruby","settings":{"foreground":"#D19A66"}},{"name":"[VSCODE-CUSTOM] Ruby Keyword Operator Other","scope":"keyword.operator.other.ruby","settings":{"foreground":"#98C379"}},{"name":"[VSCODE-CUSTOM] PHP Punctuation Variable Definition","scope":"punctuation.definition.variable.php","settings":{"foreground":"#E06C75"}},{"name":"[VSCODE-CUSTOM] PHP Meta Class","scope":"meta.class.php","settings":{"foreground":"#ABB2BF"}},{"scope":"token.info-token","settings":{"foreground":"#6796e6"}},{"scope":"token.warn-token","settings":{"foreground":"#cd9731"}},{"scope":"token.error-token","settings":{"foreground":"#f44747"}},{"scope":"token.debug-token","settings":{"foreground":"#b267e6"}}],"extensionData":{"extensionId":"codesandbox.codesandbox-black","extensionPublisher":"codesandbox","extensionName":"codesandbox-black","extensionIsBuiltin":true},"colorMap":{"activityBar.background":"#151515","activityBar.border":"#242424","button.background":"EAFF96","button.foreground":"#ffffff","button.border":"EAFF96","button.hoverBackground":"EAFF96","dropdown.background":"#151515","dropdown.border":"#242424","dropdown.foreground":"#ffffff","terminal.background":"#040404","terminal.foreground":"#ffffff","terminal.ansiBrightBlack":"#535bcf","terminal.ansiBrightRed":"#e1270e","terminal.ansiBrightGreen":"#5bc266","terminal.ansiBrightYellow":"#fbcc43","terminal.ansiBlack":"#21222c","terminal.ansiRed":"#e1270e","terminal.ansiGreen":"#5bc266","terminal.ansiYellow":"#fbcc43","terminal.ansiBlue":"#535bcf","terminal.ansiMagenta":"#bf5af2","terminal.ansiCyan":"#6cc7f6","terminal.ansiWhite":"#ffffff","contrastBorder":"#242424","selection.background":"#535bcf","errorForeground":"#e27e8d","input.background":"#242424","input.foreground":"#ffffff","input.border":"#040404","input.placeholderForeground":"#999999","inputOption.activeBorder":"#6cc7f6","inputValidation.infoBorder":"#bf5af2","inputValidation.warningBorder":"#fbcc43","inputValidation.errorBorder":"#e1270e","tab.activeBackground":"#151515","tab.activeForeground":"#ffffff","editor.background":"#151515","editor.foreground":"#ffffff","editor.hoverHighlightBackground":"#333333","editor.inactiveSelectionBackground":"#333333","editorLineNumber.foreground":"#242424","editorGroup.background":"#151515","editorGroup.border":"#242424","editorGroup.dropBackground":"#151515","editorGroupHeader.tabsBackground":"#151515","editorGroupHeader.tabsBorder":"#242424","editor.lineHighlightBackground":"#242424","editor.lineHighlightBorder":"#242424","editor.rangeHighlightBackground":"#222222","editor.selectionBackground":"#242424","editor.selectionHighlightBackground":"#242424","editor.wordHighlightStrongBackground":"#242424","editor.wordHighlightBackground":"#242424","editorBracketMatch.background":"#242424","editorBracketMatch.border":"#242424","editorCodeLens.foreground":"#242424","editorCursor.background":"#151515","editorCursor.foreground":"#ffffff","editorError.border":"#242424","editorError.foreground":"#e1270e","editorGutter.background":"#151515","editorGutter.deletedBackground":"#e1270e","editorGutter.modifiedBackground":"#151515","editorHoverWidget.background":"#151515","editorHoverWidget.border":"#242424","editorIndentGuide.background":"#151515","editorLineNumber.activeForeground":"#757575","editorLink.activeForeground":"#999999","editorMarkerNavigation.background":"#151515","editorMarkerNavigationError.background":"#151515","editorMarkerNavigationWarning.background":"#242424","editorOverviewRuler.border":"#242424","editorOverviewRuler.commonContentForeground":"#242424","editorOverviewRuler.currentContentForeground":"#e1270e","editorOverviewRuler.incomingContentForeground":"#5bc266","editorRuler.foreground":"#ffffff","editorSuggestWidget.background":"#151515","editorSuggestWidget.border":"#242424","editorSuggestWidget.foreground":"#999999","editorSuggestWidget.selectedBackground":"#242424","editorWarning.border":"#242424","editorWarning.foreground":"#f35644","editorWhitespace.foreground":"#444444","editorWidget.background":"#111111","editorWidget.border":"#2d2d2d","extensionButton.prominentBackground":"#242424","extensionButton.prominentForeground":"#ffffff","extensionButton.prominentHoverBackground":"#242424","focusBorder":"#242424","foreground":"#999999","peekView.border":"#353535","peekViewEditor.background":"#242424","peekViewEditor.matchHighlightBackground":"#76d0fb","peekViewResult.background":"#242424","peekViewResult.fileForeground":"#ffffff","peekViewResult.lineForeground":"#ffffff","peekViewResult.matchHighlightBackground":"#76d0fb","peekViewResult.selectionBackground":"#242424","peekViewResult.selectionForeground":"#ffffff","peekViewTitle.background":"#242424","peekViewTitleDescription.foreground":"#535bcf","peekViewTitleLabel.foreground":"#ffffff","tab.border":"#242424","tab.activeBorder":"#6cc7f6","tab.inactiveBackground":"#151515","tab.inactiveForeground":"#757575","tab.unfocusedActiveForeground":"#ffffff","tab.unfocusedInactiveForeground":"#757575","activityBarBadge.background":"#e1270e","sideBarTitle.foreground":"#ffffff","scrollbarSlider.activeBackground":"#ffffff","scrollbarSlider.border":"#242424","sideBar.background":"#151515","sideBar.border":"#242424","sideBar.foreground":"#ffffff","sideBarSectionHeader.background":"#151515","sideBarSectionHeader.foreground":"#ffffff","sideBarSectionHeader.border":"#242424","sideBar.hoverBackground":"#00ff00","statusBar.background":"#242424","statusBar.foreground":"#ffffff","statusBar.debuggingBackground":"#e1270e","statusBar.debuggingForeground":"#242424","statusBar.noFolderBackground":"#242424","statusBar.noFolderForeground":"#f8f8f2","statusBarItem.prominentBackground":"#e1270e","statusBarItem.prominentHoverBackground":"#ffb86c","statusBarItem.remoteForeground":"#e6e6e6","statusBarItem.remoteBackground":"#bf5af2","statusBar.border":"#242424","titleBar.background":"#151515","titleBar.activeBackground":"#151515","titleBar.activeForeground":"#ffffff","titleBar.border":"#242424","titleBar.inactiveBackground":"#151515","titleBar.inactiveForeground":"#999999","menu.background":"#151515","menu.selectionBackground":"#242424","list.activeSelectionBackground":"#151515","list.activeSelectionForeground":"#ffffff","list.dropBackground":"#151515","list.focusBackground":"#151515","list.highlightForeground":"#6cc7f6","list.hoverBackground":"#242424","list.warningForeground":"#fbcc43","list.errorForeground":"#e1270e"},"watch":false}`; diff --git a/packages/app/src/app/overmind/effects/vscode/utils.ts b/packages/app/src/app/overmind/effects/vscode/utils.ts deleted file mode 100644 index 41a12fbf12f..00000000000 --- a/packages/app/src/app/overmind/effects/vscode/utils.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { getModulePath } from '@codesandbox/common/lib/sandbox/modules'; -import { Sandbox } from '@codesandbox/common/lib/types'; -import { lineAndColumnToIndex } from 'app/overmind/utils/common'; - -export function getVSCodePath(sandbox: Sandbox, moduleId: string) { - return `/sandbox${getModulePath( - sandbox.modules, - sandbox.directories, - moduleId - )}`; -} - -export function getCurrentModelPath(editor) { - const activeEditor = editor.getActiveCodeEditor(); - - if (!activeEditor) { - return undefined; - } - - const model = activeEditor.getModel(); - - if (!model) { - return undefined; - } - - return model.uri.path.replace(/^\/sandbox/, ''); -} - -export function getCurrentModel(editor) { - const activeEditor = editor.getActiveCodeEditor(); - - return activeEditor && activeEditor.getModel(); -} - -export function getSelection(lines, selection) { - const startSelection = lineAndColumnToIndex( - lines, - selection.startLineNumber, - selection.startColumn - ); - const endSelection = lineAndColumnToIndex( - lines, - selection.endLineNumber, - selection.endColumn - ); - - return { - selection: - startSelection === endSelection ? [] : [startSelection, endSelection], - cursorPosition: lineAndColumnToIndex( - lines, - selection.positionLineNumber, - selection.positionColumn - ), - }; -} diff --git a/packages/app/src/app/overmind/effects/vscode/vscode-script-loader.ts b/packages/app/src/app/overmind/effects/vscode/vscode-script-loader.ts index 98d58f42cc8..84738660fd9 100644 --- a/packages/app/src/app/overmind/effects/vscode/vscode-script-loader.ts +++ b/packages/app/src/app/overmind/effects/vscode/vscode-script-loader.ts @@ -368,7 +368,7 @@ function initializeRequires() { }); } -export default function(isVSCode: boolean, requiredModule?: string[]) { +export default function (isVSCode: boolean, requiredModule?: string[]) { var METADATA = isVSCode ? VSCODE_METADATA : MONACO_METADATA; var IS_FILE_PROTOCOL = global.location.protocol === 'file:'; var DIRNAME: string | null = null; @@ -393,13 +393,13 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { } } - var LOADER_OPTS = (function() { + var LOADER_OPTS = (function () { function parseQueryString() { var str = global.location.search; str = str.replace(/^\?/, ''); var pieces = str.split(/&/); var result = {}; - pieces.forEach(function(piece) { + pieces.forEach(function (piece) { var config = piece.split(/=/); result[config[0]] = config[1]; }); @@ -408,7 +408,7 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { var overwrites = parseQueryString(); var result = {}; result['editor'] = overwrites['editor'] || 'src'; - METADATA.PLUGINS.map(function(plugin) { + METADATA.PLUGINS.map(function (plugin) { result[plugin.name] = overwrites[plugin.name] || (process.env.VSCODE ? 'src' : 'npm/min'); }); @@ -437,10 +437,10 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { this.contrib = contrib; this.selectedPath = LOADER_OPTS[name]; } - Component.prototype.isRelease = function() { + Component.prototype.isRelease = function () { return /release/.test(this.selectedPath); }; - Component.prototype.getResolvedPath = function() { + Component.prototype.getResolvedPath = function () { var resolvedPath = this.paths[this.selectedPath]; if ( this.selectedPath === 'npm/dev' || @@ -461,27 +461,27 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { } return resolvedPath; }; - Component.prototype.generateLoaderConfig = function(dest) { + Component.prototype.generateLoaderConfig = function (dest) { dest[this.modulePrefix] = process.env.CODESANDBOX_HOST + this.getResolvedPath(); }; - Component.prototype.generateUrlForPath = function(pathName) { + Component.prototype.generateUrlForPath = function (pathName) { var NEW_LOADER_OPTS = {}; - Object.keys(LOADER_OPTS).forEach(function(key) { + Object.keys(LOADER_OPTS).forEach(function (key) { NEW_LOADER_OPTS[key] = LOADER_OPTS[key] === 'npm/dev' ? undefined : LOADER_OPTS[key]; }); NEW_LOADER_OPTS[this.name] = pathName === 'npm/dev' ? undefined : pathName; var search = Object.keys(NEW_LOADER_OPTS) - .map(function(key) { + .map(function (key) { var value = NEW_LOADER_OPTS[key]; if (value) { return key + '=' + value; } return ''; }) - .filter(function(assignment) { + .filter(function (assignment) { return Boolean(assignment); }) .join('&'); @@ -490,14 +490,14 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { } return toHREF(search); }; - Component.prototype.renderLoadingOptions = function() { + Component.prototype.renderLoadingOptions = function () { return ( '' + this.name + ':   ' + Object.keys(this.paths) .map( - function(pathName) { + function (pathName) { if (pathName === this.selectedPath) { return '' + pathName + ''; } @@ -516,7 +516,7 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { var RESOLVED_CORE = new Component('editor', 'vs', METADATA.CORE.paths); global.RESOLVED_CORE_PATH = RESOLVED_CORE.getResolvedPath(); - var RESOLVED_PLUGINS = METADATA.PLUGINS.map(function(plugin) { + var RESOLVED_PLUGINS = METADATA.PLUGINS.map(function (plugin) { return new Component( plugin.name, plugin.modulePrefix, @@ -539,7 +539,7 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { } } - (function() { + (function () { if (process.env.DEBUG_VERSION) { var allComponents = [RESOLVED_CORE]; if (!RESOLVED_CORE.isRelease()) { @@ -557,7 +557,7 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { div.innerHTML = '
  • ' + allComponents - .map(function(component) { + .map(function (component) { return component.renderLoadingOptions(); }) .join('
  • ') + @@ -575,7 +575,7 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { } })(); - return function(callback: () => void, PATH_PREFIX?: string) { + return function (callback: () => void, PATH_PREFIX?: string) { PATH_PREFIX = PATH_PREFIX || ''; global.nodeRequire = path => { @@ -604,7 +604,7 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { 'vs/language/vue': '/public/14/vs/language/vue', }; if (!RESOLVED_CORE.isRelease()) { - RESOLVED_PLUGINS.forEach(function(plugin) { + RESOLVED_PLUGINS.forEach(function (plugin) { plugin.generateLoaderConfig(loaderPathsConfig); }); } @@ -635,14 +635,14 @@ export default function(isVSCode: boolean, requiredModule?: string[]) { global.deps = new Set(); if (requiredModule) { - global.require(requiredModule, function(a) { + global.require(requiredModule, function (a) { if (!isVSCode && !RESOLVED_CORE.isRelease()) { // At this point we've loaded the monaco-editor-core global.require( - RESOLVED_PLUGINS.map(function(plugin) { + RESOLVED_PLUGINS.map(function (plugin) { return plugin.contrib; }), - function() { + function () { // At this point we've loaded all the plugins callback(); } diff --git a/packages/app/src/app/overmind/factories.ts b/packages/app/src/app/overmind/factories.ts index 5ae507f9805..0b72d8ad8ef 100755 --- a/packages/app/src/app/overmind/factories.ts +++ b/packages/app/src/app/overmind/factories.ts @@ -1,7 +1,6 @@ -import { Contributor, PermissionType } from '@codesandbox/common/lib/types'; +import { Contributor } from '@codesandbox/common/lib/types'; import { identify } from '@codesandbox/common/lib/utils/analytics'; import { notificationState } from '@codesandbox/common/lib/utils/notifications'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; import { NotificationStatus } from '@codesandbox/notifications'; import { IState, derived, ContextFunction } from 'overmind'; import { Context } from '.'; @@ -29,7 +28,6 @@ export const withLoadApp = ( effects.connection.addListener(actions.connectionChanged); actions.internal.setStoredSettings(); actions.internal.prefetchOfficialTemplates(); - effects.codesandboxApi.listen(actions.server.onCodeSandboxAPIMessage); if (localStorage.jwt) { // We've introduced a new way of signing in to CodeSandbox, and we should let the user know to @@ -106,61 +104,6 @@ export const withLoadApp = ( } }; -export const withOwnedSandbox = ( - continueAction: ContextFunction, - cancelAction: ContextFunction = () => Promise.resolve(), - requiredPermission?: PermissionType -): ContextFunction => async (context: Context, payload: I) => { - const { state, actions } = context; - - const sandbox = state.editor.currentSandbox; - - if (sandbox) { - if ( - typeof requiredPermission === 'undefined' - ? !sandbox.owned - : !hasPermission(sandbox.authorization, requiredPermission) - ) { - if (state.editor.isForkingSandbox) { - return cancelAction(context, payload); - } - - try { - await actions.editor.internal.forkSandbox({ - sandboxId: sandbox.id, - }); - } catch (e) { - // If we know the error is caused by an anonymous user trying to save a - // file and fork a server sandbox, we don't want to show the vscode error - // with the cancelAction and we don't want to continue either. - if (e.message === 'ERR_ANON_SSE_FORK') { - return () => {}; - } - - return cancelAction(context, payload); - } - } else if (sandbox.isFrozen && state.editor.sessionFrozen) { - const modalResponse = await actions.modals.forkFrozenModal.open(); - - if (modalResponse === 'fork') { - try { - await actions.editor.internal.forkSandbox({ - sandboxId: sandbox.id, - }); - } catch (e) { - return cancelAction(context, payload); - } - } else if (modalResponse === 'unfreeze') { - state.editor.sessionFrozen = false; - } else if (modalResponse === 'cancel') { - return cancelAction(context, payload); - } - } - } - - return continueAction(context, payload); -}; - export const createModals = < T extends { [name: string]: { diff --git a/packages/app/src/app/overmind/index.ts b/packages/app/src/app/overmind/index.ts index c979961d99e..54ecc61fa77 100755 --- a/packages/app/src/app/overmind/index.ts +++ b/packages/app/src/app/overmind/index.ts @@ -12,20 +12,11 @@ import * as effects from './effects'; import { createModals } from './factories'; import * as modals from './modals'; import * as checkout from './namespaces/checkout'; -import * as comments from './namespaces/comments'; import * as dashboard from './namespaces/dashboard'; import * as sidebar from './namespaces/sidebar'; -import * as deployment from './namespaces/deployment'; -import * as editor from './namespaces/editor'; -import * as files from './namespaces/files'; -import * as git from './namespaces/git'; -import * as live from './namespaces/live'; import * as preferences from './namespaces/preferences'; import * as profile from './namespaces/profile'; -import * as server from './namespaces/server'; import * as userNotifications from './namespaces/userNotifications'; -import * as workspace from './namespaces/workspace'; -import * as preview from './namespaces/preview'; import { state } from './state'; export const config = merge( @@ -37,18 +28,9 @@ export const config = merge( namespaced({ preferences, userNotifications, - editor, - live, - workspace, dashboard, sidebar, - deployment, - files, - git, profile, - server, - comments, - preview, checkout, modals: createModals(modals), }) diff --git a/packages/app/src/app/overmind/internalActions.ts b/packages/app/src/app/overmind/internalActions.ts index ef196db6a85..0fb9729b67b 100755 --- a/packages/app/src/app/overmind/internalActions.ts +++ b/packages/app/src/app/overmind/internalActions.ts @@ -1,11 +1,4 @@ -import { - ModuleTab, - NotificationButton, - Sandbox, - ServerContainerStatus, - ServerStatus, - TabType, -} from '@codesandbox/common/lib/types'; +import { NotificationButton, Sandbox } from '@codesandbox/common/lib/types'; import history from 'app/utils/history'; import { NotificationMessage } from '@codesandbox/notifications/lib/state'; import { NotificationStatus } from '@codesandbox/notifications'; @@ -19,8 +12,6 @@ import { SandboxToFork, } from 'app/components/Create/utils/types'; import { ApiError } from './effects/api/apiFactory'; -import { defaultOpenedModule, mainModule } from './utils/main-module'; -import { parseConfigurations } from './utils/parse-configurations'; import { Context } from '.'; import { TEAM_ID_LOCAL_STORAGE } from './utils/team'; import { AuthOptions, GH_BASE_SCOPE, MAP_GH_SCOPE_OPTIONS } from './utils/auth'; @@ -47,7 +38,6 @@ export const initializeNewUser = async ({ actions.internal.showUserSurveyIfNeeded(); actions.internal.showUpdatedToSIfNeeded(); - await effects.live.getSocket(); actions.userNotifications.internal.initialize(); actions.internal.setStoredSettings(); @@ -80,7 +70,6 @@ export const signIn = async ( state.user = renameZeitToVercel(currentUser); await actions.internal.initializeNewUser(); - actions.refetchSandboxInfo(); state.hasLogIn = true; state.isAuthenticating = false; actions.getActiveTeamInfo(); @@ -296,8 +285,6 @@ export const authorize = async ({ state, effects }: Context) => { try { state.isLoadingAuthToken = true; state.authToken = await effects.api.getAuthToken(); - } catch (error) { - state.editor.error = error.message; } finally { state.isLoadingAuthToken = false; } @@ -410,134 +397,6 @@ export const switchCurrentWorkspaceBySandbox = ( } }; -export const currentSandboxChanged = ({ state, actions }: Context) => { - const sandbox = state.editor.currentSandbox!; - actions.internal.switchCurrentWorkspaceBySandbox({ - sandbox, - }); -}; - -export const setCurrentSandbox = async ( - { state, effects, actions }: Context, - sandbox: Sandbox -) => { - state.editor.sandboxes[sandbox.id] = sandbox; - state.editor.currentId = sandbox.id; - - let { currentModuleShortid } = state.editor; - const parsedConfigs = parseConfigurations(sandbox); - const main = mainModule(sandbox, parsedConfigs); - - state.editor.mainModuleShortid = main?.shortid; - - // Only change the module shortid if it doesn't exist in the new sandbox - // This can happen when a sandbox is opened that's different from the current - // sandbox, with completely different files - if ( - !sandbox.modules.find(module => module.shortid === currentModuleShortid) - ) { - const defaultModule = defaultOpenedModule(sandbox, parsedConfigs); - - if (defaultModule) { - currentModuleShortid = defaultModule.shortid; - } - } - - const sandboxOptions = effects.router.getSandboxOptions(); - - if (sandboxOptions.currentModule) { - try { - const resolvedModule = effects.utils.resolveModule( - sandboxOptions.currentModule, - sandbox.modules, - sandbox.directories - ); - currentModuleShortid = resolvedModule - ? resolvedModule.shortid - : currentModuleShortid; - } catch (error) { - actions.internal.handleError({ - message: `Could not find module ${sandboxOptions.currentModule}`, - error, - }); - } - } - - state.editor.currentModuleShortid = currentModuleShortid; - state.editor.workspaceConfigCode = ''; - - state.server.status = ServerStatus.INITIALIZING; - state.server.containerStatus = ServerContainerStatus.INITIALIZING; - state.server.error = null; - state.server.hasUnrecoverableError = false; - state.server.ports = []; - - const newTab: ModuleTab = { - type: TabType.MODULE, - moduleShortid: currentModuleShortid, - dirty: true, - }; - - state.editor.tabs = [newTab]; - - state.preferences.showPreview = Boolean( - sandboxOptions.isPreviewScreen || sandboxOptions.isSplitScreen - ); - - state.preferences.showEditor = Boolean( - sandboxOptions.isEditorScreen || sandboxOptions.isSplitScreen - ); - - if (sandboxOptions.initialPath) - state.editor.initialPath = sandboxOptions.initialPath; - if (sandboxOptions.fontSize) - state.preferences.settings.fontSize = sandboxOptions.fontSize; - if (sandboxOptions.highlightedLines) - state.editor.highlightedLines = sandboxOptions.highlightedLines; - if (sandboxOptions.hideNavigation) - state.preferences.hideNavigation = sandboxOptions.hideNavigation; - if (sandboxOptions.isInProjectView) - state.editor.isInProjectView = sandboxOptions.isInProjectView; - if (sandboxOptions.autoResize) - state.preferences.settings.autoResize = sandboxOptions.autoResize; - if (sandboxOptions.enableEslint) - state.preferences.settings.enableEslint = sandboxOptions.enableEslint; - if (sandboxOptions.forceRefresh) - state.preferences.settings.forceRefresh = sandboxOptions.forceRefresh; - if (sandboxOptions.expandDevTools) - state.preferences.showDevtools = sandboxOptions.expandDevTools; - if (sandboxOptions.runOnClick) - state.preferences.runOnClick = sandboxOptions.runOnClick; - - state.workspace.project.title = sandbox.title || ''; - state.workspace.project.description = sandbox.description || ''; - state.workspace.project.alias = sandbox.alias || ''; - - // Do this before startContainer, because startContainer flushes in overmind and causes - // the components to rerender. Because of this sometimes the GitHub component will get a - // sandbox without a git - actions.workspace.openDefaultItem(); - actions.server.startContainer(sandbox); - - actions.internal.currentSandboxChanged(); -}; - -export const closeTabByIndex = ({ state }: Context, tabIndex: number) => { - const { currentModule } = state.editor; - const tabs = state.editor.tabs as ModuleTab[]; - const isActiveTab = currentModule.shortid === tabs[tabIndex].moduleShortid; - - if (isActiveTab) { - const newTab = tabIndex > 0 ? tabs[tabIndex - 1] : tabs[tabIndex + 1]; - - if (newTab) { - state.editor.currentModuleShortid = newTab.moduleShortid; - } - } - - state.editor.tabs.splice(tabIndex, 1); -}; - export const getErrorMessage = ( context: Context, { error }: { error: ApiError | Error } diff --git a/packages/app/src/app/overmind/namespaces/comments/actions.ts b/packages/app/src/app/overmind/namespaces/comments/actions.ts deleted file mode 100644 index 0acde6cf7a5..00000000000 --- a/packages/app/src/app/overmind/namespaces/comments/actions.ts +++ /dev/null @@ -1,1016 +0,0 @@ -import { - CommentsFilterOption, - Module, - UserQuery, -} from '@codesandbox/common/lib/types'; -import { captureException } from '@codesandbox/common/lib/utils/analytics/sentry'; -import { getTextOperation } from '@codesandbox/common/lib/utils/diff'; -import { - DIALOG_TRANSITION_DURATION, - REPLY_TRANSITION_DELAY, -} from 'app/constants'; -import { - CodeReference, - CodeReferenceMetadata, - CodeReferenceMetadataFragment, - CommentAddedSubscription, - CommentChangedSubscription, - CommentFragment, - CommentRemovedSubscription, - ImageReference, - UserReference, - UserReferenceMetadata, - ImageReferenceMetadata, - PreviewReferenceMetadata, -} from 'app/graphql/types'; -import { Context } from 'app/overmind'; -import { - convertImagesToImageReferences, - convertMentionsToMentionLinks, - convertMentionsToUserReferences, -} from 'app/overmind/utils/comments'; -import { - indexToLineAndColumn, - lineAndColumnToIndex, -} from 'app/overmind/utils/common'; -import { utcToZonedTime } from 'date-fns-tz'; -import { Selection, TextOperation } from 'ot'; -import { debounce, filter, pipe } from 'overmind'; -import * as uuid from 'uuid'; - -import { OPTIMISTIC_COMMENT_ID } from './state'; - -const PREVIEW_COMMENT_OFFSET = -500; -const CODE_COMMENT_OFFSET = 500; -const BUBBLE_IMAGE = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAC8SURBVHgBxZO9EYJAEIW/PTG3BEogNIQKoBQ7QDvQTrQCCQwMrwMpwdxR3ANkGAIHjoA3czd7P+/b25lbYaBqG8WsSKnIEMJ229bjzUHutuzfl84YRxte5Bru+K8jawUV9tkBWvNVw4hxsgpJHMTUyybzWDP13caDaM2h1vzAR0Ji1Jzjqw+ZYdrThy9I5wEgNMzUXIBdGKBf2x8gnFxf+AIsAXsXTAdo5l8fuGUwylRR6nzRdGe52aJ/9AWAvjArPZuVDgAAAABJRU5ErkJggg=='; - -const BUBBLE_IMAGE_2X = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAFsSURBVHgBxZfBUcMwEEW/5ISzS1AqQNwYLoQOoAJaCBUAHUAHoQOoIOaQGW64A9QBPsMEs5IdBA6ZKImsfTM7icce79f3yt4VCKQe6xwfOIfAKR1qCkWRt6crCkNRUhQY4kkUZRVyX7HpgvpYK0hM6MrLXwlDmGKBW/FSGuwiwK34E9f0d4L9uBPz8grbCGhXPaOzCnEw5MbZf27IleQnWkdOblHIMHP37vDHgR5W3mXFiR8BbZW/9pjcixjiaLlL/COwBdd/cotqi9vhHHDWZ3hDShYY2UfROJDhBqmRzfYW7X5/R3oqqoWRdK9XHtyrXVIVjMEFfVdsDRyCD20FKPChrIBtvnCxySWY4RZQcQsw3AJKbgEFrwBqXjkFTG1PwCeAOmb7wyNA4H7ZlnEIMBj4/mOAtDRN6dxPTSkdMKhx0Z0NUjkQPphEhwrOteFrZsS+HKjI7gd80Vy4YTiNJcCP5zWecYDH0PH8G30scDsRZ7W6AAAAAElFTkSuQmCC'; - -export const selectCommentsFilter = ( - { state }: Context, - option: CommentsFilterOption -) => { - state.comments.selectedCommentsFilter = option; -}; - -export const updateComment = async ( - { actions, effects, state }: Context, - { - commentId, - content, - mentions, - images, - }: { - commentId: string; - mentions: { [username: string]: UserQuery }; - images: { - [fileName: string]: { src: string; resolution: [number, number] }; - }; - content: string; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - if (commentId === OPTIMISTIC_COMMENT_ID) { - await actions.comments.saveOptimisticComment({ - content, - mentions, - }); - return; - } - const id = commentId; - const sandboxId = state.editor.currentSandbox.id; - const comment = state.comments.comments[sandboxId][id]; - - effects.analytics.track('Comments - Update Comment'); - - comment.content = convertMentionsToMentionLinks(content, mentions); - - try { - await effects.gql.mutations.updateComment({ - commentId, - content: comment.content, - sandboxId, - codeReferences: [], - imageReferences: Object.keys(images).map(fileName => ({ - fileName, - resolution: images[fileName].resolution, - src: images[fileName].src, - url: '', // Typing issue on backend, need the url here - })), - userReferences: Object.keys(mentions).map(username => ({ - username, - userId: mentions[username].id, - })), - }); - } catch (error) { - effects.notificationToast.error( - 'Unable to update your comment, please try again' - ); - } -}; - -export const queryUsers = pipe( - ({ state }: Context, query: string | null) => { - state.comments.isQueryingUsers = true; - // We reset the users when we detect a new query being written - if (query && query.length === 3) { - state.comments.usersQueryResult = []; - } - - return query; - }, - debounce(200), - filter((_, query) => Boolean(query && query.length >= 3)), - ({ effects }: Context, query) => effects.api.queryUsers(query!), - ({ state }: Context, result) => { - state.comments.usersQueryResult = result; - state.comments.isQueryingUsers = false; - } -); - -export const getCommentReplies = async ( - { state, effects }: Context, - commentId: string -) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - try { - const { - // No idea why TS complains about this - // @ts-ignore - sandbox: { comment }, - } = await effects.browser.waitAtLeast( - (DIALOG_TRANSITION_DURATION + REPLY_TRANSITION_DELAY) * 1000, - () => - effects.gql.queries.comment({ - sandboxId: sandbox.id, - commentId, - }) - ); - - comment.comments.forEach(childComment => { - state.comments.comments[sandbox.id][childComment.id] = childComment; - }); - } catch (e) { - effects.notificationToast.error( - 'Unable to get your comment, please try again' - ); - } -}; - -export const onCommentClick = ( - { state, effects, actions }: Context, - { - commentIds, - bounds, - }: { - commentIds: string[]; - bounds: { - left: number; - top: number; - right: number; - bottom: number; - }; - } -) => { - if ( - state.comments.currentCommentId && - commentIds.includes(state.comments.currentCommentId) - ) { - actions.comments.closeComment(); - return; - } - - if (!commentIds.length) { - actions.comments.createCodeLineComment(); - } else if (commentIds.length === 1) { - effects.analytics.track('Comments - Open Comment'); - actions.comments.selectComment({ - commentId: commentIds[0], - bounds, - }); - } else { - state.comments.multiCommentsSelector = { - ids: commentIds, - x: bounds.left, - y: bounds.top, - }; - } -}; - -export const closeComment = ({ state, effects }: Context) => { - if (!state.editor.currentSandbox) { - return; - } - - if (state.comments.currentCommentId === OPTIMISTIC_COMMENT_ID) { - delete state.comments.comments[state.editor.currentSandbox.id][ - OPTIMISTIC_COMMENT_ID - ]; - } - - effects.analytics.track('Comments - Close Comments'); - - state.comments.currentCommentId = null; - state.comments.currentCommentPositions = null; - - if (state.preview.mode === 'add-comment') { - state.preview.mode = null; - effects.preview.hideCommentCursor(); - } else if (state.preview.mode === 'responsive-add-comment') { - state.preview.mode = 'responsive'; - effects.preview.hideCommentCursor(); - } -}; - -export const closeMultiCommentsSelector = ({ state }: Context) => { - state.comments.multiCommentsSelector = null; -}; - -export const selectComment = async ( - { state, effects, actions }: Context, - { - commentId, - bounds, - }: { - commentId: string; - bounds?: { - left: number; - top: number; - right: number; - bottom: number; - }; - } -) => { - actions.comments.closeMultiCommentsSelector(); - - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - const comment = state.comments.comments[sandbox.id][commentId]; - - actions.comments.getCommentReplies(commentId); - - if (!bounds) { - state.comments.currentCommentId = commentId; - return; - } - - if (comment.anchorReference && comment.anchorReference.type === 'code') { - if (module) { - await actions.editor.moduleSelected({ - path: (comment.anchorReference - .metadata as CodeReferenceMetadataFragment).path, - }); - - // update comment position with precise info - const referenceBounds = await effects.vscode.getCodeReferenceBoundary( - commentId, - comment.anchorReference.metadata as CodeReferenceMetadata - ); - - if (state.comments.currentCommentId === OPTIMISTIC_COMMENT_ID) { - delete state.comments.comments[sandbox.id][OPTIMISTIC_COMMENT_ID]; - } - - state.comments.currentCommentId = commentId; - state.comments.currentCommentPositions = { - trigger: bounds, - dialog: { - left: referenceBounds.left, - top: referenceBounds.top, - bottom: referenceBounds.bottom, - right: referenceBounds.right, - }, - }; - } - } else if ( - comment.anchorReference && - comment.anchorReference.type === 'preview' - ) { - const metadata = comment.anchorReference - .metadata as PreviewReferenceMetadata; - - const previewBounds = await effects.preview.getIframeBoundingRect(); - - state.preview.responsive.resolution = [metadata.width, metadata.height]; - state.preview.mode = 'responsive'; - state.comments.currentCommentId = commentId; - - // We have to wait for the bubble to appear - await Promise.resolve(); - - const left = previewBounds.left + PREVIEW_COMMENT_OFFSET; - const top = previewBounds.top; - state.comments.currentCommentPositions = { - trigger: bounds, - dialog: { - // never go over the left of the screen - left: left >= 20 ? left : 20, - top, - bottom: top, - right: left, - }, - }; - } else { - state.comments.currentCommentId = commentId; - state.comments.currentCommentPositions = { - trigger: bounds, - dialog: null, - }; - } -}; - -export const createCodeLineComment = async ({ - state, - effects, - actions, -}: Context) => { - const selection = state.live.currentSelection!; - const codeLines = state.editor.currentModule.code.split('\n'); - const { lineNumber } = indexToLineAndColumn( - codeLines, - selection.primary.selection[0] || selection.primary.cursorPosition - ); - const anchor = lineAndColumnToIndex(codeLines, lineNumber, 1); - - effects.analytics.track('Comments - Compose Comment', { - type: 'code', - isLineComment: true, - }); - - actions.comments.addOptimisticCodeComment({ - __typename: 'CodeReferenceMetadata', - sandboxId: state.editor.currentSandbox!.id, - anchor, - head: anchor, - code: state.editor.currentModule.code.substr( - anchor, - lineAndColumnToIndex(codeLines, lineNumber + 1, 1) - ), - path: state.editor.currentModule.path, - }); -}; - -export const createCodeComment = async ({ - state, - effects, - actions, -}: Context) => { - const selection = state.live.currentSelection!; - - effects.analytics.track('Comments - Compose Comment', { - type: 'code', - isLineComment: false, - }); - - actions.comments.addOptimisticCodeComment({ - __typename: 'CodeReferenceMetadata', - sandboxId: state.editor.currentSandbox!.id, - anchor: selection.primary.selection[0] || selection.primary.cursorPosition, - head: selection.primary.selection[1] || selection.primary.cursorPosition, - code: selection.primary.selection.length - ? state.editor.currentModule.code.substr( - selection.primary.selection[0], - selection.primary.selection[1] - selection.primary.selection[0] - ) - : '', - path: state.editor.currentModule.path, - }); -}; - -export const addOptimisticCodeComment = async ( - { state, effects }: Context, - codeReference: CodeReferenceMetadata -) => { - const sandbox = state.editor.currentSandbox!; - const user = state.user!; - const id = OPTIMISTIC_COMMENT_ID; - const now = utcToZonedTime(new Date().toISOString(), 'Etc/UTC'); - const comments = state.comments.comments; - const optimisticComment: CommentFragment = { - parentComment: null, - id, - anchorReference: { - id: uuid.v4(), - type: 'code', - metadata: codeReference, - resource: state.editor.currentModule.path, - }, - insertedAt: now, - updatedAt: now, - content: '', - isResolved: false, - user: { - id: user.id, - name: user.name, - username: user.username, - avatarUrl: user.avatarUrl, - }, - references: [], - replyCount: 0, - }; - - if (!comments[sandbox.id]) { - comments[sandbox.id] = {}; - } - - comments[sandbox.id][id] = optimisticComment; - // placeholder value until we know the correct values - const { - left, - right, - top, - bottom, - } = await effects.vscode.getCodeReferenceBoundary(id, codeReference); - - state.comments.currentCommentId = id; - state.comments.currentCommentPositions = { - trigger: { - left, - top, - bottom, - right: right + CODE_COMMENT_OFFSET, - }, - dialog: { - left: left + CODE_COMMENT_OFFSET, - top, - bottom, - right, - }, - }; -}; - -export const addOptimisticPreviewComment = async ( - { state, effects }: Context, - { - x, - y, - scale, - screenshot, - }: { - x: number; - y: number; - scale: number; - screenshot: string; - } -) => { - effects.analytics.track('Comments - Create Optimistic Preview Comment'); - - const sandbox = state.editor.currentSandbox!; - const user = state.user!; - const id = OPTIMISTIC_COMMENT_ID; - const now = utcToZonedTime(new Date().toISOString(), 'Etc/UTC'); - const comments = state.comments.comments; - const previewIframeBounds = await effects.preview.getIframeBoundingRect(); - const previewPath = await effects.preview.getPreviewPath(); - const screenshotUrl = await effects.preview.createScreenshot({ - screenshotSource: screenshot, - bubbleSource: window.devicePixelRatio > 1 ? BUBBLE_IMAGE_2X : BUBBLE_IMAGE, - cropWidth: 1000, - cropHeight: 400, - x: Math.round(x), - y: Math.round(y), - scale: state.preview.hasExtension ? 1 : scale, - }); - const isResponsive = state.preview.mode === 'responsive-add-comment'; - const metadata: PreviewReferenceMetadata = { - userAgent: effects.browser.getUserAgent(), - screenshotUrl, - width: isResponsive - ? state.preview.responsive.resolution[0] - : previewIframeBounds.width, - height: isResponsive - ? state.preview.responsive.resolution[1] - : previewIframeBounds.height, - x: isResponsive ? Math.round(x * (1 / scale)) : x, - y: isResponsive ? Math.round(y * (1 / scale)) : y, - previewPath, - }; - const optimisticComment: CommentFragment = { - parentComment: null, - id, - anchorReference: { - id: uuid.v4(), - type: 'preview', - metadata, - resource: previewPath, - }, - insertedAt: now, - updatedAt: now, - content: '', - isResolved: false, - user: { - id: user.id, - name: user.name, - username: user.username, - avatarUrl: user.avatarUrl, - }, - references: [], - replyCount: 0, - }; - - if (!comments[sandbox.id]) { - comments[sandbox.id] = {}; - } - - comments[sandbox.id][id] = optimisticComment; - state.comments.currentCommentId = id; - - const left = x + previewIframeBounds.left; - const top = y + previewIframeBounds.top; - const bottom = top; - const right = left; - - state.comments.currentCommentPositions = { - trigger: { - left, - top, - bottom, - right: right + PREVIEW_COMMENT_OFFSET, - }, - dialog: { - left: left + PREVIEW_COMMENT_OFFSET, - top, - bottom, - right, - }, - }; -}; - -export const saveOptimisticComment = async ( - { state, actions, effects }: Context, - { - content: rawContent, - mentions, - }: { - content: string; - mentions: { [username: string]: UserQuery }; - } -) => { - const sandbox = state.editor.currentSandbox!; - const sandboxId = sandbox.id; - const id = uuid.v4(); - const content = convertMentionsToMentionLinks(rawContent, mentions); - const comment = { - ...state.comments.comments[sandboxId][OPTIMISTIC_COMMENT_ID], - content, - id, - }; - state.comments.comments[sandbox.id][id] = comment; - state.comments.currentCommentId = state.comments.currentCommentId ? id : null; - delete state.comments.comments[sandbox.id][OPTIMISTIC_COMMENT_ID]; - - effects.preview.hideCommentCursor(); - - if (state.preview.mode === 'responsive-add-comment') { - state.preview.mode = 'responsive'; - } else { - state.preview.mode = null; - } - - return actions.comments.saveComment(comment); -}; - -export const saveNewComment = async ( - { state, actions }: Context, - { - content: rawContent, - mentions, - images, - parentCommentId, - }: { - content: string; - mentions: { [username: string]: UserQuery }; - images: { - [fileName: string]: { - src: string; - resolution: [number, number]; - }; - }; - parentCommentId?: string; - } -) => { - const user = state.user!; - const sandbox = state.editor.currentSandbox!; - const now = utcToZonedTime(new Date().toISOString(), 'Etc/UTC'); - const comments = state.comments.comments; - - if (!comments[sandbox.id]) { - comments[sandbox.id] = {}; - } - - const id = uuid.v4(); - const content = convertMentionsToMentionLinks(rawContent, mentions); - const comment: CommentFragment = { - parentComment: parentCommentId ? { id: parentCommentId } : null, - anchorReference: null, - id, - insertedAt: now, - updatedAt: now, - content, - isResolved: false, - user: { - id: user.id, - name: user.name, - username: user.username, - avatarUrl: user.avatarUrl, - }, - references: [ - ...convertMentionsToUserReferences(mentions), - ...convertImagesToImageReferences(images), - ], - replyCount: 0, - }; - - return actions.comments.saveComment(comment); -}; - -export const saveComment = async ( - { state, effects }: Context, - comment: CommentFragment -) => { - const comments = state.comments.comments; - const sandbox = state.editor.currentSandbox!; - - state.comments.selectedCommentsFilter = CommentsFilterOption.OPEN; - comments[sandbox.id][comment.id] = comment; - - if (comment.parentComment) { - comments[sandbox.id][comment.parentComment.id].replyCount++; - } - - effects.analytics.track('Comments - Create Comment', { - type: (comment.anchorReference && comment.anchorReference.type) || 'global', - }); - - // The server might be ahead on sandbox version, so we need to try to save - // several times - let tryCount = 0; - - async function trySaveComment() { - tryCount++; - - const { - userReferences, - codeReferences, - imageReferences, - } = comment.references.reduce<{ - userReferences: UserReference[]; - codeReferences: CodeReference[]; - imageReferences: ImageReference[]; - }>( - (aggr, reference) => { - if (reference.type === 'user') { - aggr.userReferences.push({ - userId: (reference.metadata as UserReferenceMetadata).userId, - username: (reference.metadata as UserReferenceMetadata).username, - }); - } else if (reference.type === 'code') { - aggr.codeReferences.push({ - anchor: (reference.metadata as CodeReferenceMetadata).anchor, - code: (reference.metadata as CodeReferenceMetadata).code, - head: (reference.metadata as CodeReferenceMetadata).head, - path: (reference.metadata as CodeReferenceMetadata).path, - lastUpdatedAt: state.editor.currentSandbox!.modules.find( - module => - module.path === - (reference.metadata as CodeReferenceMetadata).path - )!.updatedAt, - }); - } else if (reference.type === 'image') { - aggr.imageReferences.push({ - fileName: (reference.metadata as ImageReferenceMetadata).fileName, - resolution: (reference.metadata as ImageReferenceMetadata) - .resolution, - src: (reference.metadata as ImageReferenceMetadata).url, - url: '', // Backend typing issue, need the url here - }); - } - return aggr; - }, - { - userReferences: [], - codeReferences: [], - imageReferences: [], - } - ); - const baseCommentPayload = { - id: comment.id, - parentCommentId: comment.parentComment ? comment.parentComment.id : null, - sandboxId: sandbox.id, - content: comment.content || '', - userReferences, - codeReferences, - imageReferences, - }; - - if (comment.anchorReference) { - const reference = comment.anchorReference; - - if (reference.type === 'code') { - const metadata = reference.metadata as CodeReferenceMetadata; - await effects.gql.mutations.createCodeComment({ - ...baseCommentPayload, - anchorReference: { - anchor: metadata.anchor, - head: metadata.head, - code: metadata.code, - path: metadata.path, - lastUpdatedAt: state.editor.currentSandbox!.modules.find( - module => module.path === metadata.path - )!.updatedAt, - }, - }); - } else if (reference.type === 'preview') { - const metadata = reference.metadata as PreviewReferenceMetadata; - await effects.gql.mutations.createPreviewComment({ - ...baseCommentPayload, - anchorReference: { - height: Math.round(metadata.height), - previewPath: metadata.previewPath, - userAgent: metadata.userAgent, - screenshotSrc: metadata.screenshotUrl || null, - width: Math.round(metadata.width), - x: Math.round(metadata.x), - y: Math.round(metadata.y), - }, - }); - } - } else { - await effects.gql.mutations.createComment(baseCommentPayload); - } - } - - try { - await trySaveComment(); - } catch (error) { - if (error.response?.data?.error === 'old_version' && tryCount < 3) { - await trySaveComment(); - } else { - captureException(error); - effects.notificationToast.error( - 'Unable to create your comment, please try again' - ); - - if (comment.parentComment) { - comments[sandbox.id][comment.parentComment.id].replyCount--; - } - - delete comments[sandbox.id][comment.id]; - } - } -}; - -export const deleteComment = async ( - { state, effects }: Context, - { - commentId, - }: { - commentId: string; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - const sandboxId = state.editor.currentSandbox.id; - const comments = state.comments.comments; - const deletedComment = comments[sandboxId][commentId]; - delete comments[sandboxId][commentId]; - - effects.analytics.track('Comments - Delete Comment'); - - try { - await effects.gql.mutations.deleteComment({ - commentId, - sandboxId, - }); - } catch (error) { - effects.notificationToast.error( - 'Unable to delete your comment, please try again' - ); - comments[sandboxId][commentId] = deletedComment; - } -}; - -export const resolveComment = async ( - { effects, state }: Context, - { - commentId, - isResolved, - }: { - commentId: string; - isResolved: boolean; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - const comments = state.comments.comments; - const sandboxId = state.editor.currentSandbox.id; - const oldIsResolved = comments[sandboxId][commentId].isResolved; - - comments[sandboxId][commentId].isResolved = isResolved; - - effects.analytics.track('Comments - Resolve Comment'); - - try { - await (isResolved - ? effects.gql.mutations.resolveComment({ - commentId, - sandboxId, - }) - : effects.gql.mutations.unresolveComment({ - commentId, - sandboxId, - })); - } catch (error) { - effects.notificationToast.error( - 'Unable to update your comment, please try again' - ); - comments[sandboxId][commentId].isResolved = oldIsResolved; - } -}; - -export const copyPermalinkToClipboard = ( - { effects }: Context, - commentId: string -) => { - effects.analytics.track('Comments - Copy Permalink'); - effects.browser.copyToClipboard(effects.router.createCommentUrl(commentId)); - effects.notificationToast.success('Comment permalink copied to clipboard'); -}; - -export const getSandboxComments = async ( - { state, effects, actions }: Context, - sandboxId: string -) => { - try { - const { sandbox: sandboxComments } = await effects.gql.queries.comments({ - sandboxId, - }); - - if (!sandboxComments || !sandboxComments.comments) { - return; - } - - state.comments.comments[sandboxId] = sandboxComments.comments.reduce<{ - [id: string]: CommentFragment; - }>((aggr, commentThread) => { - aggr[commentThread.id] = commentThread; - - return aggr; - }, {}); - - const urlCommentId = effects.router.getCommentId(); - if (urlCommentId) { - actions.workspace.setWorkspaceItem({ item: 'comments' }); - actions.comments.selectComment({ - commentId: urlCommentId, - bounds: { - left: effects.browser.getWidth() / 2, - top: 80, - right: effects.browser.getWidth() / 3, - bottom: 0, - }, - }); - } - - effects.gql.subscriptions.commentAdded.dispose(); - effects.gql.subscriptions.commentChanged.dispose(); - effects.gql.subscriptions.commentRemoved.dispose(); - - effects.gql.subscriptions.commentAdded( - { - sandboxId, - }, - actions.comments.onCommentAdded - ); - effects.gql.subscriptions.commentChanged( - { - sandboxId, - }, - actions.comments.onCommentChanged - ); - effects.gql.subscriptions.commentRemoved( - { - sandboxId, - }, - actions.comments.onCommentRemoved - ); - } catch (e) { - state.comments.comments[sandboxId] = {}; - effects.notificationToast.notice( - `There was a problem getting the sandbox comments` - ); - } - - // When we load the comments there might be changes already, lets make sure we transpose - // any comments on these changes. This does not fix it if you already managed to save, but - // that is considered an extreme edge case - state.editor.changedModuleShortids.forEach(moduleShortid => { - const module = state.editor.currentSandbox!.modules.find( - moduleItem => moduleItem.shortid === moduleShortid - ); - - if (!module) { - return; - } - - const operation = getTextOperation(module.savedCode || '', module.code); - actions.comments.transposeComments({ module, operation }); - }); -}; - -export const onCommentAdded = ( - { state }: Context, - { commentAdded: comment }: CommentAddedSubscription -) => { - if (comment.anchorReference && comment.anchorReference.type === 'code') { - const metadata = comment.anchorReference - .metadata as CodeReferenceMetadataFragment; - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - const module = sandbox.modules.find( - moduleItem => moduleItem.path === metadata.path - ); - - if (!module) { - return; - } - - // We create a diff operation which is applied to the comment to ensure - // any operations received in the meantime is applied - const diffOperation = getTextOperation( - module.savedCode || module.code, - module.code - ); - const range = new Selection.Range(metadata.anchor, metadata.head); - const newRange = range.transform(diffOperation); - - metadata.anchor = newRange.anchor; - metadata.head = newRange.head; - } - - state.comments.comments[comment.sandbox.id][comment.id] = comment; -}; - -export const onCommentChanged = ( - { state }: Context, - { commentChanged: comment }: CommentChangedSubscription -) => { - Object.assign( - state.comments.comments[comment.sandbox.id][comment.id], - comment - ); -}; - -export const onCommentRemoved = ( - { state }: Context, - { commentRemoved: comment }: CommentRemovedSubscription -) => { - delete state.comments.comments[comment.sandbox.id][comment.id]; -}; - -export const transposeComments = ( - { state }: Context, - { - module, - operation, - }: { - module: Module; - operation: TextOperation; - } -) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const comments = state.comments.fileComments[module.path] || []; - comments.forEach(fileComment => { - const range = new Selection.Range(...fileComment.range); - const newRange = range.transform(operation); - const comment = state.comments.comments[sandbox.id][fileComment.commentId]; - if (comment.anchorReference && comment.anchorReference.type === 'code') { - const metadata = comment.anchorReference - .metadata as CodeReferenceMetadataFragment; - metadata.anchor = newRange.anchor; - metadata.head = newRange.head; - } - }); -}; diff --git a/packages/app/src/app/overmind/namespaces/comments/constants.ts b/packages/app/src/app/overmind/namespaces/comments/constants.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/app/src/app/overmind/namespaces/comments/index.ts b/packages/app/src/app/overmind/namespaces/comments/index.ts deleted file mode 100644 index dd0715cd945..00000000000 --- a/packages/app/src/app/overmind/namespaces/comments/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as actions from './actions'; -import { state } from './state'; - -export { state, actions }; diff --git a/packages/app/src/app/overmind/namespaces/comments/state.ts b/packages/app/src/app/overmind/namespaces/comments/state.ts deleted file mode 100644 index fb016cd41ef..00000000000 --- a/packages/app/src/app/overmind/namespaces/comments/state.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { CommentsFilterOption, UserQuery } from '@codesandbox/common/lib/types'; -import { - CodeReferenceMetadata, - CommentFragment, - CommentWithRepliesFragment, -} from 'app/graphql/types'; -import { Context } from 'app/overmind'; -import isToday from 'date-fns/isToday'; -import { derived } from 'overmind'; - -export const OPTIMISTIC_COMMENT_ID = 'OPTIMISTIC_COMMENT_ID'; - -type FileComments = { - [path: string]: Array<{ - commentId: string; - range: [number, number]; - }>; -}; - -type State = { - comments: { - [sandboxId: string]: { - [commentId: string]: CommentFragment; - }; - }; - currentComments: CommentFragment[]; - isQueryingUsers: boolean; - selectedCommentsFilter: CommentsFilterOption; - currentCommentId: string | null; - currentCommentPositions: { - trigger: { left: number; top: number; right: number; bottom: number }; - dialog: { left: number; top: number; right: number; bottom: number } | null; - } | null; - currentComment: CommentWithRepliesFragment | null; - fileComments: FileComments; - usersQueryResult: UserQuery[]; - currentCommentsByDate: { - today: CommentFragment[]; - prev: CommentFragment[]; - }; - multiCommentsSelector: { - ids: string[]; - x: number; - y: number; - } | null; -}; - -export const state: State = { - multiCommentsSelector: null, - isQueryingUsers: false, - currentCommentPositions: null, - usersQueryResult: [], - comments: {}, - currentCommentId: null, - fileComments: derived( - ({ comments }: State, { editor: { currentSandbox } }: Context['state']) => { - if (!currentSandbox || !comments[currentSandbox.id]) { - return {}; - } - const rootComments = Object.values(comments[currentSandbox.id]).filter( - comment => comment.parentComment == null && !comment.isResolved - ); - - return rootComments.reduce<{ - [path: string]: Array<{ - commentId: string; - range: [number, number]; - }>; - }>((aggr, comment) => { - if ( - comment.anchorReference && - comment.anchorReference.type === 'code' - ) { - const metadata = comment.anchorReference - .metadata as CodeReferenceMetadata; - if (!aggr[metadata.path]) { - aggr[metadata.path] = []; - } - aggr[metadata.path].push({ - commentId: comment.id, - range: [metadata.anchor, metadata.head], - }); - } - - return aggr; - }, {}); - } - ), - currentComment: derived( - ( - { comments, currentCommentId }: State, - { editor: { currentSandbox } }: Context['state'] - ) => { - if ( - !currentSandbox || - !comments[currentSandbox.id] || - !currentCommentId - ) { - return null; - } - - function sortByInsertedAt( - commentA: CommentFragment | null, - commentB: CommentFragment | null - ) { - if (!commentA || !commentB) { - return 0; - } - - const aDate = new Date(commentA.insertedAt); - const bDate = new Date(commentB.insertedAt); - - if (aDate > bDate) { - return 1; - } - - if (bDate < aDate) { - return -1; - } - - return 0; - } - - if (!comments[currentSandbox.id][currentCommentId]) { - return null; - } - - return { - ...comments[currentSandbox.id][currentCommentId], - comments: Object.keys(comments[currentSandbox.id]) - .reduce((aggr, commentId) => { - if ( - comments[currentSandbox.id][commentId].parentComment?.id === - currentCommentId - ) { - return aggr.concat(comments[currentSandbox.id][commentId]); - } - - return aggr; - }, []) - .sort(sortByInsertedAt), - }; - } - ), - selectedCommentsFilter: CommentsFilterOption.OPEN, - currentComments: derived( - ( - { comments, selectedCommentsFilter }: State, - { editor: { currentSandbox } }: Context['state'] - ) => { - if (!currentSandbox || !comments[currentSandbox.id]) { - return []; - } - - function sortByInsertedAt( - commentA: CommentFragment, - commentB: CommentFragment - ) { - const aDate = new Date(commentA.insertedAt); - const bDate = new Date(commentB.insertedAt); - - if (aDate > bDate) { - return -1; - } - - if (bDate < aDate) { - return 1; - } - - return 0; - } - - const rootComments = Object.values(comments[currentSandbox.id]).filter( - comment => - comment.parentComment == null && comment.id !== OPTIMISTIC_COMMENT_ID - ); - switch (selectedCommentsFilter) { - case CommentsFilterOption.ALL: - return rootComments.sort(sortByInsertedAt); - case CommentsFilterOption.RESOLVED: - return rootComments - .filter(comment => comment.isResolved) - .sort(sortByInsertedAt); - case CommentsFilterOption.OPEN: - return rootComments - .filter(comment => !comment.isResolved) - .sort(sortByInsertedAt); - default: - return []; - } - } - ), - currentCommentsByDate: derived(({ currentComments }: State) => - currentComments.reduce<{ - today: CommentFragment[]; - prev: CommentFragment[]; - }>( - (acc, comment) => { - if ( - isToday(new Date(comment.insertedAt)) || - isToday(new Date(comment.updatedAt)) - ) { - acc.today.push(comment); - } else { - acc.prev.push(comment); - } - - return acc; - }, - { - today: [], - prev: [], - } - ) - ), -}; diff --git a/packages/app/src/app/overmind/namespaces/dashboard/actions.ts b/packages/app/src/app/overmind/namespaces/dashboard/actions.ts index 0412ea6e748..83013d19dd5 100755 --- a/packages/app/src/app/overmind/namespaces/dashboard/actions.ts +++ b/packages/app/src/app/overmind/namespaces/dashboard/actions.ts @@ -9,7 +9,13 @@ import { RepoFragmentDashboardFragment, ProjectFragment, } from 'app/graphql/types'; -import { v2BranchUrl } from '@codesandbox/common/lib/utils/url-generator'; +import { + sandboxUrl, + v2BranchUrl, + vsCodeLauncherUrl, + vsCodeUrl, +} from '@codesandbox/common/lib/utils/url-generator'; +import { ForkSandboxBody } from '@codesandbox/common/lib/types'; import { notificationState } from '@codesandbox/common/lib/utils/notifications'; import { NotificationStatus } from '@codesandbox/notifications'; import { PrivacyLevel } from 'app/components/Create/utils/types'; @@ -1719,3 +1725,84 @@ export const convertToDevbox = async ( // Re-fetch team to get updated usage data actions.getActiveTeamInfo(); }; + +export const forkSandbox = async ( + { effects, state, actions }: Context, + { + sandboxId, + openInNewWindow, + openInVSCode, + autoLaunchVSCode, + customVMTier, + body, + redirectAfterFork, + }: { + sandboxId: string; + openInNewWindow?: boolean; + openInVSCode?: boolean; + autoLaunchVSCode?: boolean; + customVMTier?: number; + redirectAfterFork?: boolean; + body?: { + collectionId?: string; + alias?: string; + v2?: boolean; + title?: string; + privacy?: 0 | 1 | 2; + }; + } +) => { + effects.analytics.track('Fork Sandbox', { type: 'external', sandboxId }); + + const usedBody: ForkSandboxBody = body || {}; + + if (state.activeTeam) { + usedBody.teamId = state.activeTeam; + } + + try { + const forkedSandbox = await effects.api.forkSandbox(sandboxId, usedBody); + + if (customVMTier) { + await effects.api.setVMSpecs(forkedSandbox.id, customVMTier); + } + + if (openInVSCode) { + if (autoLaunchVSCode) { + window.open(vsCodeUrl(forkedSandbox.id)); + } else { + window.open(vsCodeLauncherUrl(forkedSandbox.id)); + } + } + + if (redirectAfterFork) { + const url = sandboxUrl({ id: forkedSandbox.id, isV2: forkedSandbox.v2 }); + + if (openInNewWindow) { + window.open(url, '_blank'); + } else { + window.location.href = url; + } + } + + return forkedSandbox; + } catch (error) { + const errorMessage = actions.internal.getErrorMessage({ error }); + + if (errorMessage.includes('DRAFT_LIMIT')) { + effects.notificationToast.add({ + title: 'Cannot create draft', + message: + 'Your drafts folder is full. Delete unneeded drafts, or upgrade to Pro for unlimited drafts.', + status: NotificationStatus.ERROR, + }); + } else { + actions.internal.handleError({ + message: 'We were unable to fork the sandbox', + error, + }); + } + } + + return undefined; +}; diff --git a/packages/app/src/app/overmind/namespaces/deployment/actions.ts b/packages/app/src/app/overmind/namespaces/deployment/actions.ts deleted file mode 100755 index ed03a0753b2..00000000000 --- a/packages/app/src/app/overmind/namespaces/deployment/actions.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { Context } from 'app/overmind'; -import { AxiosError } from 'axios'; -import { get } from 'lodash-es'; - -import * as internalActions from './internalActions'; - -export const internal = internalActions; - -const getVercelErrorMessage = (error: AxiosError) => - get( - error, - 'response.data.error.message', - 'An unknown error occurred when connecting to Vercel' - ); - -export const deployWithNetlify = async ({ - effects, - actions, - state, -}: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - state.deployment.deploying = true; - - try { - const id = await effects.netlify.deploy(sandbox); - state.deployment.deploying = false; - - await actions.deployment.getNetlifyDeploys(); - - state.deployment.building = true; - await effects.netlify.getLogs(id); - effects.notificationToast.success('Sandbox Deploying'); - } catch (error) { - actions.internal.handleError({ - message: 'An error occurred when deploying your Netlify site', - error, - }); - } - state.deployment.deploying = false; - state.deployment.building = false; -}; - -export const getNetlifyDeploys = async ({ state, effects }: Context) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - try { - state.deployment.netlify.claimUrl = await effects.netlify.claimSite( - sandbox.id - ); - state.deployment.netlify.site = await effects.netlify.getDeployments( - sandbox.id - ); - } catch (error) { - state.deployment.netlify.site = null; - } -}; - -export const getDeploys = async ({ state, actions, effects }: Context) => { - if ( - !state.user || - !state.user.integrations.vercel || - !state.editor.currentSandbox - ) { - return; - } - - state.deployment.vercel.gettingDeploys = true; - - try { - const vercelConfig = effects.vercel.getConfig(state.editor.currentSandbox); - - if (vercelConfig.name) { - state.deployment.vercel.deploys = await effects.vercel.getDeployments( - vercelConfig.name - ); - } - } catch (error) { - actions.internal.handleError({ - message: getVercelErrorMessage(error), - error, - }); - } - - state.deployment.vercel.gettingDeploys = false; -}; - -export const deployPreviewClicked = async ({ - state, - effects, - actions, -}: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - try { - state.deployment.deploying = true; - const zip = await effects.zip.create(sandbox); - const contents = await effects.jsZip.loadAsync(zip.file); - - if (sandbox.isSse && state.editor.currentSandbox) { - const envs = await effects.api.getEnvironmentVariables( - state.editor.currentSandbox.id - ); - if (envs) { - await effects.vercel.checkEnvironmentVariables(sandbox, envs); - } - } - - state.deployment.vercel.url = await effects.vercel.deploy( - contents, - sandbox - ); - } catch (error) { - actions.internal.handleError({ - message: getVercelErrorMessage(error), - error, - }); - } - - state.deployment.deploying = false; - - actions.deployment.getDeploys(); -}; -export const deployProductionClicked = async ({ - state, - effects, - actions, -}: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - try { - state.deployment.deploying = true; - const zip = await effects.zip.create(sandbox); - const contents = await effects.jsZip.loadAsync(zip.file); - - if (sandbox.isSse && state.editor.currentSandbox) { - const envs = await effects.api.getEnvironmentVariables( - state.editor.currentSandbox.id - ); - if (envs) { - await effects.vercel.checkEnvironmentVariables(sandbox, envs); - } - } - - state.deployment.vercel.url = await effects.vercel.deploy( - contents, - sandbox, - 'production' - ); - } catch (error) { - actions.internal.handleError({ - message: getVercelErrorMessage(error), - error, - }); - } - - state.deployment.deploying = false; - - actions.deployment.getDeploys(); -}; - -export const deploySandboxClicked = async ({ - actions, - effects, - state, -}: Context) => { - state.currentModal = 'deployment'; - - const vercelIntegration = state.user && state.user.integrations.vercel; - - if (!vercelIntegration || !vercelIntegration.token) { - effects.notificationToast.error( - 'You are not authorized with Vercel, please refresh and log in again' - ); - return; - } - - if (!vercelIntegration.email) { - try { - const user = await effects.vercel.getUser(); - - if (state.user && state.user.integrations.vercel) { - state.user.integrations.vercel.email = user.email; - } - } catch (error) { - actions.internal.handleError({ - message: - 'We were not able to fetch your Vercel user details. You should still be able to deploy to Vercel, please try again if needed.', - error, - }); - } - } - - state.deployment.vercel.url = null; -}; - -export const setDeploymentToDelete = ({ state }: Context, id: string) => { - state.deployment.vercel.deployToDelete = id; -}; - -export const deleteDeployment = async ({ - state, - effects, - actions, -}: Context) => { - const id = state.deployment.vercel.deployToDelete; - - if (!id) { - return; - } - - state.currentModal = null; - state.deployment.vercel.deploysBeingDeleted.push(id); - - try { - await effects.vercel.deleteDeployment(id); - - effects.notificationToast.success('Deployment deleted'); - actions.deployment.getDeploys(); - } catch (error) { - actions.internal.handleError({ - message: 'An unknown error occurred when deleting your deployment', - error, - }); - } - - state.deployment.vercel.deploysBeingDeleted.splice( - state.deployment.vercel.deploysBeingDeleted.indexOf(id), - 1 - ); -}; - -export const deployWithGitHubPages = async ({ - effects, - actions, - state, -}: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - state.deployment.deploying = true; - - try { - await effects.githubPages.deploy( - sandbox, - state.deployment.githubSite.ghLogin - ); - state.deployment.deploying = false; - - state.deployment.building = true; - await effects.githubPages.getLogs(sandbox.id); - state.deployment.githubSite.ghPages = true; - effects.notificationToast.success('Sandbox Deploying'); - } catch (error) { - actions.internal.handleError({ - message: 'An error occurred when deploying your sandbox to GitHub Pages', - error, - }); - } - state.deployment.deploying = false; - state.deployment.building = false; -}; - -export const fetchGithubSite = async ({ effects, state }: Context) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - try { - const site = await effects.githubPages.getSite(sandbox.id); - state.deployment.githubSite = { - ...site, - name: `csb-${sandbox.id}`, - }; - } catch { - state.deployment.githubSite.name = `csb-${sandbox.id}`; - } -}; diff --git a/packages/app/src/app/overmind/namespaces/deployment/index.ts b/packages/app/src/app/overmind/namespaces/deployment/index.ts deleted file mode 100755 index 0d573be52de..00000000000 --- a/packages/app/src/app/overmind/namespaces/deployment/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { state } from './state'; -import * as actions from './actions'; - -export { state, actions }; diff --git a/packages/app/src/app/overmind/namespaces/deployment/internalActions.ts b/packages/app/src/app/overmind/namespaces/deployment/internalActions.ts deleted file mode 100755 index db2d2107cd4..00000000000 --- a/packages/app/src/app/overmind/namespaces/deployment/internalActions.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Context } from 'app/overmind'; - -export const getVercelUserDetails = async ({ - state, - actions, - effects, -}: Context) => { - if ( - state.user && - state.user.integrations.vercel && - state.user.integrations.vercel.token && - !state.user.integrations.vercel.email - ) { - state.isLoadingVercel = true; - try { - const vercelDetails = await effects.vercel.getUser(); - state.user.integrations.vercel.email = vercelDetails.email; - } catch (error) { - actions.internal.handleError({ - message: - 'We were not able to fetch your Vercel user details. You should still be able to deploy to Vercel, please try again if needed.', - error, - }); - } - state.isLoadingVercel = false; - } -}; diff --git a/packages/app/src/app/overmind/namespaces/deployment/state.ts b/packages/app/src/app/overmind/namespaces/deployment/state.ts deleted file mode 100755 index 10b88c9a486..00000000000 --- a/packages/app/src/app/overmind/namespaces/deployment/state.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NetlifySite, VercelDeployment } from '@codesandbox/common/lib/types'; - -type State = { - deploying: boolean; - building: boolean; - vercel: { - gettingDeploys: boolean; - deploys: VercelDeployment[]; - deploysBeingDeleted: string[]; - deployToDelete: string | null; - url: string | null; - }; - netlify: { - claimUrl: string | null; - site: NetlifySite | null; - }; - githubSite: { - ghLogin: string; - ghPages: boolean; - name: string; - }; -}; - -export const state: State = { - deploying: false, - building: false, - vercel: { - deploys: [], - deploysBeingDeleted: [], - deployToDelete: null, - url: null, - gettingDeploys: true, - }, - netlify: { - claimUrl: null, - site: null, - }, - githubSite: { - name: '', - ghLogin: '', - ghPages: false, - }, -}; diff --git a/packages/app/src/app/overmind/namespaces/editor/actions.ts b/packages/app/src/app/overmind/namespaces/editor/actions.ts deleted file mode 100755 index 990bf3a3336..00000000000 --- a/packages/app/src/app/overmind/namespaces/editor/actions.ts +++ /dev/null @@ -1,1957 +0,0 @@ -import { resolveModule } from '@codesandbox/common/lib/sandbox/modules'; -import getTemplate from '@codesandbox/common/lib/templates'; -import { - EnvironmentVariable, - Module, - ModuleCorrection, - ModuleError, - ModuleTab, - UserSelection, - WindowOrientation, - ForkSandboxBody, -} from '@codesandbox/common/lib/types'; -import { - captureException, - logBreadcrumb, -} from '@codesandbox/common/lib/utils/analytics/sentry'; -import { isAbsoluteVersion } from '@codesandbox/common/lib/utils/dependencies'; -import { getTextOperation } from '@codesandbox/common/lib/utils/diff'; -import { convertTypeToStatus } from '@codesandbox/common/lib/utils/notifications'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { - sandboxUrl, - signInPageUrl, - docsUrl, - vsCodeUrl, - vsCodeLauncherUrl, -} from '@codesandbox/common/lib/utils/url-generator'; -import { NotificationStatus } from '@codesandbox/notifications'; -import { - Authorization, - CollaboratorFragment, - InvitationFragment, -} from 'app/graphql/types'; -import { Context } from 'app/overmind'; -import { withLoadApp, withOwnedSandbox } from 'app/overmind/factories'; -import { getSavedCode } from 'app/overmind/utils/sandbox'; -import { - addDevToolsTab as addDevToolsTabUtil, - closeDevToolsTab as closeDevToolsTabUtil, - moveDevToolsTab as moveDevToolsTabUtil, -} from 'app/pages/Sandbox/Editor/Content/utils'; -import { convertAuthorizationToPermissionType } from 'app/utils/authorization'; -import { clearCorrectionsFromAction } from 'app/utils/corrections'; -import history from 'app/utils/history'; -import { isPrivateScope } from 'app/utils/private-registry'; -import { debounce } from 'lodash-es'; -import { TextOperation } from 'ot'; -import { json } from 'overmind'; - -import eventToTransform from '../../utils/event-to-transform'; -import { SERVER } from '../../utils/items'; -import * as internalActions from './internalActions'; - -export const internal = internalActions; - -export const onNavigateAway = () => {}; - -export const persistCursorToUrl = debounce( - ( - { effects }: Context, - { - module, - selection, - }: { - module: Module; - selection?: UserSelection; - } - ) => { - let parameter = module.path; - - if (!parameter) { - return; - } - - if (selection?.primary?.selection?.length) { - const [head, anchor] = selection.primary.selection; - const serializedSelection = head + '-' + anchor; - parameter += `:${serializedSelection}`; - } - - const newUrl = new URL(document.location.href); - newUrl.searchParams.set('file', parameter); - - // Restore the URI encoded parts to their original values. Our server handles this well - // and all the browsers do too. - if (newUrl) { - effects.router.replace( - newUrl.toString().replace(/%2F/g, '/').replace('%3A', ':') - ); - } - }, - 500 -); - -export const loadCursorFromUrl = async ({ - effects, - actions, - state, -}: Context) => { - if (!state.editor.currentSandbox) { - return; - } - - const parameter = effects.router.getParameter('file'); - if (!parameter) { - return; - } - const [path, selection] = parameter.split(':'); - - const module = state.editor.currentSandbox.modules.find(m => m.path === path); - if (!module) { - return; - } - - await actions.editor.moduleSelected({ id: module.id }); - - if (!selection) { - return; - } - - const [parsedHead, parsedAnchor] = selection.split('-').map(Number); - if (!isNaN(parsedHead) && !isNaN(parsedAnchor)) { - effects.vscode.setSelection(parsedHead, parsedAnchor); - } -}; - -export const refreshPreview = ({ effects }: Context) => { - effects.preview.refresh(); -}; - -export const addNpmDependency = withOwnedSandbox( - async ( - { actions, effects, state }: Context, - { - name, - version, - isDev, - }: { - name: string; - version?: string; - isDev?: boolean; - } - ) => { - const currentSandbox = state.editor.currentSandbox; - const isPrivatePackage = - currentSandbox && isPrivateScope(currentSandbox, name); - - effects.analytics.track('Add NPM Dependency', { - private: isPrivatePackage, - }); - state.currentModal = null; - let newVersion = version || 'latest'; - - if (!isAbsoluteVersion(newVersion)) { - if (isPrivatePackage && currentSandbox) { - try { - const manifest = await effects.api.getDependencyManifest( - currentSandbox.id, - name - ); - const distTags = manifest['dist-tags']; - const absoluteVersion = distTags ? distTags[newVersion] : null; - - if (absoluteVersion) { - newVersion = absoluteVersion; - } - } catch (e) { - if (currentSandbox.privacy !== 2) { - effects.notificationToast.add({ - status: NotificationStatus.ERROR, - title: 'There was a problem adding the private package', - message: - 'Private packages can only be added to private sandboxes', - actions: { - primary: { - label: 'Make Sandbox Private', - run: async () => { - await actions.workspace.sandboxPrivacyChanged({ - privacy: 2, - source: 'Private Package Notification', - }); - actions.editor.addNpmDependency({ name, version }); - }, - }, - secondary: { - label: 'Learn More', - run: () => { - effects.browser.openWindow( - docsUrl('/learn/sandboxes/custom-npm-registry') - ); - }, - }, - }, - }); - } else { - captureException(e); - actions.internal.handleError({ - error: e, - message: 'There was a problem adding the private package', - }); - } - return; - } - } else { - const dependency = await effects.api.getDependency(name, newVersion); - newVersion = dependency.version; - } - } - - await actions.editor.internal.addNpmDependencyToPackageJson({ - name, - version: newVersion, - isDev: Boolean(isDev), - }); - - actions.workspace.changeDependencySearch(''); - actions.workspace.clearExplorerDependencies(); - - effects.preview.executeCodeImmediately(); - } -); - -export const npmDependencyRemoved = withOwnedSandbox( - async ({ actions, effects }: Context, name: string) => { - effects.analytics.track('Remove NPM Dependency'); - - await actions.editor.internal.removeNpmDependencyFromPackageJson(name); - - effects.preview.executeCodeImmediately(); - } -); - -export const sandboxChanged = withLoadApp<{ - id: string; - hasBetaEditorExperiment: boolean; -}>( - async ( - { state, actions, effects }: Context, - { id, hasBetaEditorExperiment } - ) => { - // This happens when we fork. This can be avoided with state first routing - if (state.editor.isForkingSandbox && state.editor.currentSandbox) { - if (state.editor.currentModule.id) { - effects.vscode.openModule(state.editor.currentModule); - } - - await actions.editor.internal.initializeSandbox( - state.editor.currentSandbox - ); - - actions.git.loadGitSource(); - - state.editor.isForkingSandbox = false; - return; - } - - await effects.vscode.closeAllTabs(); - - state.editor.error = null; - state.git.sourceSandboxId = null; - - let newId = id; - - newId = actions.editor.internal.ensureSandboxId(newId); - - effects.browser.storage.set('currentSandboxId', newId); - - const hasExistingSandbox = Boolean(state.editor.currentId); - - if (state.live.isLive) { - actions.live.internal.disconnect(); - } - - state.editor.isLoading = !hasExistingSandbox; - - const url = new URL(document.location.href); - const invitationToken = url.searchParams.get('ts'); - - if (invitationToken) { - // This user came here with an invitation token, which can be sent to the email to make - // the user a collaborator - if (!state.hasLogIn) { - // Redirect to sign in URL, which then redirects back to this after - history.push(signInPageUrl(document.location.href)); - return; - } - - try { - await effects.gql.mutations.redeemSandboxInvitation({ - sandboxId: newId, - invitationToken, - }); - - // Timeout to prevent that we load the whole sandbox twice at the same time - setTimeout(() => { - // Remove the invite from the url - url.searchParams.delete('ts'); - history.replace(url.pathname); - }, 3000); - } catch (error) { - if ( - !error.message.includes('Cannot redeem token, invitation not found') - ) { - actions.internal.handleError({ - error, - message: 'Something went wrong with redeeming invitation token', - }); - } - } - } - - try { - const params = state.activeTeam - ? { teamId: state.activeTeam } - : undefined; - const sandbox = await effects.api.getSandbox(newId, params); - - // Failsafe, in case someone types in the URL to load a v2 sandbox in v1 - // or if they have the experimental v2 editor enabled - if (sandbox.v2 || (!sandbox.isSse && hasBetaEditorExperiment)) { - // Only pass githubInfo for the URL if the URL in v1 is based on GH - const githubInfoForURL = - sandbox.git && url.pathname.startsWith('/s/github') - ? sandbox.git - : undefined; - - const sandboxV2Url = sandboxUrl( - { - id: sandbox.id, - alias: sandbox.alias, - git: githubInfoForURL, - isV2: sandbox.v2, - isSse: sandbox.isSse, - query: url.searchParams, - }, - hasBetaEditorExperiment - ); - - window.location.href = sandboxV2Url; - } - - actions.internal.setCurrentSandbox(sandbox); - } catch (error) { - const data = error.response?.data; - const errors = data?.errors; - let detail = errors?.detail; - - if (Array.isArray(detail)) { - detail = detail[0]; - } else if (typeof errors === 'object') { - detail = errors[Object.keys(errors)[0]]; - } else if (data?.error) { - detail = data?.error; - } - - state.editor.error = { - message: detail || error.message, - status: error.response.status, - }; - - state.editor.isLoading = false; - - return; - } - - const sandbox = state.editor.currentSandbox!; - - await effects.vscode.changeSandbox(sandbox, fs => { - state.editor.modulesByPath = fs; - }); - - if (sandbox.featureFlags?.containerLsp && !sandbox.owned) { - effects.vscode.setReadOnly(true); - effects.notificationToast.add({ - message: - 'This Sandbox is running an experiment. You have to fork it before you can make any changes', - title: 'Experimental Sandbox', - status: convertTypeToStatus('notice'), - sticky: true, - actions: { - primary: { - label: 'Fork', - run: () => { - actions.editor.forkSandboxClicked({}); - }, - }, - }, - }); - } - - await actions.editor.internal.initializeSandbox(sandbox); - - // We only recover files at this point if we are not live. When live we recover them - // when the module_state is received - if ( - !state.live.isLive && - hasPermission(sandbox.authorization, 'write_code') - ) { - actions.files.internal.recoverFiles(); - } - - if (state.editor.currentModule.id) { - effects.vscode.openModule(state.editor.currentModule); - } else { - // This means that there is no current module. Normally we tell that the editor has loaded - // when the module has loaded (for the skeleton), but in this case we will never load a module. - // So we need to explicitly mark that the module has been loaded instead. - state.editor.hasLoadedInitialModule = true; - } - try { - await actions.editor.loadCursorFromUrl(); - } catch (e) { - /** - * This is not extremely important logic, if it breaks (which is possible because of user input) - * we don't want to crash the whole editor. That's why we try...catch this. - */ - } - - if ( - sandbox.featureFlags.comments && - hasPermission(sandbox.authorization, 'comment') - ) { - actions.comments.getSandboxComments(sandbox.id); - } - - actions.git.loadGitSource(); - - state.editor.isLoading = false; - } -); - -export const contentMounted = ({ state, effects }: Context) => { - effects.browser.onUnload(event => { - if (!state.editor.isAllModulesSynced && !state.editor.isForkingSandbox) { - const returnMessage = - 'You have not saved all your modules, are you sure you want to close this tab?'; - - event.returnValue = returnMessage; // eslint-disable-line - - return returnMessage; - } - - return null; - }); -}; - -export const resizingStarted = ({ state }: Context) => { - state.editor.isResizing = true; -}; - -export const resizingStopped = ({ state }: Context) => { - state.editor.isResizing = false; -}; - -export const codeSaved = withOwnedSandbox( - async ( - { state, actions }: Context, - { - code, - moduleShortid, - cbID, - }: { - code: string; - moduleShortid: string; - cbID: string | null; - } - ) => { - actions.editor.internal.saveCode({ - code, - moduleShortid, - cbID, - }); - }, - async ({ effects }, { cbID }) => { - if (cbID) { - effects.vscode.callCallbackError(cbID); - } - }, - 'write_code' -); - -export const onOperationApplied = ( - { state, effects, actions }: Context, - { - code, - moduleShortid, - operation, - }: { - moduleShortid: string; - operation: TextOperation; - code: string; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - const module = state.editor.currentSandbox.modules.find( - m => m.shortid === moduleShortid - ); - - if (!module) { - return; - } - - actions.comments.transposeComments({ module, operation }); - - actions.editor.internal.updateModuleCode({ - module, - code, - }); - - if (state.preferences.settings.livePreviewEnabled) { - actions.editor.internal.updatePreviewCode(); - } - - // If we are in a state of sync, we set "revertModule" to set it as saved - if (module.savedCode !== null && module.code === module.savedCode) { - effects.vscode.syncModule(module); - } - - if (state.editor.currentSandbox.originalGit) { - actions.git.updateGitChanges(); - } -}; - -/** - * Set the code of the module and send it if live is turned on. Keep in mind that this overwrites the editor state, - * which means that if the user was typing something else in the file, it will get overwritten(!). - * - * There is some extra logic to handle files that are opened and not opened. If the file is opened we will set the code - * within VSCode and let the event that VSCode generates handle the rest, however, if the file is not opened in VSCode, - * we'll just update it in the state and send a live message based on the diff. - * - * The difference between `setCode` and `codeChanged` is small but important to keep in mind. Calling this method will *always* - * cause `codeChanged` to be called. But from different sources based on whether the file is currently open. I'd recommend to always - * call this function if you're aiming to manually set code (like updating package.json), while editors shouild call codeChanged. - * - * The two cases: - * - * ### Already opened in VSCode - * 1. set code in VSCode - * 2. which generates an event - * 3. which triggers codeChanged - * - * ### Not opened in VSCode - * 1. codeChanged called directly - */ -export const setCode = ( - { effects, state, actions }: Context, - { - code, - moduleShortid, - }: { - moduleShortid: string; - code: string; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - const module = state.editor.currentSandbox.modules.find( - m => m.shortid === moduleShortid - ); - - if (!module) { - return; - } - - // If the code is opened in VSCode, change the code in VSCode and let - // other clients know via the event triggered by VSCode. Otherwise - // send a manual event and just change the state. - if (effects.vscode.isModuleOpened(module)) { - effects.vscode.setModuleCode({ ...module, code }, true); - } else { - actions.editor.codeChanged({ moduleShortid, code }); - } -}; - -export const codeChanged = ( - { effects, state, actions }: Context, - { - code, - event, - moduleShortid, - }: { - moduleShortid: string; - code: string; - event?: any; - } -) => { - effects.analytics.trackWithCooldown('Change Code', 30 * 1000); - - if (!state.editor.currentSandbox) { - return; - } - - const module = state.editor.currentSandbox.modules.find( - m => m.shortid === moduleShortid - ); - - if (!module) { - return; - } - - const savedCode = getSavedCode(module.code, module.savedCode); - const isSavedCode = savedCode === code; - const isFirstChange = - !effects.live.hasClient(module.shortid) || - (effects.live.getClient(moduleShortid).revision === 0 && - effects.live.getClient(moduleShortid).state.name === 'Synchronized'); - - // Don't send saved code of a moduke that has not been registered with yet, since the server - // will take the saved code as base. Which means that the change that would generate the saved code - // would be applied on the saved code by the server. - if (state.live.isLive && !(isSavedCode && isFirstChange)) { - let operation: TextOperation; - if (event) { - logBreadcrumb({ - category: 'ot', - message: `Change Event ${JSON.stringify({ - moduleShortid: module.shortid, - event, - })}`, - }); - operation = eventToTransform(event, module.code).operation; - } else { - operation = getTextOperation(module.code, code); - } - - effects.live.sendCodeUpdate(moduleShortid, operation); - - actions.comments.transposeComments({ module, operation }); - } - - actions.editor.internal.updateModuleCode({ - module, - code, - }); - - if (module.savedCode !== null && module.code === module.savedCode) { - effects.vscode.syncModule(module); - } - - const { isServer } = getTemplate(state.editor.currentSandbox.template); - - if (!isServer && state.preferences.settings.livePreviewEnabled) { - actions.editor.internal.updatePreviewCode(); - } - - if (state.editor.currentSandbox.originalGit) { - actions.git.updateGitChanges(); - actions.git.resolveConflicts(module); - } -}; - -export const saveClicked = withOwnedSandbox( - async ({ state, effects, actions }: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - try { - const changedModules = sandbox.modules.filter(module => - state.editor.changedModuleShortids.includes(module.shortid) - ); - - if (sandbox.featureFlags.comments) { - const versions = await Promise.all( - changedModules.map(module => - effects.live - .saveModule(module) - .then(({ saved_code, updated_at, inserted_at, version }) => { - module.savedCode = saved_code; - module.updatedAt = updated_at; - module.insertedAt = inserted_at; - - return version; - }) - ) - ); - sandbox.version = Math.max(...versions); - } else { - const updatedModules = await effects.api.saveModules( - sandbox.id, - changedModules - ); - - updatedModules.forEach(updatedModule => { - const module = sandbox.modules.find( - moduleItem => moduleItem.shortid === updatedModule.shortid - ); - - if (module) { - module.insertedAt = updatedModule.insertedAt; - module.updatedAt = updatedModule.updatedAt; - - module.savedCode = - updatedModule.code === module.code ? null : updatedModule.code; - - effects.vscode.sandboxFsSync.writeFile( - state.editor.modulesByPath, - module - ); - effects.moduleRecover.remove(sandbox.id, module); - } else { - // We might not have the module, as it was created by the server. In - // this case we put it in. There is an edge case here where the user - // might delete the module while it is being updated, but it will very - // likely not happen - sandbox.modules.push(updatedModule); - } - }); - } - - effects.preview.executeCodeImmediately(); - } catch (error) { - actions.internal.handleError({ - message: 'There was a problem with saving the files, please try again', - error, - }); - } - } -); - -export const createZipClicked = ({ state, effects }: Context) => { - if (!state.editor.currentSandbox) { - return; - } - - if (state.editor.currentSandbox.permissions.preventSandboxExport) { - effects.notificationToast.error( - 'You do not permission to export this sandbox' - ); - return; - } - - effects.zip.download(state.editor.currentSandbox); - - effects.analytics.track('Editor - Click Menu Item - Export as ZIP'); -}; - -// The forkExternalSandbox only seems to be used inside of the dashboard pages and -// that seems to be the reason why it's called external. -// TODO: Move the fork function to the dashboard namespace. -export const forkExternalSandbox = async ( - { effects, state, actions }: Context, - { - sandboxId, - openInNewWindow, - openInVSCode, - autoLaunchVSCode, - hasBetaEditorExperiment, - customVMTier, - body, - redirectAfterFork, - }: { - sandboxId: string; - openInNewWindow?: boolean; - openInVSCode?: boolean; - autoLaunchVSCode?: boolean; - hasBetaEditorExperiment?: boolean; - customVMTier?: number; - redirectAfterFork?: boolean; - body?: { - collectionId?: string; - alias?: string; - v2?: boolean; - title?: string; - privacy?: 0 | 1 | 2; - }; - } -) => { - effects.analytics.track('Fork Sandbox', { type: 'external', sandboxId }); - - const usedBody: ForkSandboxBody = body || {}; - - if (state.activeTeam) { - usedBody.teamId = state.activeTeam; - } - - try { - const forkedSandbox = await effects.api.forkSandbox(sandboxId, usedBody); - - if (customVMTier) { - await effects.api.setVMSpecs(forkedSandbox.id, customVMTier); - } - - if (openInVSCode) { - if (autoLaunchVSCode) { - window.open(vsCodeUrl(forkedSandbox.id)); - } else { - window.open(vsCodeLauncherUrl(forkedSandbox.id)); - } - } else { - state.editor.sandboxes[forkedSandbox.id] = forkedSandbox; - - if (redirectAfterFork) { - effects.router.updateSandboxUrl(forkedSandbox, { - openInNewWindow, - hasBetaEditorExperiment, - }); - } - } - - return forkedSandbox; - } catch (error) { - const errorMessage = actions.internal.getErrorMessage({ error }); - - if (errorMessage.includes('DRAFT_LIMIT')) { - effects.notificationToast.add({ - title: 'Cannot create draft', - message: - 'Your drafts folder is full. Delete unneeded drafts, or upgrade to Pro for unlimited drafts.', - status: NotificationStatus.ERROR, - }); - } else { - actions.internal.handleError({ - message: 'We were unable to fork the sandbox', - error, - }); - } - } - - return undefined; -}; - -// TODO: Look into -export const forkSandboxClicked = async ( - { state, actions }: Context, - { - teamId, - }: { - teamId?: string | null; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - await actions.editor.internal.forkSandbox({ - sandboxId: state.editor.currentSandbox.id, - teamId, - }); -}; - -export const likeSandboxToggled = async ( - { state, effects }: Context, - id: string -) => { - if (state.editor.sandboxes[id].userLiked) { - state.editor.sandboxes[id].userLiked = false; - state.editor.sandboxes[id].likeCount--; - await effects.api.unlikeSandbox(id); - effects.analytics.track('Sandbox - Like', { place: 'EDITOR' }); - } else { - state.editor.sandboxes[id].userLiked = true; - state.editor.sandboxes[id].likeCount++; - await effects.api.likeSandbox(id); - effects.analytics.track('Sandbox - Undo Like', { place: 'EDITOR' }); - } -}; - -export const moduleSelected = async ( - { actions, effects, state }: Context, - { - id, - path, - }: - | { - // Id means it is coming from Explorer - id: string; - path?: undefined; - } - | { - // Path means it is coming from VSCode - id?: undefined; - path: string; - } -) => { - effects.analytics.track('Open File'); - - state.editor.hasLoadedInitialModule = true; - - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - try { - const module = path - ? effects.utils.resolveModule( - path.replace(/^\/sandbox\//, ''), - sandbox.modules, - sandbox.directories - ) - : sandbox.modules.filter(moduleItem => moduleItem.id === id)[0]; - - if (module.shortid === state.editor.currentModuleShortid && path) { - // If this comes from VSCode we can return, but if this call comes from CodeSandbox - // we shouldn't return, since the promise would resolve sooner than VSCode loaded - // the file - return; - } - - await actions.editor.internal.setCurrentModule(module); - - actions.editor.persistCursorToUrl({ module }); - - if (state.live.isLive && state.live.liveUser && state.live.roomInfo) { - actions.editor.internal.updateSelectionsOfModule({ module }); - state.live.liveUser.currentModuleShortid = module.shortid; - - if (state.live.followingUserId) { - const followingUser = state.live.roomInfo.users.find( - u => u.id === state.live.followingUserId - ); - - if ( - followingUser && - followingUser.currentModuleShortid !== module.shortid - ) { - // Reset following as this is a user change module action - actions.live.onStopFollow(); - } - } - - effects.live.sendUserCurrentModule(module.shortid); - - if (!state.editor.isInProjectView) { - actions.editor.internal.updatePreviewCode(); - } - } - } catch (error) { - // You jumped to a file not in the Sandbox, for example typings - state.editor.currentModuleShortid = null; - } -}; - -export const clearModuleSelected = ({ state }: Context) => { - state.editor.currentModuleShortid = null; -}; - -export const moduleDoubleClicked = ({ state, effects }: Context) => { - effects.vscode.runCommand('workbench.action.keepEditor'); - - const { currentModule } = state.editor; - const tabs = state.editor.tabs as ModuleTab[]; - const tab = tabs.find( - tabItem => tabItem.moduleShortid === currentModule.shortid - ); - - if (tab) { - tab.dirty = false; - } -}; - -export const tabClosed = ({ state, actions }: Context, tabIndex: number) => { - if (state.editor.tabs.length > 1) { - actions.internal.closeTabByIndex(tabIndex); - } -}; - -export const tabMoved = ( - { state }: Context, - { - prevIndex, - nextIndex, - }: { - prevIndex: number; - nextIndex: number; - } -) => { - const { tabs } = state.editor; - const tab = json(tabs[prevIndex]); - - state.editor.tabs.splice(prevIndex, 1); - state.editor.tabs.splice(nextIndex, 0, tab); -}; - -// TODO(@CompuIves): Look into whether we even want to call this function. -// We can probably call the dispatch from the bundler itself instead. -export const errorsCleared = ({ state, effects }: Context) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - if (state.editor.errors.length) { - state.editor.errors.forEach(error => { - try { - const module = resolveModule( - error.path, - sandbox.modules, - sandbox.directories - ); - module.errors = []; - } catch (e) { - // Module is probably somewhere in eg. /node_modules which is not - // in the store - } - }); - state.editor.errors = []; - effects.vscode.setErrors(state.editor.errors); - } -}; - -export const toggleStatusBar = ({ state }: Context) => { - state.editor.statusBar = !state.editor.statusBar; -}; - -export const projectViewToggled = ({ state, actions }: Context) => { - state.editor.isInProjectView = !state.editor.isInProjectView; - actions.editor.internal.updatePreviewCode(); -}; - -export const frozenUpdated = async ( - { effects, state }: Context, - frozen: boolean -) => { - if (!state.editor.currentSandbox) { - return; - } - - state.editor.currentSandbox.isFrozen = frozen; - - await effects.api.saveFrozen(state.editor.currentSandbox.id, frozen); -}; - -export const quickActionsOpened = ({ state }: Context) => { - state.editor.quickActionsOpen = true; -}; - -export const quickActionsClosed = ({ state }: Context) => { - state.editor.quickActionsOpen = false; -}; - -export const setPreviewContent = () => {}; - -export const togglePreviewContent = ({ state, effects }: Context) => { - state.editor.previewWindowVisible = !state.editor.previewWindowVisible; - effects.vscode.resetLayout(); -}; - -export const currentTabChanged = ( - { state }: Context, - { - tabId, - }: { - tabId: string; - } -) => { - state.editor.currentTabId = tabId; -}; - -export const discardModuleChanges = ( - { state, effects, actions }: Context, - { - moduleShortid, - }: { - moduleShortid: string; - } -) => { - effects.analytics.track('Code Discarded'); - - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - const module = sandbox.modules.find( - moduleItem => moduleItem.shortid === moduleShortid - ); - - if (!module) { - return; - } - - module.updatedAt = new Date().toString(); - effects.vscode.syncModule(module); -}; - -export const fetchEnvironmentVariables = async ({ - state, - effects, -}: Context) => { - if (!state.editor.currentSandbox) { - return; - } - - state.editor.currentSandbox.environmentVariables = await effects.api.getEnvironmentVariables( - state.editor.currentSandbox.id - ); -}; - -export const updateEnvironmentVariables = async ( - { effects, state }: Context, - environmentVariable: EnvironmentVariable -) => { - if (!state.editor.currentSandbox) { - return; - } - - state.editor.currentSandbox.environmentVariables = await effects.api.saveEnvironmentVariable( - state.editor.currentSandbox.id, - environmentVariable - ); - - effects.codesandboxApi.restartSandbox(); -}; - -export const deleteEnvironmentVariable = async ( - { effects, state }: Context, - name: string -) => { - if (!state.editor.currentSandbox) { - return; - } - - const { id } = state.editor.currentSandbox; - - state.editor.currentSandbox.environmentVariables = await effects.api.deleteEnvironmentVariable( - id, - name - ); - effects.codesandboxApi.restartSandbox(); -}; - -/** - * This will let the user know on fork that some secrets need to be set if there are any empty ones - */ -export const showEnvironmentVariablesNotification = async ({ - state, - actions, - effects, -}: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - await actions.editor.fetchEnvironmentVariables(); - - const environmentVariables = sandbox.environmentVariables!; - const emptyVarCount = Object.keys(environmentVariables).filter( - key => !environmentVariables[key] - ).length; - if (emptyVarCount > 0) { - effects.notificationToast.add({ - status: NotificationStatus.NOTICE, - title: 'Unset Secrets', - message: `This sandbox has ${emptyVarCount} secrets that need to be set. You can set them in the server tab.`, - actions: { - primary: { - label: 'Open Server Tab', - run: () => { - actions.workspace.setWorkspaceItem({ item: SERVER.id }); - }, - }, - }, - }); - } -}; - -export const onSelectionChanged = ( - { actions, state }: Context, - selection: UserSelection -) => { - if (!state.editor.currentModule) { - return; - } - - actions.editor.persistCursorToUrl({ - module: state.editor.currentModule, - selection, - }); -}; - -export const toggleEditorPreviewLayout = ({ state, effects }: Context) => { - const currentOrientation = state.editor.previewWindowOrientation; - - state.editor.previewWindowOrientation = - currentOrientation === WindowOrientation.VERTICAL - ? WindowOrientation.HORIZONTAL - : WindowOrientation.VERTICAL; - - effects.vscode.resetLayout(); -}; - -export const previewActionReceived = ( - { actions, effects, state }: Context, - action: any -) => { - switch (action.action) { - case 'notification': - effects.notificationToast.add({ - message: action.title, - status: action.notificationType, - timeAlive: action.timeAlive, - }); - break; - case 'show-error': { - if (!state.editor.currentSandbox) { - return; - } - const error: ModuleError = { - column: action.column, - line: action.line, - columnEnd: action.columnEnd, - lineEnd: action.lineEnd, - message: action.message, - title: action.title, - path: action.path, - source: action.source, - severity: action.severity, - type: action.type, - }; - try { - const module = resolveModule( - error.path, - state.editor.currentSandbox.modules, - state.editor.currentSandbox.directories - ); - - module.errors.push(json(error)); - state.editor.errors.push(error); - effects.vscode.setErrors(state.editor.errors); - } catch (e) { - /* ignore, this module can be in a node_modules for example */ - } - break; - } - case 'show-correction': { - if (!state.editor.currentSandbox) { - return; - } - const correction: ModuleCorrection = { - path: action.path, - column: action.column, - line: action.line, - columnEnd: action.columnEnd, - lineEnd: action.lineEnd, - message: action.message, - source: action.source, - severity: action.severity, - }; - try { - const module = resolveModule( - correction.path as string, - state.editor.currentSandbox.modules, - state.editor.currentSandbox.directories - ); - - state.editor.corrections.push(correction); - module.corrections.push(json(correction)); - effects.vscode.setCorrections(state.editor.corrections); - } catch (e) { - /* ignore, this module can be in a node_modules for example */ - } - break; - } - case 'clear-errors': { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const currentErrors = state.editor.errors; - - const newErrors = clearCorrectionsFromAction(currentErrors, action); - - if (newErrors.length !== currentErrors.length) { - state.editor.errors.forEach(error => { - try { - const module = resolveModule( - error.path, - sandbox.modules, - sandbox.directories - ); - - module.errors = []; - } catch (e) { - // Module doesn't exist anymore - } - }); - newErrors.forEach(error => { - const module = resolveModule( - error.path, - sandbox.modules, - sandbox.directories - ); - - module.errors.push(error); - }); - state.editor.errors = newErrors; - effects.vscode.setErrors(state.editor.errors); - } - break; - } - case 'clear-corrections': { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - const currentCorrections = state.editor.corrections; - - const newCorrections = clearCorrectionsFromAction( - currentCorrections, - action - ); - - if (newCorrections.length !== currentCorrections.length) { - state.editor.corrections.forEach(correction => { - try { - const module = resolveModule( - correction.path!, - sandbox.modules, - sandbox.directories - ); - - module.corrections = []; - } catch (e) { - // Module is probably in node_modules or something, which is not in - // our store - } - }); - newCorrections.forEach(correction => { - const module = resolveModule( - correction.path!, - sandbox.modules, - sandbox.directories - ); - - module.corrections.push(correction); - }); - state.editor.corrections = newCorrections; - effects.vscode.setCorrections(state.editor.corrections); - } - break; - } - case 'source.module.rename': { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const module = effects.utils.resolveModule( - action.path.replace(/^\//, ''), - sandbox.modules, - sandbox.directories - ); - - if (module) { - const sandboxModule = sandbox.modules.find( - moduleEntry => moduleEntry.shortid === module.shortid - ); - - if (sandboxModule) { - sandboxModule.title = action.title; - } - } - break; - } - case 'source.dependencies.add': { - const name = action.dependency; - actions.editor.addNpmDependency({ - name, - }); - break; - } - } -}; - -export const renameModule = withOwnedSandbox( - async ( - { state, actions, effects }: Context, - { - title, - moduleShortid, - }: { - title: string; - moduleShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const module = sandbox.modules.find( - moduleItem => moduleItem.shortid === moduleShortid - ); - - if (!module) { - return; - } - - const oldTitle = module.title; - - module.title = title; - - try { - await effects.api.saveModuleTitle(sandbox.id, moduleShortid, title); - - if (state.live.isCurrentEditor) { - effects.live.sendModuleUpdate(module); - } - } catch (error) { - module.title = oldTitle; - - actions.internal.handleError({ message: 'Could not rename file', error }); - } - } -); - -export const onDevToolsTabAdded = ( - { state, actions }: Context, - { - tab, - }: { - tab: any; - } -) => { - const { devToolTabs } = state.editor; - const { devTools: newDevToolTabs, position } = addDevToolsTabUtil( - json(devToolTabs), - tab - ); - const nextPos = position; - - actions.editor.internal.updateDevtools(newDevToolTabs); - - state.editor.currentDevToolsPosition = nextPos; -}; - -export const onDevToolsTabMoved = ( - { state, actions }: Context, - { - prevPos, - nextPos, - }: { - prevPos: any; - nextPos: any; - } -) => { - const { devToolTabs } = state.editor; - const newDevToolTabs = moveDevToolsTabUtil( - json(devToolTabs), - prevPos, - nextPos - ); - - actions.editor.internal.updateDevtools(newDevToolTabs); - - state.editor.currentDevToolsPosition = nextPos; -}; - -export const onDevToolsTabClosed = ( - { state, actions }: Context, - { - pos, - }: { - pos: any; - } -) => { - const { devToolTabs } = state.editor; - const closePos = pos; - const newDevToolTabs = closeDevToolsTabUtil(json(devToolTabs), closePos); - - actions.editor.internal.updateDevtools(newDevToolTabs); -}; - -export const onDevToolsPositionChanged = ( - { state }: Context, - { - position, - }: { - position: any; - } -) => { - state.editor.currentDevToolsPosition = position; -}; - -export const openDevtoolsTab = ( - { state, actions }: Context, - { - tab: tabToFind, - }: { - tab: any; - } -) => { - const serializedTab = JSON.stringify(tabToFind); - const { devToolTabs } = state.editor; - let nextPos; - - for (let i = 0; i < devToolTabs.length; i++) { - const view = devToolTabs[i]; - - for (let j = 0; j < view.views.length; j++) { - const tab = view.views[j]; - if (JSON.stringify(tab) === serializedTab) { - nextPos = { - devToolIndex: i, - tabPosition: j, - }; - } - } - } - - if (nextPos) { - state.editor.currentDevToolsPosition = nextPos; - } else { - actions.editor.onDevToolsTabAdded({ - tab: tabToFind, - }); - } -}; - -export const sessionFreezeOverride = ({ state }: Context, frozen: boolean) => { - state.editor.sessionFrozen = frozen; -}; - -export const listenToSandboxChanges = async ( - { state, actions, effects }: Context, - { - sandboxId, - }: { - sandboxId: string; - } -) => { - effects.gql.subscriptions.onSandboxChangged.dispose(); - - if (!state.isLoggedIn) { - return; - } - - effects.gql.subscriptions.onSandboxChangged({ sandboxId }, result => { - const sandbox = state.editor.sandboxes[sandboxId]; - - if (sandbox) { - const newInfo = result.sandboxChanged; - sandbox.privacy = newInfo.privacy as 0 | 1 | 2; - - const authorization = convertAuthorizationToPermissionType( - newInfo.authorization - ); - - if (authorization !== sandbox.authorization) { - sandbox.authorization = authorization; - - actions.refetchSandboxInfo(); - } - } - }); -}; - -export const loadCollaborators = async ( - { state, effects }: Context, - { sandboxId }: { sandboxId: string } -) => { - if (!state.editor.currentSandbox || !state.isLoggedIn) { - return; - } - - effects.gql.subscriptions.onCollaboratorAdded.dispose(); - effects.gql.subscriptions.onCollaboratorRemoved.dispose(); - effects.gql.subscriptions.onCollaboratorChanged.dispose(); - effects.gql.subscriptions.onInvitationCreated.dispose(); - effects.gql.subscriptions.onInvitationRemoved.dispose(); - effects.gql.subscriptions.onInvitationChanged.dispose(); - - try { - const collaboratorResponse = await effects.gql.queries.collaborators({ - sandboxId, - }); - if (!collaboratorResponse.sandbox) { - return; - } - - state.editor.collaborators = collaboratorResponse.sandbox.collaborators; - - effects.gql.subscriptions.onCollaboratorAdded({ sandboxId }, event => { - const newCollaborator = event.collaboratorAdded; - state.editor.collaborators = [ - ...state.editor.collaborators.filter( - c => c.user.username !== event.collaboratorAdded.user.username - ), - newCollaborator, - ]; - }); - - effects.gql.subscriptions.onCollaboratorChanged({ sandboxId }, event => { - const existingCollaborator = state.editor.collaborators.find( - c => c.user.username === event.collaboratorChanged.user.username - ); - if (existingCollaborator) { - existingCollaborator.authorization = - event.collaboratorChanged.authorization; - - existingCollaborator.warning = event.collaboratorChanged.warning; - existingCollaborator.lastSeenAt = event.collaboratorChanged.lastSeenAt; - } - }); - - effects.gql.subscriptions.onCollaboratorRemoved({ sandboxId }, event => { - state.editor.collaborators = state.editor.collaborators.filter( - c => c.user.username !== event.collaboratorRemoved.user.username - ); - }); - - if (hasPermission(state.editor.currentSandbox.authorization, 'owner')) { - const invitationResponse = await effects.gql.queries.invitations({ - sandboxId, - }); - const sandbox = invitationResponse.sandbox; - if (!sandbox) { - return; - } - - state.editor.invitations = sandbox.invitations; - - effects.gql.subscriptions.onInvitationCreated({ sandboxId }, event => { - if (event.invitationCreated.id === null) { - // Ignore this - return; - } - - const newInvitation = event.invitationCreated; - state.editor.invitations = [ - ...state.editor.invitations.filter( - i => i.id !== event.invitationCreated.id - ), - newInvitation, - ]; - }); - - effects.gql.subscriptions.onInvitationChanged({ sandboxId }, event => { - const existingInvitation = state.editor.invitations.find( - i => i.id === event.invitationChanged.id - ); - if (existingInvitation) { - existingInvitation.authorization = - event.invitationChanged.authorization; - } - }); - - effects.gql.subscriptions.onInvitationRemoved({ sandboxId }, event => { - state.editor.invitations = state.editor.invitations.filter( - i => i.id !== event.invitationRemoved.id - ); - }); - } - } catch { - // Unable to connect, not sure what to do here - } -}; - -export const changeCollaboratorAuthorization = async ( - { state, effects }: Context, - { - username, - authorization, - sandboxId, - }: { - username: string; - authorization: Authorization; - sandboxId: string; - } -) => { - effects.analytics.track('Update Collaborator Authorization', { - authorization, - }); - - const existingCollaborator = state.editor.collaborators.find( - c => c.user.username === username - ); - - let oldAuthorization: Authorization | null = null; - if (existingCollaborator) { - oldAuthorization = existingCollaborator.authorization; - - existingCollaborator.authorization = authorization; - } - - try { - await effects.gql.mutations.changeCollaboratorAuthorization({ - sandboxId, - authorization, - username, - }); - } catch (e) { - if (existingCollaborator && oldAuthorization) { - existingCollaborator.authorization = oldAuthorization; - } - } -}; - -export const addCollaborator = async ( - { state, effects }: Context, - { - username, - sandboxId, - authorization, - }: { - username: string; - sandboxId: string; - authorization: Authorization; - } -) => { - effects.analytics.track('Add Collaborator', { authorization }); - const newCollaborator: CollaboratorFragment = { - lastSeenAt: null, - id: 'OPTIMISTIC_ID', - authorization, - user: { - id: 'OPTIMISTIC_USER_ID', - username, - avatarUrl: '', - }, - warning: null, - }; - - state.editor.collaborators = [...state.editor.collaborators, newCollaborator]; - - try { - const result = await effects.gql.mutations.addCollaborator({ - sandboxId, - username, - authorization, - }); - state.editor.collaborators = [ - ...state.editor.collaborators.filter(c => c.user.username !== username), - result.addCollaborator, - ]; - } catch (e) { - state.editor.collaborators = state.editor.collaborators.filter( - c => c.id !== 'OPTIMISTIC_ID' - ); - } -}; - -export const removeCollaborator = async ( - { state, effects }: Context, - { - username, - sandboxId, - }: { - username: string; - sandboxId: string; - } -) => { - effects.analytics.track('Remove Collaborator'); - const existingCollaborator = state.editor.collaborators.find( - c => c.user.username === username - ); - - state.editor.collaborators = state.editor.collaborators.filter( - c => c.user.username !== username - ); - - try { - await effects.gql.mutations.removeCollaborator({ - sandboxId, - username, - }); - } catch (e) { - if (existingCollaborator) { - state.editor.collaborators = [ - ...state.editor.collaborators, - existingCollaborator, - ]; - } - } -}; - -export const inviteCollaborator = async ( - { state, effects }: Context, - { - email, - sandboxId, - authorization, - }: { - email: string; - sandboxId: string; - authorization: Authorization; - } -) => { - effects.analytics.track('Invite Collaborator (Email)', { authorization }); - - const newInvitation: InvitationFragment = { - id: 'OPTIMISTIC_ID', - authorization, - email, - }; - - state.editor.invitations = [...state.editor.invitations, newInvitation]; - - try { - const result = await effects.gql.mutations.inviteCollaborator({ - sandboxId, - email, - authorization, - }); - - if (result.createSandboxInvitation.id === null) { - // When it's null it has already tied the email to a collaborator, and the collaborator - // has been added via websockets - state.editor.invitations = state.editor.invitations.filter( - c => c.id !== 'OPTIMISTIC_ID' - ); - } else { - state.editor.invitations = [ - ...state.editor.invitations.filter( - c => - c.id !== 'OPTIMISTIC_ID' && - c.id !== result.createSandboxInvitation.id - ), - - result.createSandboxInvitation, - ]; - } - } catch (e) { - state.editor.invitations = state.editor.invitations.filter( - c => c.id !== 'OPTIMISTIC_ID' - ); - } -}; - -export const revokeSandboxInvitation = async ( - { state, effects }: Context, - { - invitationId, - sandboxId, - }: { - invitationId: string; - sandboxId: string; - } -) => { - effects.analytics.track('Cancel Invite Collaborator (Email)'); - - const existingInvitation = state.editor.invitations.find( - c => c.id === invitationId - ); - - state.editor.invitations = state.editor.invitations.filter( - c => c.id !== invitationId - ); - - try { - await effects.gql.mutations.revokeInvitation({ - sandboxId, - invitationId, - }); - } catch (e) { - if (existingInvitation) { - state.editor.invitations = [ - ...state.editor.invitations, - existingInvitation, - ]; - } - } -}; - -export const changeInvitationAuthorization = async ( - { state, effects }: Context, - { - invitationId, - sandboxId, - authorization, - }: { - invitationId: string; - authorization: Authorization; - sandboxId: string; - } -) => { - const existingInvitation = state.editor.invitations.find( - c => c.id === invitationId - ); - - let oldAuthorization: Authorization | null = null; - if (existingInvitation) { - oldAuthorization = existingInvitation.authorization; - - existingInvitation.authorization = authorization; - } - - try { - await effects.gql.mutations.changeSandboxInvitationAuthorization({ - sandboxId, - authorization, - invitationId, - }); - } catch (e) { - if (existingInvitation && oldAuthorization) { - existingInvitation.authorization = oldAuthorization; - } - } -}; - -export const setPreventSandboxLeaving = async ( - { effects, state }: Context, - preventSandboxLeaving: boolean -) => { - if (!state.editor.currentSandbox) return; - - // optimistic update - const oldValue = - state.editor.currentSandbox.permissions.preventSandboxLeaving; - state.editor.currentSandbox.permissions.preventSandboxLeaving = preventSandboxLeaving; - - effects.analytics.track(`Editor - Change sandbox permissions`, { - preventSandboxLeaving, - }); - - try { - await effects.gql.mutations.setPreventSandboxesLeavingWorkspace({ - sandboxIds: [state.editor.currentSandbox.id], - preventSandboxLeaving, - }); - } catch (error) { - state.editor.currentSandbox.permissions.preventSandboxLeaving = oldValue; - effects.notificationToast.error( - 'There was a problem updating your sandbox permissions' - ); - } -}; - -export const setPreventSandboxExport = async ( - { effects, state }: Context, - preventSandboxExport: boolean -) => { - if (!state.editor.currentSandbox) return; - - // optimistic update - const oldValue = state.editor.currentSandbox.permissions.preventSandboxExport; - state.editor.currentSandbox.permissions.preventSandboxExport = preventSandboxExport; - - effects.analytics.track(`Editor - Change sandbox permissions`, { - preventSandboxExport, - }); - - try { - await effects.gql.mutations.setPreventSandboxesExport({ - sandboxIds: [state.editor.currentSandbox.id], - preventSandboxExport, - }); - } catch (error) { - state.editor.currentSandbox.permissions.preventSandboxExport = oldValue; - effects.notificationToast.error( - 'There was a problem updating your sandbox permissions' - ); - } -}; diff --git a/packages/app/src/app/overmind/namespaces/editor/index.ts b/packages/app/src/app/overmind/namespaces/editor/index.ts deleted file mode 100755 index dd0715cd945..00000000000 --- a/packages/app/src/app/overmind/namespaces/editor/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as actions from './actions'; -import { state } from './state'; - -export { state, actions }; diff --git a/packages/app/src/app/overmind/namespaces/editor/internalActions.ts b/packages/app/src/app/overmind/namespaces/editor/internalActions.ts deleted file mode 100755 index c112fbb71a6..00000000000 --- a/packages/app/src/app/overmind/namespaces/editor/internalActions.ts +++ /dev/null @@ -1,640 +0,0 @@ -import getTemplateDefinition, { - TemplateType, -} from '@codesandbox/common/lib/templates'; -import { ViewConfig } from '@codesandbox/common/lib/templates/template'; -import { - Module, - ModuleTab, - Sandbox, - ServerContainerStatus, - TabType, -} from '@codesandbox/common/lib/types'; -import { captureException } from '@codesandbox/common/lib/utils/analytics/sentry'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import slugify from '@codesandbox/common/lib/utils/slugify'; -import { - editorUrl, - sandboxUrl, -} from '@codesandbox/common/lib/utils/url-generator'; -import { NotificationStatus } from '@codesandbox/notifications'; -import { Context } from 'app/overmind'; -import { sortObjectByKeys } from 'app/overmind/utils/common'; -import { getTemplate as computeTemplate } from 'codesandbox-import-utils/lib/create-sandbox/templates'; -import { mapValues } from 'lodash-es'; - -export const ensureSandboxId = ({ state }: Context, id: string) => { - if (state.editor.sandboxes[id]) { - return id; - } - - const { sandboxes } = state.editor; - const matchingSandboxId = Object.keys(sandboxes).find( - // @ts-ignore - idItem => sandboxUrl(sandboxes[idItem]) === `${editorUrl()}${id}` - ); - - return matchingSandboxId || id; -}; - -export const initializeSandbox = async ( - { actions }: Context, - sandbox: Sandbox -) => { - await Promise.all([ - actions.editor.internal.initializeLiveSandbox(sandbox), - actions.editor.loadCollaborators({ sandboxId: sandbox.id }), - actions.editor.listenToSandboxChanges({ sandboxId: sandbox.id }), - actions.internal.switchCurrentWorkspaceBySandbox({ sandbox }), - actions.getSandboxesLimits(), - ]); -}; - -export const initializeLiveSandbox = async ( - { state, actions }: Context, - sandbox: Sandbox -) => { - state.live.isTeam = Boolean(sandbox.team); - - if (state.live.isLive && state.live.roomInfo) { - const roomChanged = state.live.roomInfo.roomId !== sandbox.roomId; - - if (!roomChanged) { - // In this case we don't need to initialize new live session, we reuse the existing one - return; - } - - if ( - // If the joinSource is /live/ and the user is only a viewer, - // we won't get the roomId and the user will disconnect automatically, - // we want to prevent this by only re-initializing the live session on joinSource === 'sandbox'. Because - // for joinSource === 'sandbox' we know for sure that the user will get a roomId if they have permissions - // to join - state.live.joinSource === 'sandbox' - ) { - actions.live.internal.disconnect(); - } - } - - if (sandbox.roomId) { - await actions.live.internal.initialize(sandbox.roomId); - } -}; - -export const updateSelectionsOfModule = async ( - { actions, effects }: Context, - { module }: { module: Module } -) => { - effects.vscode.updateUserSelections( - module, - actions.live.internal.getSelectionsForModule(module) - ); -}; - -export const setModuleSavedCode = ( - { state }: Context, - { - moduleShortid, - savedCode, - }: { - moduleShortid: string; - savedCode: string | null; - } -) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - const moduleIndex = sandbox.modules.findIndex( - m => m.shortid === moduleShortid - ); - - if (moduleIndex > -1) { - const module = sandbox.modules[moduleIndex]; - - module.savedCode = module.code === savedCode ? null : savedCode; - } -}; - -export const saveCode = async ( - { state, effects, actions }: Context, - { - code, - moduleShortid, - cbID, - }: { - code: string; - moduleShortid: string; - cbID?: string | null; - } -) => { - effects.analytics.track('Save Code'); - - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - const module = sandbox.modules.find(m => m.shortid === moduleShortid); - - if (!module) { - return; - } - - effects.preview.executeCodeImmediately(); - - try { - let updatedModule: { - updatedAt: string; - insertedAt: string; - code: string; - isBinary: boolean; - }; - if (sandbox.featureFlags.comments) { - await effects.live.waitForLiveReady(); - const { - saved_code, - updated_at, - inserted_at, - } = await effects.live.saveModule(module); - - updatedModule = { - code: saved_code, - updatedAt: updated_at, - insertedAt: inserted_at, - isBinary: false, - }; - } else { - updatedModule = await effects.api.saveModuleCode( - sandbox.id, - module.shortid, - code - ); - } - - module.insertedAt = updatedModule.insertedAt; - module.updatedAt = updatedModule.updatedAt; - module.isBinary = updatedModule.isBinary; - - if (!effects.vscode.isModuleOpened(module)) { - module.code = updatedModule.code; - } - const savedCode = - updatedModule.code === module.code ? null : updatedModule.code; - - module.savedCode = savedCode; - - if (savedCode === null) { - // If the savedCode is also module.code - effects.moduleRecover.remove(sandbox.id, module); - effects.vscode.syncModule(module); - } - - if ( - state.live.isLive && - state.live.isCurrentEditor && - !sandbox.featureFlags.comments - ) { - setTimeout(() => { - // Send the save event 50ms later so the operation can be sent first (the operation that says the - // file difference created by VSCode due to the file watch event). If the other client gets the save before the operation, - // the other client will also send an operation with the same difference resulting in a duplicate event. - effects.live.sendModuleSaved(module); - }, 50); - } - - effects.vscode.sandboxFsSync.writeFile(state.editor.modulesByPath, module); - - if (cbID) { - effects.vscode.callCallback(cbID); - } - - // If the executor is a server we only should send updates if the sandbox has been - // started already - if ( - !effects.executor.isServer() || - state.server.containerStatus === ServerContainerStatus.SANDBOX_STARTED - ) { - effects.executor.updateFiles(sandbox); - } - - if (sandbox.template === 'static') { - effects.preview.refresh(); - } - - await actions.editor.internal.updateCurrentTemplate(); - - effects.vscode.runCommand('workbench.action.keepEditor'); - } catch (error) { - actions.internal.handleError({ - message: 'There was a problem with saving the code, please try again', - error, - }); - captureException(error); - - if (cbID) { - effects.vscode.callCallbackError(cbID, error.message); - } - } -}; - -export const updateCurrentTemplate = async ({ effects, state }: Context) => { - if (!state.editor.currentSandbox) { - return; - } - - try { - const currentTemplate = state.editor.currentSandbox.template; - const templateDefinition = getTemplateDefinition(currentTemplate); - - // We always want to be able to update server template based on its detection. - // We only want to update the client template when it's explicitly specified - // in the sandbox configuration. - if ( - templateDefinition.isServer || - state.editor.parsedConfigurations?.sandbox?.parsed?.template - ) { - const { parsed = {} } = state.editor.parsedConfigurations?.package || {}; - - const modulesByPath = mapValues(state.editor.modulesByPath, module => ({ - // No idea why this typing fails! - // @ts-ignore - content: module.code || '', - // @ts-ignore - isBinary: module.isBinary, - })); - - // TODO: What is a template really? Two different kinds of templates here, need to fix the types - // Talk to Ives and Bogdan - const newTemplate = (computeTemplate(parsed, modulesByPath) || - 'node') as TemplateType; - - if ( - newTemplate !== currentTemplate && - templateDefinition.isServer === - getTemplateDefinition(newTemplate).isServer - ) { - state.editor.currentSandbox.template = newTemplate; - await effects.api.saveTemplate( - state.editor.currentSandbox.id, - newTemplate - ); - } - } - } catch (e) { - // We don't want this to be blocking at all, it's low prio - if (process.env.NODE_ENV === 'development') { - console.error(e); - } - } -}; - -export const removeNpmDependencyFromPackageJson = async ( - { state, actions }: Context, - name: string -) => { - if ( - !state.editor.currentSandbox || - !state.editor.currentPackageJSONCode || - !state.editor.currentPackageJSON - ) { - return; - } - - const packageJson = JSON.parse(state.editor.currentPackageJSONCode); - - delete packageJson.dependencies[name]; - - const code = JSON.stringify(packageJson, null, 2); - const moduleShortid = state.editor.currentPackageJSON.shortid; - const module = state.editor.currentSandbox.modules.find( - m => m.shortid === moduleShortid - ); - - if (!module) { - return; - } - - actions.editor.setCode({ moduleShortid, code }); - - await actions.editor.codeSaved({ - code, - moduleShortid, - cbID: null, - }); -}; - -export const addNpmDependencyToPackageJson = async ( - { state, actions }: Context, - { - name, - isDev, - version, - }: { - name: string; - version?: string; - isDev: boolean; - } -) => { - if ( - !state.editor.currentSandbox || - !state.editor.currentPackageJSONCode || - !state.editor.currentPackageJSON - ) { - return; - } - - const packageJson = JSON.parse(state.editor.currentPackageJSONCode); - - const type = isDev ? 'devDependencies' : 'dependencies'; - - packageJson[type] = packageJson[type] || {}; - packageJson[type][name] = version || 'latest'; - packageJson[type] = sortObjectByKeys(packageJson[type]); - - const code = JSON.stringify(packageJson, null, 2); - const moduleShortid = state.editor.currentPackageJSON.shortid; - - const module = state.editor.currentSandbox.modules.find( - m => m.shortid === moduleShortid - ); - - if (!module) { - return; - } - - actions.editor.setCode({ moduleShortid, code }); - - await actions.editor.codeSaved({ - code, - moduleShortid, - cbID: null, - }); -}; - -export const updateModuleCode = ( - { state, effects }: Context, - { - module, - code, - }: { - module: Module; - code: string; - } -) => { - const { currentSandbox } = state.editor; - - if (!currentSandbox) { - return; - } - - if (module.savedCode === null) { - module.savedCode = module.code; - } - - effects.vscode.runCommand('workbench.action.keepEditor'); - - const tabs = state.editor.tabs as ModuleTab[]; - const tab = tabs.find(tabItem => tabItem.moduleShortid === module.shortid); - - if (tab) { - tab.dirty = false; - } - - module.code = code; - // Save the code to localStorage so we can recover in case of a crash - effects.moduleRecover.save(currentSandbox.id, currentSandbox.version, module); -}; - -// TODO: We can make this function simpler or we can also make a different convertSandbox -// action for when we don't fork it, but change it to a v2 sandbox. -export const forkSandbox = async ( - { state, effects, actions }: Context, - { - sandboxId: id, - teamId, - body, - openInNewWindow = false, - }: { - sandboxId: string; - teamId?: string | null; - body?: { collectionId: string | undefined }; - openInNewWindow?: boolean; - } -) => { - const sandbox = state.editor.currentSandbox; - const currentSandboxId = state.editor.currentId; - - if (!sandbox || !currentSandboxId) { - return; - } - - const templateDefinition = getTemplateDefinition( - sandbox ? sandbox.template : null - ); - - // If the user is not signed in and the sandbox is exectued in a server container - // we can't fork. - if (!state.isLoggedIn && templateDefinition.isServer) { - // So we track this happens - effects.analytics.track('Show Server Fork Sign In Modal'); - // And open a modal (alert) to show a sign in button - actions.modalOpened({ modal: 'forkServerModal' }); - - // Throwing an error here ensures that it get's caught in the "withOwnedSandbox" function - // when it tries to execute forkSandbox. When we just return instead of throwing the error - // is not caught and "withOwnedSandbox" will run its "continueAction" which, in case of the - // "codeSaved" function will continue to save code, with all kinds of actions and api calls. - throw new Error('ERR_ANON_SSE_FORK'); - } - - effects.analytics.track('Fork Sandbox', { - template: sandbox.customTemplate?.title, - sandboxId: sandbox.id, - }); - - try { - state.editor.isForkingSandbox = true; - - const usedBody: { - collectionId?: string; - teamId?: string; - } = body || {}; - - if (state.user) { - if (teamId === undefined && state.activeTeam) { - usedBody.teamId = state.activeTeam; - } else if (teamId !== null) { - usedBody.teamId = teamId; - } - } - - const forkedSandbox = await effects.api.forkSandbox(id, usedBody); - - // Copy over any unsaved code - Object.assign(forkedSandbox, { - modules: forkedSandbox.modules.map(module => { - const foundEquivalentModule = sandbox.modules.find( - currentSandboxModule => - currentSandboxModule.shortid === module.shortid - ); - - if (!foundEquivalentModule) { - return module; - } - - return { - ...module, - savedCode: foundEquivalentModule.savedCode, - code: foundEquivalentModule.code, - }; - }), - }); - - state.workspace.project.title = forkedSandbox.title || ''; - state.workspace.project.description = forkedSandbox.description || ''; - state.workspace.project.alias = forkedSandbox.alias || ''; - - effects.vscode.clearComments(); - Object.assign(state.editor.sandboxes[currentSandboxId]!, forkedSandbox); - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create( - forkedSandbox - ); - effects.preview.updateAddressbarUrl(); - - if (templateDefinition.isServer) { - effects.preview.refresh(); - actions.server.startContainer(forkedSandbox); - } - - if (state.workspace.openedWorkspaceItem === 'project-summary') { - actions.workspace.openDefaultItem(); - } - - effects.notificationToast.success('Forked sandbox!'); - - if (templateDefinition.isServer) { - actions.editor.showEnvironmentVariablesNotification(); - } - - effects.router.updateSandboxUrl(forkedSandbox, { openInNewWindow }); - - if (sandbox.originalGit) { - actions.git.loadGitSource(); - } - - actions.internal.currentSandboxChanged(); - await actions.getSandboxesLimits(); - } catch (error) { - const errorMessage = actions.internal.getErrorMessage({ error }); - - if (errorMessage.includes('DRAFT_LIMIT')) { - effects.notificationToast.add({ - title: 'Cannot create draft', - message: - 'Your drafts folder is full. Delete unneeded drafts, or upgrade to Pro for unlimited drafts.', - status: NotificationStatus.ERROR, - }); - } else { - actions.internal.handleError({ - message: 'We were unable to fork the sandbox', - error, - }); - } - - state.editor.isForkingSandbox = false; - throw error; - } -}; - -export const setCurrentModule = async ( - { state, effects }: Context, - module: Module -) => { - state.editor.currentTabId = null; - - const tabs = state.editor.tabs as ModuleTab[]; - const tab = tabs.find(tabItem => tabItem.moduleShortid === module.shortid); - - if (!tab) { - const dirtyTabIndex = tabs.findIndex(tabItem => tabItem.dirty); - const newTab: ModuleTab = { - type: TabType.MODULE, - moduleShortid: module.shortid, - dirty: true, - }; - - if (dirtyTabIndex >= 0) { - state.editor.tabs.splice(dirtyTabIndex, 1, newTab); - } else { - state.editor.tabs.splice(0, 0, newTab); - } - } - - state.editor.currentModuleShortid = module.shortid; - await effects.vscode.openModule(module); - effects.vscode.setErrors(state.editor.errors); - effects.vscode.setCorrections(state.editor.corrections); -}; - -export const updateSandboxPackageJson = async ({ state, actions }: Context) => { - const sandbox = state.editor.currentSandbox; - - if ( - !sandbox || - !state.editor.parsedConfigurations?.package?.parsed || - !state.editor.currentPackageJSON - ) { - return; - } - - if (!hasPermission(sandbox.authorization, 'write_code')) { - return; - } - - const { parsed } = state.editor.parsedConfigurations.package; - - parsed.keywords = sandbox.tags; - parsed.name = slugify(sandbox.title || sandbox.id); - parsed.description = sandbox.description; - - const code = JSON.stringify(parsed, null, 2); - const moduleShortid = state.editor.currentPackageJSON.shortid; - - const module = sandbox.modules.find(m => m.shortid === moduleShortid); - - if (!module) { - return; - } - - actions.editor.setCode({ moduleShortid, code }); - - await actions.editor.codeSaved({ - code, - moduleShortid, - cbID: null, - }); -}; - -export const updateDevtools = async ( - { state, actions }: Context, - viewConfig: ViewConfig[] -) => { - if (!state.editor.currentSandbox) { - return; - } - - await actions.files.updateWorkspaceConfig({ - preview: viewConfig, - }); -}; - -export const updatePreviewCode = ({ state, effects }: Context) => { - if (state.preferences.settings.instantPreviewEnabled) { - effects.preview.executeCodeImmediately(); - } else { - effects.preview.executeCode(); - } -}; diff --git a/packages/app/src/app/overmind/namespaces/editor/state.ts b/packages/app/src/app/overmind/namespaces/editor/state.ts deleted file mode 100755 index 135e60dd5c5..00000000000 --- a/packages/app/src/app/overmind/namespaces/editor/state.ts +++ /dev/null @@ -1,354 +0,0 @@ -import getTemplate from '@codesandbox/common/lib/templates'; -import { generateFileFromSandbox } from '@codesandbox/common/lib/templates/configuration/package-json'; -import { getPreviewTabs } from '@codesandbox/common/lib/templates/devtools'; -import { - ParsedConfigurationFiles, - ViewConfig, -} from '@codesandbox/common/lib/templates/template'; -import { - DevToolsTabPosition, - DiffTab, - Directory, - Module, - ModuleCorrection, - ModuleError, - ModuleTab, - Sandbox, - SandboxFs, - Tabs, - WindowOrientation, -} from '@codesandbox/common/lib/types'; -import { getSandboxOptions } from '@codesandbox/common/lib/url'; -import { CollaboratorFragment, InvitationFragment } from 'app/graphql/types'; -import { RecoverData } from 'app/overmind/effects/moduleRecover'; -import immer from 'immer'; -import { derived } from 'overmind'; - -import { mainModule as getMainModule } from '../../utils/main-module'; -import { parseConfigurations } from '../../utils/parse-configurations'; - -type State = { - /** - * Never use this! It doesn't reflect the id of the current sandbox. Use editor.currentSandbox.id instead. - */ - currentId: string | null; - currentModuleShortid: string | null; - isForkingSandbox: boolean; - mainModuleShortid: string | null; - sandboxes: { - [id: string]: Sandbox; - }; - collaborators: CollaboratorFragment[]; - invitations: InvitationFragment[]; - // TODO: What is this really? Could not find it in Cerebral, but - // EditorPreview is using it... weird stuff - devToolTabs: ViewConfig[]; - isLoading: boolean; - error: { - status: number; - message: string; - } | null; - isResizing: boolean; - changedModuleShortids: string[]; - currentTabId: string | null; - tabs: Tabs; - errors: ModuleError[]; - corrections: ModuleCorrection[]; - isInProjectView: boolean; - initialPath: string; - highlightedLines: number[]; - isUpdatingPrivacy: boolean; - quickActionsOpen: boolean; - previewWindowVisible: boolean; - workspaceConfigCode: string; - workspaceConfig: { - 'responsive-preview'?: { - [preset: string]: [number, number]; - }; - } | null; - statusBar: boolean; - previewWindowOrientation: WindowOrientation; - canWriteCode: boolean; - isAllModulesSynced: boolean; - currentSandbox: Sandbox | null; - currentModule: Module; - mainModule: Module | null; - currentPackageJSON: Module | null; - currentPackageJSONCode: string | null; - parsedConfigurations: ParsedConfigurationFiles | null; - currentTab: ModuleTab | DiffTab | undefined; - modulesByPath: SandboxFs; - isAdvancedEditor: boolean; - shouldDirectoryBeOpen: (params: { - directoryId: string; - module?: Module; - }) => boolean; - currentDevToolsPosition: DevToolsTabPosition; - sessionFrozen: boolean; - hasLoadedInitialModule: boolean; - recoveredFiles: Array<{ recoverData: RecoverData; module: Module }>; -}; - -export const state: State = { - hasLoadedInitialModule: false, - sandboxes: {}, - currentId: null, - isForkingSandbox: false, - currentModuleShortid: null, - mainModuleShortid: null, - isLoading: true, - error: null, - isResizing: false, - modulesByPath: {}, - collaborators: [], - invitations: [], - changedModuleShortids: derived(({ currentSandbox }: State) => { - if (!currentSandbox) { - return []; - } - - return currentSandbox.modules.reduce((aggr, module) => { - if (module.savedCode !== null && module.savedCode !== module.code) { - return aggr.concat(module.shortid); - } - - return aggr; - }, [] as string[]); - }), - currentTabId: null, - tabs: [], - errors: [], - sessionFrozen: true, - corrections: [], - isInProjectView: false, - initialPath: '/', - highlightedLines: [], - isUpdatingPrivacy: false, - quickActionsOpen: false, - previewWindowVisible: true, - statusBar: true, - recoveredFiles: [], - previewWindowOrientation: - window.innerHeight / window.innerWidth > 0.9 - ? WindowOrientation.HORIZONTAL - : WindowOrientation.VERTICAL, - - /** - * Normally we save this code in a file (.codesandbox/workspace.json), however, when someone - * doesn't own a sandbox and changes the UI we don't want to fork the sandbox (yet). That's - * why we introduce this field until we have datasources. When we have datasources we can store - * the actual content in the localStorage. - */ - workspaceConfigCode: '', - workspaceConfig: derived( - ({ currentSandbox, modulesByPath, workspaceConfigCode }: State) => { - if (!currentSandbox) { - return null; - } - let workspaceConfig; - - try { - workspaceConfig = JSON.parse( - (modulesByPath['/.codesandbox/workspace.json'] as Module).code - ); - } catch { - // nothing - } - - if (currentSandbox.owned) { - return modulesByPath['/.codesandbox/workspace.json'] - ? workspaceConfig - : null; - } - - return workspaceConfigCode ? JSON.parse(workspaceConfigCode) : null; - } - ), - currentDevToolsPosition: { - devToolIndex: 0, - tabPosition: 0, - }, - canWriteCode: derived( - ({ currentSandbox }: State) => - currentSandbox?.authorization === 'write_code' - ), - currentSandbox: derived(({ sandboxes, currentId }: State) => { - if (currentId && sandboxes[currentId]) { - return sandboxes[currentId]; - } - - return null; - }), - - isAllModulesSynced: derived( - ({ changedModuleShortids }: State) => !changedModuleShortids.length - ), - currentModule: derived( - ({ currentSandbox, currentModuleShortid }: State) => - (currentSandbox && - currentSandbox.modules.find( - module => module.shortid === currentModuleShortid - )) || - ({} as Module) - ), - currentTab: derived(({ currentTabId, currentModuleShortid, tabs }: State) => { - if (currentTabId) { - const foundTab = tabs.find(tab => 'id' in tab && tab.id === currentTabId); - - if (foundTab) { - return foundTab; - } - } - - return tabs.find( - tab => - 'moduleShortid' in tab && tab.moduleShortid === currentModuleShortid - ); - }), - /** - * We have two types of editors in CodeSandbox: an editor focused on smaller projects and - * an editor that works with bigger projects that run on a container. The advanced editor - * only has added features, so it's a subset on top of the existing editor. - */ - isAdvancedEditor: derived(({ currentSandbox }: State) => { - if (!currentSandbox) { - return false; - } - - const { isServer } = getTemplate(currentSandbox.template); - - return isServer && currentSandbox.owned; - }), - parsedConfigurations: derived(({ currentSandbox }: State) => - currentSandbox ? parseConfigurations(currentSandbox) : null - ), - mainModule: derived(({ currentSandbox, parsedConfigurations }: State) => - currentSandbox ? getMainModule(currentSandbox, parsedConfigurations) : null - ), - currentPackageJSON: derived(({ currentSandbox }: State) => { - if (!currentSandbox) { - return null; - } - - const module = currentSandbox.modules.find( - m => m.directoryShortid == null && m.title === 'package.json' - ); - - return module || null; - }), - currentPackageJSONCode: derived( - ({ currentSandbox, currentPackageJSON }: State) => { - if (!currentPackageJSON || !currentSandbox) { - return null; - } - - return currentPackageJSON.code - ? currentPackageJSON.code - : generateFileFromSandbox(currentSandbox); - } - ), - shouldDirectoryBeOpen: derived( - ({ currentSandbox, currentModule }: State) => ({ - directoryId, - module = currentModule, - }) => { - if (!currentSandbox) { - return false; - } - - const { modules, directories } = currentSandbox; - const currentModuleId = module.id; - const currentModuleParents = getModuleParents( - modules, - directories, - currentModuleId - ); - - const isParentOfModule = currentModuleParents.includes(directoryId); - - return isParentOfModule; - } - ), - devToolTabs: derived( - ({ - currentSandbox: sandbox, - parsedConfigurations, - workspaceConfigCode: intermediatePreviewCode, - }: State) => { - if (!sandbox || !parsedConfigurations) { - return []; - } - - const views = getPreviewTabs( - sandbox, - parsedConfigurations, - intermediatePreviewCode - ); - - // Do it in an immutable manner, prevents changing the original object - return immer(views, draft => { - const sandboxConfig = sandbox.modules.find( - x => x.directoryShortid == null && x.title === 'sandbox.config.json' - ); - let view = 'browser'; - if (sandboxConfig) { - try { - view = JSON.parse(sandboxConfig.code || '').view || 'browser'; - } catch (e) { - /* swallow */ - } - } - - const sandboxOptions = getSandboxOptions(location.href); - if ( - sandboxOptions.previewWindow && - (sandboxOptions.previewWindow === 'tests' || - sandboxOptions.previewWindow === 'console') - ) { - // Backwards compatibility for ?previewwindow= - - view = sandboxOptions.previewWindow; - } - - if (view !== 'browser') { - // Backwards compatibility for sandbox.config.json - if (view === 'console') { - draft[0].views = draft[0].views.filter( - t => t.id !== 'codesandbox.console' - ); - draft[0].views.unshift({ id: 'codesandbox.console' }); - } else if (view === 'tests') { - draft[0].views = draft[0].views.filter( - t => t.id !== 'codesandbox.tests' - ); - draft[0].views.unshift({ id: 'codesandbox.tests' }); - } - } - }); - } - ), -}; - -// This should be moved somewhere else -function getModuleParents( - modules: Module[], - directories: Directory[], - id: string -): string[] { - const module = modules.find(moduleEntry => moduleEntry.id === id); - - if (!module) return []; - - let directory = directories.find( - directoryEntry => directoryEntry.shortid === module.directoryShortid - ); - let directoryIds: string[] = []; - while (directory != null) { - directoryIds = [...directoryIds, directory!.id]; - directory = directories.find( - directoryEntry => directoryEntry.shortid === directory!.directoryShortid // eslint-disable-line - ); - } - - return directoryIds; -} diff --git a/packages/app/src/app/overmind/namespaces/files/actions.ts b/packages/app/src/app/overmind/namespaces/files/actions.ts deleted file mode 100755 index 643f9d563d1..00000000000 --- a/packages/app/src/app/overmind/namespaces/files/actions.ts +++ /dev/null @@ -1,1078 +0,0 @@ -import { - getDirectoryPath, - getModulePath, - getModulesAndDirectoriesInDirectory, -} from '@codesandbox/common/lib/sandbox/modules'; -import getDefinition from '@codesandbox/common/lib/templates'; -import { Directory, Module, UploadFile } from '@codesandbox/common/lib/types'; -import { getTextOperation } from '@codesandbox/common/lib/utils/diff'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { Context } from 'app/overmind'; -import { RecoverData } from 'app/overmind/effects/moduleRecover'; -import { withOwnedSandbox } from 'app/overmind/factories'; -import { createOptimisticModule } from 'app/overmind/utils/common'; -import { INormalizedModules } from 'codesandbox-import-util-types'; -import denormalize from 'codesandbox-import-utils/lib/utils/files/denormalize'; - -import { - resolveDirectoryWrapped, - resolveModuleWrapped, -} from '../../utils/resolve-module-wrapped'; -import * as internalActions from './internalActions'; - -export const internal = internalActions; - -export const applyRecover = ( - { state, effects, actions }: Context, - recoveredList: Array<{ - module: Module; - recoverData: RecoverData; - }> -) => { - if (!state.editor.currentSandbox) { - return; - } - - effects.moduleRecover.clearSandbox(state.editor.currentSandbox.id); - recoveredList.forEach(({ recoverData, module }) => { - actions.editor.codeChanged({ - moduleShortid: module.shortid, - code: recoverData.code, - }); - effects.vscode.setModuleCode(module); - }); - - effects.analytics.track('Files Recovered', { - fileCount: recoveredList.length, - }); -}; - -export const createRecoverDiffs = ( - { state, effects, actions }: Context, - recoveredList: Array<{ - module: Module; - recoverData: RecoverData; - }> -) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - effects.moduleRecover.clearSandbox(sandbox.id); - recoveredList.forEach(({ recoverData, module }) => { - const oldCode = module.code; - actions.editor.codeChanged({ - moduleShortid: module.shortid, - code: recoverData.code, - }); - effects.vscode.openDiff(sandbox.id, module, oldCode); - }); - - effects.analytics.track('Files Recovered', { - fileCount: recoveredList.length, - }); -}; - -export const discardRecover = ({ effects, state }: Context) => { - if (!state.editor.currentSandbox) { - return; - } - effects.moduleRecover.clearSandbox(state.editor.currentSandbox.id); -}; - -export const moduleRenamed = withOwnedSandbox( - async ( - { state, actions, effects }: Context, - { - title, - moduleShortid, - }: { - title: string; - moduleShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const module = sandbox.modules.find( - moduleItem => moduleItem.shortid === moduleShortid - ); - - if (!module || !module.id) { - return; - } - - const oldTitle = module.title; - const oldPath = module.path; - - module.title = title; - module.path = getModulePath( - sandbox.modules, - sandbox.directories, - module.id - ); - - effects.vscode.sandboxFsSync.rename( - state.editor.modulesByPath, - oldPath!, - module.path - ); - - await effects.vscode.updateTabsPath(oldPath!, module.path); - - if (state.editor.currentModuleShortid === module.shortid) { - effects.vscode.openModule(module); - } - - actions.editor.internal.updatePreviewCode(); - try { - await effects.api.saveModuleTitle(sandbox.id, moduleShortid, title); - - if (state.live.isCurrentEditor) { - effects.live.sendModuleUpdate(module); - } - effects.executor.updateFiles(sandbox); - } catch (error) { - module.title = oldTitle; - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - - if (state.editor.currentModuleShortid === module.shortid) { - effects.vscode.openModule(module); - } - - actions.editor.internal.updatePreviewCode(); - - actions.internal.handleError({ message: 'Could not rename file', error }); - } - }, - async () => {}, - 'write_code' -); - -export const directoryCreated = withOwnedSandbox( - async ( - { state, effects, actions }: Context, - { - title, - directoryShortid, - }: { - title: string; - directoryShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const optimisticId = effects.utils.createOptimisticId(); - const optimisticDirectory = { - id: optimisticId, - title, - directoryShortid, - shortid: effects.utils.createOptimisticId(), - sourceId: sandbox.sourceId, - insertedAt: new Date().toString(), - updatedAt: new Date().toString(), - type: 'directory' as 'directory', - path: (null as unknown) as string, - }; - - sandbox.directories.push(optimisticDirectory as Directory); - optimisticDirectory.path = getDirectoryPath( - sandbox.modules, - sandbox.directories, - optimisticId - ); - effects.vscode.sandboxFsSync.mkdir( - state.editor.modulesByPath, - optimisticDirectory - ); - - try { - const newDirectory = await effects.api.createDirectory( - sandbox.id, - directoryShortid, - title - ); - const directory = sandbox.directories.find( - directoryItem => directoryItem.shortid === optimisticDirectory.shortid - ); - - if (!directory) { - effects.notificationToast.error( - 'Could not find saved directory, please refresh and try again' - ); - return; - } - - Object.assign(directory, { - id: newDirectory.id, - shortid: newDirectory.shortid, - }); - - effects.live.sendDirectoryCreated(directory); - effects.executor.updateFiles(sandbox); - } catch (error) { - const directoryIndex = sandbox.directories.findIndex( - directoryItem => directoryItem.shortid === optimisticDirectory.shortid - ); - - sandbox.directories.splice(directoryIndex, 1); - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - actions.internal.handleError({ - message: 'Unable to save new directory', - error, - }); - } - }, - async () => {}, - 'write_code' -); - -export const moduleMovedToDirectory = withOwnedSandbox( - async ( - { state, actions, effects }: Context, - { - moduleShortid, - directoryShortid, - }: { - moduleShortid: string; - directoryShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const module = sandbox.modules.find( - moduleItem => moduleItem.shortid === moduleShortid - ); - - if (!module || !module.id) { - return; - } - - const currentDirectoryShortid = module.directoryShortid; - const oldPath = module.path; - - module.directoryShortid = directoryShortid; - module.path = getModulePath( - sandbox.modules, - sandbox.directories, - module.id - ); - - effects.vscode.sandboxFsSync.rename( - state.editor.modulesByPath, - oldPath!, - module.path - ); - effects.vscode.openModule(module); - actions.editor.internal.updatePreviewCode(); - try { - await effects.api.saveModuleDirectory( - sandbox.id, - moduleShortid, - directoryShortid - ); - effects.live.sendModuleUpdate(module); - effects.executor.updateFiles(sandbox); - } catch (error) { - module.directoryShortid = currentDirectoryShortid; - module.path = oldPath; - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - actions.internal.handleError({ - message: 'Could not save new module location', - error, - }); - } - - if (sandbox.originalGit) { - actions.git.updateGitChanges(); - } - }, - async () => {}, - 'write_code' -); - -export const directoryMovedToDirectory = withOwnedSandbox( - async ( - { state, actions, effects }: Context, - { - shortid, - directoryShortid, - }: { - shortid: string; - directoryShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const directoryToMove = sandbox.directories.find( - directoryItem => directoryItem.shortid === shortid - ); - - if (!directoryToMove) { - return; - } - - const oldPath = directoryToMove.path; - - directoryToMove.directoryShortid = directoryShortid; - directoryToMove.path = getDirectoryPath( - sandbox.modules, - sandbox.directories, - directoryToMove.id - ); - - // We have to recreate the whole thing as many files and folders - // might have changed their path - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - actions.editor.internal.updatePreviewCode(); - try { - await effects.api.saveDirectoryDirectory( - sandbox.id, - shortid, - directoryShortid - ); - effects.live.sendDirectoryUpdate(directoryToMove); - effects.executor.updateFiles(sandbox); - } catch (error) { - directoryToMove.directoryShortid = shortid; - directoryToMove.path = oldPath; - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - actions.internal.handleError({ - message: 'Could not save new directory location', - error, - }); - } - - if (sandbox.originalGit) { - actions.git.updateGitChanges(); - } - }, - async () => {}, - 'write_code' -); - -export const directoryDeleted = withOwnedSandbox( - async ( - { state, actions, effects }: Context, - { - directoryShortid, - }: { - directoryShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const directory = sandbox.directories.find( - directoryItem => directoryItem.shortid === directoryShortid - ); - - if (!directory) { - return; - } - - const removedDirectory = sandbox.directories.splice( - sandbox.directories.indexOf(directory), - 1 - )[0]; - const { - removedModules, - removedDirectories, - } = getModulesAndDirectoriesInDirectory( - removedDirectory, - sandbox.modules, - sandbox.directories - ); - - removedModules.forEach(removedModule => { - effects.vscode.sandboxFsSync.unlink( - state.editor.modulesByPath, - removedModule - ); - sandbox.modules.splice(sandbox.modules.indexOf(removedModule), 1); - }); - - removedDirectories.forEach(removedDirectoryItem => { - sandbox.directories.splice( - sandbox.directories.indexOf(removedDirectoryItem), - 1 - ); - }); - - // We open the main module as we do not really know if you had opened - // any nested file of this directory. It would require complex logic - // to figure that out. This concept is soon removed anyways - if (state.editor.mainModule) - effects.vscode.openModule(state.editor.mainModule); - actions.editor.internal.updatePreviewCode(); - try { - await effects.api.deleteDirectory(sandbox.id, directoryShortid); - effects.live.sendDirectoryDeleted(directoryShortid); - effects.executor.updateFiles(sandbox); - } catch (error) { - sandbox.directories.push(removedDirectory); - - removedModules.forEach(removedModule => { - sandbox.modules.push(removedModule); - }); - removedDirectories.forEach(removedDirectoryItem => { - sandbox.directories.push(removedDirectoryItem); - }); - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - actions.internal.handleError({ - message: 'Could not delete directory', - error, - }); - } - - if (sandbox.originalGit) { - actions.git.updateGitChanges(); - } - }, - async () => {}, - 'write_code' -); - -export const directoryRenamed = withOwnedSandbox( - async ( - { effects, actions, state }: Context, - { - title, - directoryShortid, - }: { - title: string; - directoryShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const directory = sandbox.directories.find( - directoryEntry => directoryEntry.shortid === directoryShortid - ); - - if (!directory) { - return; - } - - const oldTitle = directory.title; - - actions.files.internal.renameDirectoryInState({ - directory, - sandbox, - title, - }); - - actions.editor.internal.updatePreviewCode(); - try { - await effects.api.saveDirectoryTitle(sandbox.id, directoryShortid, title); - - if (state.live.isCurrentEditor) { - effects.live.sendDirectoryUpdate(directory); - } - - effects.executor.updateFiles(sandbox); - } catch (error) { - actions.files.internal.renameDirectoryInState({ - directory, - sandbox, - title: oldTitle, - }); - actions.internal.handleError({ - message: 'Could not rename directory', - error, - }); - } - }, - async () => {}, - 'write_code' -); - -export const gotUploadedFiles = async ( - { state, actions, effects }: Context, - message: string -) => { - const modal = 'storageManagement'; - effects.analytics.track('Open Modal', { modal }); - state.currentModalMessage = message; - state.currentModal = modal; - - try { - const uploadedFilesInfo = await effects.api.getUploads(); - - state.uploadedFiles = uploadedFilesInfo.uploads; - state.maxStorage = uploadedFilesInfo.maxSize; - state.usedStorage = uploadedFilesInfo.currentSize; - } catch (error) { - actions.internal.handleError({ - message: 'Unable to get uploaded files information', - error, - }); - } -}; - -export const addedFileToSandbox = withOwnedSandbox( - async ( - { actions, effects, state }: Context, - { name, url }: Pick - ) => { - if (!state.editor.currentSandbox) { - return; - } - actions.internal.closeModals(false); - await actions.files.moduleCreated({ - title: name, - directoryShortid: null, - code: url, - isBinary: true, - }); - - effects.executor.updateFiles(state.editor.currentSandbox); - - if (state.editor.currentSandbox.originalGit) { - actions.git.updateGitChanges(); - } - }, - async () => {}, - 'write_code' -); - -export const deletedUploadedFile = async ( - { actions, effects, state }: Context, - id: string -) => { - if (!state.uploadedFiles) { - return; - } - const index = state.uploadedFiles.findIndex(file => file.id === id); - const removedFiles = state.uploadedFiles.splice(index, 1); - - try { - await effects.api.deleteUploadedFile(id); - } catch (error) { - state.uploadedFiles.splice(index, 0, ...removedFiles); - actions.internal.handleError({ - message: 'Unable to delete uploaded file', - error, - }); - } -}; - -export const filesUploaded = withOwnedSandbox( - async ( - { state, effects, actions }: Context, - { - files, - directoryShortid, - }: { - files: { [k: string]: { dataURI: string; type: string } }; - directoryShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - const modal = 'uploading'; - effects.analytics.track('Open Modal', { modal }); - // What message? - // state.currentModalMessage = message; - state.currentModal = modal; - - try { - const { modules, directories } = await actions.files.internal.uploadFiles( - { - files, - directoryShortid, - } - ); - - actions.files.massCreateModules({ - modules, - directories, - directoryShortid, - }); - - effects.executor.updateFiles(sandbox); - actions.git.updateGitChanges(); - } catch (error) { - if (error.message.indexOf('413') !== -1) { - actions.internal.handleError({ - message: `The uploaded file is bigger than 7MB, please upgrade to PRO for 30MB limits.`, - error, - hideErrorMessage: true, - }); - } else { - actions.internal.handleError({ - message: 'Unable to upload files', - error, - }); - } - } - - actions.internal.closeModals(false); - }, - async () => {}, - 'write_code' -); - -type File = { [k: string]: { dataURI: string; type: string } }; -export const thumbnailToBeCropped = withOwnedSandbox( - async ({ state, actions }: Context, { file }: { file: File }) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - const fileName = Object.keys(file)[0]; - - // if it's a gif we can't crop it, just upload it - if (fileName.split('.').pop() === 'gif') { - await actions.files.thumbnailUploaded({ file }); - - return; - } - state.workspace.activeThumb = file; - state.currentModal = 'cropThumbnail'; - }, - async () => {}, - 'write_code' -); - -export const thumbnailUploaded = async ( - { state, effects, actions }: Context, - { file }: { file: File } -) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const thumb = sandbox.modules.find(m => m.path.includes('/thumbnail.')); - state.workspace.uploadingThumb = true; - if (thumb) { - await actions.files.moduleDeleted({ moduleShortid: thumb.shortid }); - } - state.currentModal = 'uploading'; - try { - const { modules, directories } = await actions.files.internal.uploadFiles({ - files: file, - directoryShortid: '', - }); - - actions.files.massCreateModules({ - modules, - directories, - directoryShortid: null, - }); - - effects.executor.updateFiles(sandbox); - actions.git.updateGitChanges(); - effects.notificationToast.success('Thumbnail image updated'); - } catch (error) { - if (error.message.indexOf('413') !== -1) { - actions.internal.handleError({ - message: `The uploaded file is bigger than 7MB, please upgrade to PRO for 30MB limits.`, - error, - hideErrorMessage: true, - }); - } else { - actions.internal.handleError({ - message: 'Unable to upload files', - error, - }); - } - } - state.workspace.uploadingThumb = false; - actions.internal.closeModals(false); -}; - -export const massCreateModules = withOwnedSandbox( - async ( - { state, actions, effects }: Context, - { - modules, - directories, - directoryShortid, - cbID, - }: { - modules: any; - directories: any; - directoryShortid: string | null; - cbID?: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const sandboxId = sandbox.id; - - try { - const data = await effects.api.massCreateModules( - sandboxId, - directoryShortid, - modules, - directories - ); - - sandbox.modules = sandbox.modules.concat(data.modules); - sandbox.directories = sandbox.directories.concat(data.directories); - - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - - actions.editor.internal.updatePreviewCode(); - - // This can happen if you have selected a deleted file in VSCode and try to save it, - // we want to select it again - if (!state.editor.currentModuleShortid) { - const lastAddedModule = sandbox.modules[sandbox.modules.length - 1]; - - actions.editor.internal.setCurrentModule(lastAddedModule); - } - - if (state.live.isCurrentEditor) { - effects.live.sendMassCreatedModules(data.modules, data.directories); - } - - if (cbID) { - effects.vscode.callCallback(cbID); - } - - effects.executor.updateFiles(sandbox); - } catch (error) { - if (cbID) { - effects.vscode.callCallbackError(cbID, error.message); - } - - actions.internal.handleError({ - message: 'Unable to create new files', - error, - }); - } - }, - async () => {}, - 'write_code' -); - -export const moduleCreated = withOwnedSandbox( - async ( - { state, actions, effects }: Context, - { - title, - directoryShortid, - code, - isBinary, - }: { - title: string; - directoryShortid: string | null; - code?: string; - isBinary?: boolean; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const optimisticId = effects.utils.createOptimisticId(); - const optimisticModule = createOptimisticModule({ - id: optimisticId, - title, - directoryShortid: directoryShortid || null, - shortid: effects.utils.createOptimisticId(), - sourceId: sandbox.sourceId, - isNotSynced: true, - ...(code ? { code } : {}), - ...(typeof isBinary === 'boolean' ? { isBinary } : {}), - }); - - // We have to push the module to the array before we can figure out its path, - // this is all changing soon - sandbox.modules.push(optimisticModule as Module); - optimisticModule.path = getModulePath( - sandbox.modules, - sandbox.directories, - optimisticId - ); - - // We grab the module from the state to continue working with it (proxy) - const module = sandbox.modules[sandbox.modules.length - 1]; - - const template = getDefinition(sandbox.template); - const config = template.configurationFiles[module.path!]; - - if ( - config && - (config.generateFileFromSandbox || - config.getDefaultCode || - config.generateFileFromState) - ) { - if (config.generateFileFromState) { - module.code = config.generateFileFromState( - state.preferences.settings.prettierConfig - ); - } else if (config.generateFileFromSandbox) { - module.code = config.generateFileFromSandbox(sandbox); - } else if (config.getDefaultCode) { - const resolveModule = resolveModuleWrapped(sandbox); - - module.code = config.getDefaultCode(sandbox.template, resolveModule); - } - } - - effects.vscode.sandboxFsSync.appendFile(state.editor.modulesByPath, module); - actions.editor.internal.setCurrentModule(module); - - try { - const updatedModule = await effects.api.createModule(sandbox.id, module); - - module.id = updatedModule.id; - module.shortid = updatedModule.shortid; - - effects.vscode.sandboxFsSync.writeFile( - state.editor.modulesByPath, - module - ); - state.editor.currentModuleShortid = module.shortid; - - effects.executor.updateFiles(sandbox); - - if (state.live.isCurrentEditor) { - effects.live.sendModuleCreated(module); - // Update server with latest data - effects.live.sendCodeUpdate( - module.shortid, - getTextOperation(updatedModule.code || '', module.code) - ); - } - } catch (error) { - sandbox.modules.splice(sandbox.modules.indexOf(module), 1); - if (state.editor.mainModule) - actions.editor.internal.setCurrentModule(state.editor.mainModule); - - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - - actions.internal.handleError({ - message: 'Unable to save new file', - error, - }); - } - - if (sandbox.originalGit) { - actions.git.updateGitChanges(); - } - }, - async () => {}, - 'write_code' -); - -export const moduleDeleted = withOwnedSandbox( - async ( - { state, effects, actions }: Context, - { - moduleShortid, - }: { - moduleShortid: string; - } - ) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const moduleToDeleteIndex = sandbox.modules.findIndex( - module => module.shortid === moduleShortid - ); - const removedModule = sandbox.modules.splice(moduleToDeleteIndex, 1)[0]; - const wasCurrentModule = - state.editor.currentModuleShortid === moduleShortid; - - effects.vscode.sandboxFsSync.unlink( - state.editor.modulesByPath, - removedModule - ); - - if (wasCurrentModule && state.editor.mainModule) { - actions.editor.internal.setCurrentModule(state.editor.mainModule); - } - - actions.editor.internal.updatePreviewCode(); - - try { - await effects.api.deleteModule(sandbox.id, moduleShortid); - - if (state.live.isCurrentEditor) { - effects.live.sendModuleDeleted(moduleShortid); - } - effects.executor.updateFiles(sandbox); - } catch (error) { - sandbox.modules.push(removedModule); - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); - actions.internal.handleError({ message: 'Could not delete file', error }); - } - - if (sandbox.originalGit) { - actions.git.updateGitChanges(); - } - }, - async () => {}, - 'write_code' -); - -export const createModulesByPath = async ( - { state, effects, actions }: Context, - { - files, - cbID, - }: { - cbID?: string; - files: INormalizedModules; - } -) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const { modules, directories } = denormalize(files, sandbox.directories); - - await actions.files.massCreateModules({ - modules, - directories, - directoryShortid: null, - cbID, - }); - - effects.executor.updateFiles(sandbox); -}; - -export const syncSandbox = async ( - { state, actions, effects }: Context, - updates: any[] -) => { - const oldSandbox = state.editor.currentSandbox; - if (!oldSandbox) { - return; - } - const { id } = oldSandbox; - - try { - const newSandbox = await effects.api.getSandbox(id); - - updates.forEach(update => { - const { op, path, type } = update; - - if (type === 'file') { - const resolveModuleOld = resolveModuleWrapped(oldSandbox); - const resolveModuleNew = resolveModuleWrapped(newSandbox); - const oldModule = resolveModuleOld(path); - if (op === 'update') { - const newModule = resolveModuleNew(path); - - if (newModule) { - if (oldModule) { - const modulePos = oldSandbox.modules.indexOf(oldModule); - Object.assign(oldSandbox.modules[modulePos], newModule); - } else { - oldSandbox.modules.push(newModule); - } - } - } else if (op === 'delete' && oldModule) { - oldSandbox.modules.splice(oldSandbox.modules.indexOf(oldModule), 1); - } - } else { - const resolveDirectoryOld = resolveDirectoryWrapped(oldSandbox); - const resolveDirectoryNew = resolveDirectoryWrapped(newSandbox); - - if (op === 'update') { - // Create - const newDirectory = resolveDirectoryNew(path); - if (newDirectory) { - oldSandbox.directories.push(newDirectory); - } - } else { - const oldDirectory = resolveDirectoryOld(path); - if (oldDirectory) { - const directory = oldSandbox.directories.find( - directoryItem => directoryItem.shortid === oldDirectory.shortid - ); - if (directory) { - oldSandbox.directories.splice( - oldSandbox.directories.indexOf(directory), - 1 - ); - } - } - } - } - }); - } catch (error) { - if (error.response?.status === 404) { - return; - } - - actions.internal.handleError({ - message: - "We weren't able to retrieve the latest files of the sandbox, please refresh", - error, - }); - } - - // No matter if error or not we resync the whole shabang! - if (state.editor.currentSandbox) - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create( - state.editor.currentSandbox - ); -}; - -export const updateWorkspaceConfig = async ( - { state, actions }: Context, - update: {} -) => { - if (hasPermission(state.editor.currentSandbox!.authorization, 'write_code')) { - const devtoolsModule = state.editor.modulesByPath[ - '/.codesandbox/workspace.json' - ] as Module; - - if (devtoolsModule) { - const updatedCode = JSON.stringify( - Object.assign(JSON.parse(devtoolsModule.code), update), - null, - 2 - ); - actions.editor.codeChanged({ - moduleShortid: devtoolsModule.shortid, - code: updatedCode, - }); - await actions.editor.codeSaved({ - code: updatedCode, - moduleShortid: devtoolsModule.shortid, - cbID: null, - }); - } else { - await actions.files.createModulesByPath({ - files: { - '/.codesandbox/workspace.json': { - content: JSON.stringify(update, null, 2), - isBinary: false, - }, - }, - }); - } - } else { - state.editor.workspaceConfigCode = JSON.stringify( - state.editor.workspaceConfigCode - ? Object.assign(JSON.parse(state.editor.workspaceConfigCode), update) - : update, - null, - 2 - ); - } -}; diff --git a/packages/app/src/app/overmind/namespaces/files/index.ts b/packages/app/src/app/overmind/namespaces/files/index.ts deleted file mode 100755 index 41d69f6f09e..00000000000 --- a/packages/app/src/app/overmind/namespaces/files/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as actions from './actions'; - -export { actions }; diff --git a/packages/app/src/app/overmind/namespaces/files/internalActions.ts b/packages/app/src/app/overmind/namespaces/files/internalActions.ts deleted file mode 100755 index ecd1e7316cd..00000000000 --- a/packages/app/src/app/overmind/namespaces/files/internalActions.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { Context } from 'app/overmind'; -import { MAX_FILE_SIZE } from 'codesandbox-import-utils/lib/is-text'; -import denormalize from 'codesandbox-import-utils/lib/utils/files/denormalize'; -import { chunk } from 'lodash-es'; -import { Directory, Sandbox } from '@codesandbox/common/lib/types'; -import { getDirectoryPath } from '@codesandbox/common/lib/sandbox/modules'; - -function b64DecodeUnicode(file: string) { - // Adding this fixes uploading JSON files with non UTF8-characters - // https://stackoverflow.com/a/30106551 - return decodeURIComponent( - atob(file) - .split('') - .map(char => '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2)) - .join('') - ); -} - -export const recoverFiles = ({ effects, state }: Context) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - const recoverList = effects.moduleRecover.getRecoverList( - sandbox.id, - sandbox.modules - ); - - const recoveredList = recoverList.reduce((aggr, item) => { - if (!item) { - return aggr; - } - const { recoverData, module } = item; - - if (module.code !== recoverData.code) { - return aggr.concat(item); - } - - return aggr; - }, [] as typeof recoverList); - - if (recoveredList.length > 0) { - state.editor.recoveredFiles = recoveredList; - state.currentModal = 'recoveredFiles'; - } -}; - -export const uploadFiles = async ( - { effects }: Context, - { - files, - directoryShortid, - }: { - files: { [k: string]: { dataURI: string; type: string } }; - directoryShortid: string; - } -) => { - const parsedFiles: { - [key: string]: { isBinary: boolean; content: string }; - } = {}; - // We first create chunks so we don't overload the server with 100 multiple - // upload requests - const filePaths = Object.keys(files); - const chunkedFilePaths = chunk(filePaths, 5); - - const textExtensions = (await import('textextensions/source/index.json')) - .default; - - // We traverse all files and upload them when necessary, then add them to the - // parsedFiles object - /* eslint-disable no-restricted-syntax, no-await-in-loop */ - for (const filePathsChunk of chunkedFilePaths) { - await Promise.all( - filePathsChunk.map(async filePath => { - const file = files[filePath]; - const { dataURI } = file; - - const extension = filePath.split('.').pop(); - - if ( - ((extension && textExtensions.includes(extension)) || - file.type.startsWith('text/') || - file.type === 'application/json') && - dataURI.length < MAX_FILE_SIZE - ) { - const text = - dataURI !== 'data:' - ? b64DecodeUnicode(dataURI.replace(/^.*base64,/, '')) - : ''; - parsedFiles[filePath] = { - content: text, - isBinary: false, - }; - } else { - try { - const data = await effects.api.createUpload(filePath, dataURI); - parsedFiles[filePath] = { - content: data.url, - isBinary: true, - }; - } catch (error) { - error.message = `Error uploading ${filePath}: ${error.message}`; - - throw error; - } - } - }) - ); - } - /* eslint-enable */ - - // We create a module format that CodeSandbox understands - const { modules, directories } = denormalize(parsedFiles); - - // If the directory was dropped in a subdirectory we need to shift all - // the root directories to that directory - const relativeDirectories = directories.map(dir => { - if (dir.directoryShortid == null) { - return { - ...dir, - directoryShortid, - }; - } - - return dir; - }); - - const relativeModules = modules.map(m => { - if (m.directoryShortid == null) { - return { - ...m, - directoryShortid, - }; - } - - return m; - }); - - // Proceed to give the data for `massCreateModules` - return { - modules: relativeModules, - directories: relativeDirectories, - }; -}; - -export const renameDirectoryInState = ( - { state, effects }: Context, - { - title, - directory, - sandbox, - }: { - title: string; - directory: Directory; - sandbox: Sandbox; - } -) => { - const oldPath = directory.path; - directory.title = title; - const newPath = getDirectoryPath( - sandbox.modules, - sandbox.directories, - directory.id - ); - directory.path = newPath; - - effects.vscode.sandboxFsSync.rename( - state.editor.modulesByPath, - oldPath!, - directory.path - ); - - if (oldPath) { - sandbox.modules.forEach(m => { - if (m.path && m.path.startsWith(oldPath + '/')) { - m.path = m.path.replace(oldPath, newPath); - } - }); - sandbox.directories.forEach(d => { - if (d.path && d.path.startsWith(oldPath + '/')) { - d.path = d.path.replace(oldPath, newPath); - } - }); - } -}; diff --git a/packages/app/src/app/overmind/namespaces/git/actions.ts b/packages/app/src/app/overmind/namespaces/git/actions.ts deleted file mode 100755 index 776aba1b11f..00000000000 --- a/packages/app/src/app/overmind/namespaces/git/actions.ts +++ /dev/null @@ -1,1029 +0,0 @@ -import { getModulePath } from '@codesandbox/common/lib/sandbox/modules'; -import { - GitChanges, - GitFileCompare, - GitInfo, - Module, - SandboxGitState, - Sandbox, -} from '@codesandbox/common/lib/types'; -import { - captureException, - logBreadcrumb, -} from '@codesandbox/common/lib/utils/analytics/sentry'; -import { convertTypeToStatus } from '@codesandbox/common/lib/utils/notifications'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { NotificationStatus } from '@codesandbox/notifications/lib/state'; -import { Context } from 'app/overmind'; -import { debounce, pipe } from 'overmind'; -import { CSBProjectGitHubRepository } from '@codesandbox/common/lib/utils/url-generator'; - -import * as internalActions from './internalActions'; -import { createDiff } from './utils'; - -export const internal = internalActions; - -export const repoTitleChanged = ( - { state }: Context, - { - title, - }: { - title: string; - } -) => { - state.git.repoTitle = title; - state.git.error = null; -}; - -/** - * There are two states the Sandbox can be in: - * 1. It has been forked and the SOURCE is the GIT details of the Sandbox it forked from - * 2. It has been forked and the SOURCE is the PR created - * - * To give indication of what file changes has been made, we do the following: - * 1. First we load the SOURCE sandbox. We need this to indicate what you have changed in your sandbox - * 2. Every time you make a change to the files we go through modules/directories of the source sandbox and your sandbox to produce a diff - * - * The steps that is taken to find "out of sync" and "conflict", is the following: - * 1. We first want to compare your sandbox with the SOURCE (This being the GIT info on the sandbox you forked from or the PR) - * 2. We pass the "originalGitCommitSha" and the branch of the SOURCE - * 3. Now we evaluate the comparison against your files to see if anything is "out of sync" or in "conflict" and if so we break out and tell user - * 4. If you sandbox has a PR we want to compare your sandbox with the BASE (This being the GIT info your sanbox forked from) - * 5. We pass the "originalGitCommitSha" and the branch of the BASE - * 6. Now we evaluate the comparison against your files to see if anything is "out of sync" or in "conflict" and if so we break out and tell user - */ -export const loadGitSource = async ({ state, actions, effects }: Context) => { - const sandbox = state.editor.currentSandbox!; - state.git.isExported = false; - state.git.pr = null; - state.git.repoTitle = ''; - - if ( - !state.user || - !state.user.integrations.github || - !sandbox.originalGit || - !hasPermission(sandbox.authorization, 'write_code') - ) { - return; - } - - state.git.isFetching = true; - - // We go grab the current version of the source - try { - await actions.git._loadSourceSandbox(); - } catch (error) { - actions.internal.handleError({ - error, - message: - 'Could not load the source sandbox for this GitHub sandbox, please refresh or report the issue.', - }); - return; - } - - try { - state.git.permission = await effects.api.getGitRights(sandbox.id); - } catch (error) { - actions.internal.handleError({ - error, - message: - 'Could not get information about your permissions, please refresh or report the issue.', - }); - return; - } - - // Now let us compare whatever has changed between our current - // state and the source - try { - await actions.git._compareWithSource(); - } catch (error) { - actions.internal.handleError({ - error, - message: - 'We were not able to compare the content with the source, please refresh or report the issue.', - }); - return; - } - - if (state.git.gitState === SandboxGitState.SYNCED && sandbox.prNumber) { - try { - await actions.git._compareWithBase(); - } catch (error) { - actions.internal.handleError({ - error, - message: - 'We were not able to compare the content with the PR, please refresh or report the issue.', - }); - return; - } - } - - actions.git._setGitChanges(); - state.git.isFetching = false; -}; - -export const createRepoFiles = ({ effects }: Context, sandbox: Sandbox) => { - return effects.git.export(sandbox); -}; - -export const createRepoClicked = async ({ - state, - effects, - actions, -}: Context) => { - effects.analytics.track('GitHub - Create Repo'); - const { repoTitle } = state.git; - const modulesNotSaved = !state.editor.isAllModulesSynced; - - if (!repoTitle) { - state.git.error = 'Repo name cannot be empty'; - return; - } - - if (/\s/.test(repoTitle.trim())) { - state.git.error = 'Repo name cannot contain spaces'; - return; - } - - if (modulesNotSaved) { - state.git.error = 'All files need to be saved'; - return; - } - - state.git.isExported = false; - state.currentModal = 'exportGithub'; - - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - try { - const githubData = await effects.git.export(sandbox); - if (!githubData) { - return; - } - const git = await effects.api.createGit(sandbox.id, repoTitle, githubData); - - if (!git) { - effects.notificationToast.error( - 'Unable to create git repo, please try again' - ); - return; - } - - git.commitSha = null; - state.git.isExported = true; - state.currentModal = null; - - // Redirect to CodeSandbox Projects - window.location.href = CSBProjectGitHubRepository({ - owner: git.username, - repo: git.repo, - welcome: true, - }); - } catch (error) { - actions.internal.handleError({ - error, - message: - 'Unable to create the repo. Please refresh and try again or report issue.', - }); - } -}; - -export const openSourceSandbox = ({ state, effects }: Context) => { - effects.analytics.track('GitHub - Open Source Sandbox'); - const git = state.editor.currentSandbox!.baseGit - ? state.editor.currentSandbox!.baseGit - : state.editor.currentSandbox!.originalGit; - - effects.router.updateSandboxUrl({ git }); -}; - -/* - Due to us creating new urls when syncing source, we have to move these updates - from the source back to the sandbox -*/ -export const _updateBinaryUploads = async ( - { state, actions }: Context, - changes: GitChanges -) => { - const binariesToUpdate = changes.added - .filter(change => change.encoding === 'base64') - .concat(changes.modified.filter(change => change.encoding === 'base64')); - - await Promise.all( - binariesToUpdate.map(change => { - const module = state.editor.modulesByPath[change.path] as Module; - const sourceModule = state.git.sourceModulesByPath[change.path]; - - actions.editor.codeChanged({ - moduleShortid: module.shortid, - code: sourceModule.code, - }); - return actions.editor.codeSaved({ - moduleShortid: module.shortid, - code: sourceModule.code, - cbID: null, - }); - }) - ); -}; - -export const createCommitClicked = async ({ - state, - effects, - actions, -}: Context) => { - effects.analytics.track('GitHub - Create Commit'); - const sandbox = state.editor.currentSandbox!; - const git = state.git; - - git.isCommitting = true; - - try { - if (git.gitState === SandboxGitState.SYNCED) { - await actions.git._compareWithSource(); - - if (sandbox.prNumber && git.gitState === SandboxGitState.SYNCED) { - await actions.git._compareWithBase(); - } - - if (state.git.gitState !== SandboxGitState.SYNCED) { - git.isCommitting = false; - return; - } - } - - const changes = await actions.git._getGitChanges(); - const commit = await effects.api.createGitCommit( - sandbox.id, - `${git.title}\n${git.description}`, - changes, - // If we have resolved a conflict and it is a PR source conflict, we need to commit - // it with a reference to the conflicting sha as well - git.gitState === SandboxGitState.RESOLVED_PR_BASE && git.baseCommitSha - ? [git.sourceCommitSha!, git.baseCommitSha] - : [git.sourceCommitSha!] - ); - - // We need to load the source again as it has now changed. We can not optimistically deal with - // this, cause you might have added a binary - sandbox.originalGit!.commitSha = commit.sha; - sandbox.originalGitCommitSha = commit.sha; - await actions.git._loadSourceSandbox(); - await actions.git._updateBinaryUploads(changes); - actions.git._setGitChanges(); - state.git.isCommitting = false; - state.git.title = ''; - state.git.description = ''; - state.git.conflicts = []; - state.git.gitState = SandboxGitState.SYNCED; - - effects.notificationToast.success('Successfully created your commit'); - } catch (error) { - state.git.isCommitting = false; - - if (error.message.includes('code 422')) { - if (!sandbox.originalGit) return; - effects.notificationToast.add({ - message: `You do not seem to have access to commit to ${sandbox.originalGit.username}/${sandbox.originalGit.repo}. Please read the documentation to grant access.`, - title: 'Error creating commit', - status: NotificationStatus.ERROR, - actions: { - primary: { - label: 'Open documentation', - run: () => { - effects.browser.openWindow( - 'docs/git#committing-to-organizations' - ); - }, - }, - }, - }); - } else { - actions.internal.handleError({ - error, - message: - 'We were unable to create your commit. Please try again or report the issue.', - }); - } - } -}; - -export const titleChanged = ({ state }: Context, title: string) => { - state.git.title = title; -}; - -export const descriptionChanged = ({ state }: Context, description: string) => { - state.git.description = description; -}; - -export const createPrClicked = async ({ state, effects, actions }: Context) => { - effects.analytics.track('GitHub - Open PR'); - const git = state.git; - git.isCreatingPr = true; - git.pr = null; - const sandbox = state.editor.currentSandbox!; - const { id } = sandbox; - - try { - if (git.gitState === SandboxGitState.SYNCED) { - await actions.git._compareWithSource(); - - if (state.git.gitState !== SandboxGitState.SYNCED) { - git.isCreatingPr = false; - return; - } - } - - const changes = await actions.git._getGitChanges(); - const pr = await effects.api.createGitPr( - id, - state.git.title, - state.git.description, - changes - ); - - sandbox.baseGit = { - ...sandbox.originalGit, - } as GitInfo; - sandbox.baseGitCommitSha = sandbox.originalGit!.commitSha; - sandbox.originalGit = { - branch: pr.branch, - commitSha: pr.commitSha, - repo: pr.repo, - username: pr.username, - path: '', - }; - sandbox.originalGitCommitSha = pr.commitSha; - sandbox.prNumber = pr.number; - git.pr = pr; - - await actions.git._loadSourceSandbox(); - await actions.git._updateBinaryUploads(changes); - actions.git._setGitChanges(); - - git.title = ''; - git.description = ''; - state.git.conflicts = []; - state.git.gitState = SandboxGitState.SYNCED; - - git.isCreatingPr = false; - - effects.notificationToast.add({ - title: 'Successfully created your PR', - message: '', - status: convertTypeToStatus('success'), - actions: { - primary: { - label: 'Open PR', - run: () => { - effects.browser.openWindow( - `https://github.com/${sandbox.baseGit!.username}/${ - sandbox.baseGit!.repo - }/pull/${sandbox.prNumber!}` - ); - }, - }, - }, - }); - } catch (error) { - git.isCreatingPr = false; - actions.internal.handleError({ - error, - message: - 'We were unable to create your PR. Please try again or report the issue.', - }); - } -}; - -export const updateGitChanges = pipe(debounce(500), ({ actions }: Context) => { - actions.git._setGitChanges(); -}); - -export const resolveConflicts = async ( - { state, actions, effects }: Context, - module: Module -) => { - const conflict = state.git.conflicts.find( - conflictItem => module.path === '/' + conflictItem.filename - ); - - if (conflict && module.code.indexOf('<<<<<<< CodeSandbox') === -1) { - state.git.conflicts.splice(state.git.conflicts.indexOf(conflict), 1); - - await actions.editor.codeSaved({ - moduleShortid: module.shortid, - code: module.code, - cbID: null, - }); - - effects.analytics.track('GitHub - Resolve Conflicts'); - actions.git._tryResolveConflict(); - } -}; - -export const addConflictedFile = async ( - { state, actions }: Context, - conflict: GitFileCompare -) => { - state.git.conflictsResolving.push(conflict.filename); - await actions.files.createModulesByPath({ - files: { - [conflict.filename]: { content: conflict.content!, isBinary: false }, - }, - }); - state.git.sourceModulesByPath[ - '/' + conflict.filename - ].code = conflict.content!; - - state.git.conflictsResolving.splice( - state.git.conflictsResolving.indexOf(conflict.filename), - 1 - ); - state.git.conflicts.splice(state.git.conflicts.indexOf(conflict), 1); - - actions.git._tryResolveConflict(); -}; - -export const ignoreConflict = ( - { state, actions }: Context, - conflict: GitFileCompare -) => { - state.git.conflicts.splice(state.git.conflicts.indexOf(conflict), 1); - - actions.git._tryResolveConflict(); -}; - -export const deleteConflictedFile = async ( - { state, actions }: Context, - conflict: GitFileCompare -) => { - state.git.conflictsResolving.push(conflict.filename); - const module = state.editor.modulesByPath['/' + conflict.filename]; - - await actions.files.moduleDeleted({ moduleShortid: module.shortid }); - delete state.git.sourceModulesByPath['/' + conflict.filename]; - - state.git.conflictsResolving.splice( - state.git.conflictsResolving.indexOf(conflict.filename), - 1 - ); - state.git.conflicts.splice(state.git.conflicts.indexOf(conflict), 1); - - actions.git._tryResolveConflict(); -}; - -export const diffConflictedFile = async ( - { state, actions }: Context, - conflict: GitFileCompare -) => { - const module = state.editor.modulesByPath['/' + conflict.filename] as Module; - - await actions.editor.moduleSelected({ id: module.id }); - - actions.editor.setCode({ - moduleShortid: module.shortid, - code: createDiff(module.code, conflict.content), - }); -}; - -export const resolveOutOfSync = async ({ - state, - actions, - effects, -}: Context) => { - effects.analytics.track('GitHub - Resolve out of sync'); - const git = state.git; - const { added, deleted, modified } = git.outOfSyncUpdates; - - git.isResolving = true; - - const sandbox = state.editor.currentSandbox!; - - // When we have a PR and the source is out of sync with base, we need to create a commit to update it. We do this - // first, because we need the new source to deal with binary files - if (git.gitState === SandboxGitState.OUT_OF_SYNC_PR_BASE) { - const changes: GitChanges = { - added: added.map(change => ({ - path: '/' + change.filename, - content: change.content!, - encoding: change.isBinary ? 'base64' : 'utf-8', - })), - deleted: deleted.map(change => '/' + change.filename), - modified: modified.map(change => ({ - path: '/' + change.filename, - content: change.content!, - encoding: change.isBinary ? 'base64' : 'utf-8', - })), - }; - const commit = await effects.api.createGitCommit( - sandbox.id, - `Update from ${sandbox.baseGit!.branch}`, - changes, - [git.sourceCommitSha!] - ); - sandbox.originalGit!.commitSha = commit.sha; - sandbox.originalGitCommitSha = commit.sha; - - // If working directly we just need to update the commitSha, as - // we are already in sync now - } else { - await effects.api.saveGitOriginalCommitSha( - sandbox.id, - git.sourceCommitSha! - ); - sandbox.originalGit!.commitSha = git.sourceCommitSha; - sandbox.originalGitCommitSha = git.sourceCommitSha; - } - - await actions.git._loadSourceSandbox(); - - if (added.length) { - await actions.files.createModulesByPath({ - files: added.reduce((aggr, change) => { - aggr[change.filename] = change.isBinary - ? { - content: git.sourceModulesByPath['/' + change.filename].code, - isBinary: true, - uploadId: git.sourceModulesByPath['/' + change.filename].uploadId, - sha: git.sourceModulesByPath['/' + change.filename].sha, - } - : { content: change.content }; - - return aggr; - }, {}), - }); - } - - if (deleted.length) { - await Promise.all( - deleted.map(change => { - const module = state.editor.modulesByPath['/' + change.filename]; - - return actions.files.moduleDeleted({ moduleShortid: module.shortid }); - }) - ); - } - if (modified.length) { - await Promise.all( - modified.map(change => { - const module = state.editor.modulesByPath['/' + change.filename]; - - // If we are dealing with a private binary change, we need to bluntly update - // the module - if (git.sourceModulesByPath['/' + change.filename].sha) { - const code = git.sourceModulesByPath['/' + change.filename].code; - const uploadId = git.sourceModulesByPath['/' + change.filename] - .uploadId!; - const sha = git.sourceModulesByPath['/' + change.filename].sha!; - - const sandboxModule = sandbox.modules.find( - moduleItem => moduleItem.shortid === module.shortid - )!; - sandboxModule.code = code; - sandboxModule.uploadId = uploadId; - sandboxModule.sha = sha; - - return effects.api - .saveModulePrivateUpload(sandbox.id, module.shortid, { - code, - uploadId, - sha, - }) - .then(() => {}); - } - actions.editor.setCode({ - moduleShortid: module.shortid, - code: change.isBinary - ? git.sourceModulesByPath['/' + change.filename].code - : change.content!, - }); - return actions.editor.codeSaved({ - moduleShortid: module.shortid, - code: change.isBinary - ? git.sourceModulesByPath['/' + change.filename].code - : change.content!, - cbID: null, - }); - }) - ); - } - - actions.git._setGitChanges(); - git.outOfSyncUpdates.added = []; - git.outOfSyncUpdates.deleted = []; - git.outOfSyncUpdates.modified = []; - git.gitState = SandboxGitState.SYNCED; - git.isResolving = false; -}; - -export const _setGitChanges = ({ state }: Context) => { - const changes: { - added: string[]; - deleted: string[]; - modified: string[]; - } = { - added: [], - deleted: [], - modified: [], - }; - - state.editor.currentSandbox!.modules.forEach(module => { - if (!(module.path in state.git.sourceModulesByPath)) { - changes.added.push(module.path); - } else if ( - (module.sha && - state.git.sourceModulesByPath[module.path].sha !== module.sha) || - (!module.sha && - state.git.sourceModulesByPath[module.path].code !== module.code) - ) { - changes.modified.push(module.path); - } - }); - Object.keys(state.git.sourceModulesByPath).forEach(path => { - if (!state.editor.modulesByPath[path]) { - changes.deleted.push(path); - } - }); - state.git.gitChanges = changes; -}; - -export const _evaluateGitChanges = async ( - { state }: Context, - changes: GitFileCompare[] -) => { - const conflicts = changes.reduce((aggr, change) => { - const path = '/' + change.filename; - - // We are in conflict if a file has been removed in the source and the - // sandbox has made changes to it - if ( - change.status === 'removed' && - state.editor.modulesByPath[path] && - !(state.editor.modulesByPath[path] as Module).isBinary && - (state.editor.modulesByPath[path] as Module).code !== change.content - ) { - return aggr.concat(change); - } - - // We are in conflict if a file has been modified in the source, but removed - // from the sandbox - if (change.status === 'modified' && !state.editor.modulesByPath[path]) { - return aggr.concat(change); - } - - // We are in conflict if the source changed the file and sandbox also changed the file - if ( - change.status === 'modified' && - !(state.editor.modulesByPath[path] as Module).isBinary && - (state.editor.modulesByPath[path] as Module).code !== change.content && - (state.editor.modulesByPath[path] as Module).code !== - state.git.sourceModulesByPath[path].code - ) { - return aggr.concat(change); - } - - return aggr; - }, []); - const toUpdate: { - added: GitFileCompare[]; - deleted: GitFileCompare[]; - modified: GitFileCompare[]; - } = { added: [], deleted: [], modified: [] }; - - if (changes.length) { - state.git.outOfSyncUpdates = changes.reduce((aggr, change) => { - // If the change is a conflict, we do not set it up for a Sandbox update, we need - // to handle the conflict explicitly - if (conflicts.includes(change)) { - return aggr; - } - - // When not a conflict we check if the changes differs from the Sandbox. Any changes - // will update the Sandbox - if ( - (change.status === 'added' && - !state.editor.modulesByPath['/' + change.filename]) || - (change.status === 'removed' && - state.editor.modulesByPath['/' + change.filename]) || - (change.status === 'modified' && - (state.editor.modulesByPath['/' + change.filename] as Module).code !== - change.content) - ) { - aggr[change.status === 'removed' ? 'deleted' : change.status].push( - change - ); - } - - return aggr; - }, toUpdate); - } - - return { - changesCount: - toUpdate.added.length + - toUpdate.modified.length + - toUpdate.deleted.length, - conflicts, - }; -}; - -export const _loadSourceSandbox = async ({ state, effects }: Context) => { - const sandbox = state.editor.currentSandbox!; - const { originalGit } = sandbox; - - if (!originalGit) { - return; - } - - const sourceSandbox = await effects.api.getSandbox( - `github/${originalGit.username}/${ - originalGit.repo - }/tree/${sandbox.originalGitCommitSha!}/${originalGit.path}` - ); - - state.editor.sandboxes[sourceSandbox.id] = sourceSandbox; - state.git.sourceSandboxId = sourceSandbox.id; - - state.git.sourceModulesByPath = sourceSandbox.modules.reduce( - (aggr, module) => { - const path = getModulePath( - sourceSandbox.modules, - sourceSandbox.directories, - module.id - ); - module.path = path; - if (path) { - aggr[path] = { - code: module.code, - isBinary: module.isBinary, - uploadId: module.uploadId, - sha: module.sha, - }; - } - - return aggr; - }, - {} - ); -}; - -export const _compareWithSource = async ({ - state, - effects, - actions, -}: Context) => { - const sandbox = state.editor.currentSandbox!; - const originalGitCommitSha = sandbox.originalGitCommitSha; - - try { - const originalChanges = await effects.api.compareGit( - sandbox.id, - sandbox.originalGitCommitSha!, - sandbox.originalGit!.branch, - true - ); - const updates = await actions.git._evaluateGitChanges( - originalChanges.files - ); - - state.git.sourceCommitSha = originalChanges.headCommitSha; - state.git.conflicts = updates.conflicts; - - if (updates.changesCount || updates.conflicts.length) { - effects.notificationToast.add({ - message: `The sandbox is out of sync with "${ - sandbox.originalGit!.branch - }" ${updates.conflicts.length ? 'and there are conflicts' : ''}`, - title: 'Out of sync', - status: convertTypeToStatus('notice'), - sticky: false, - actions: { - primary: { - label: 'Resolve', - run: () => { - actions.workspace.setWorkspaceItem({ item: 'github' }); - }, - }, - secondary: { - label: 'See changes', - run: () => { - effects.browser.openWindow( - `https://github.com/${sandbox.originalGit!.username}/${ - sandbox.originalGit!.repo - }/compare/${originalGitCommitSha}...${ - sandbox.originalGit!.branch - }` - ); - }, - }, - }, - }); - effects.preview.refresh(); - state.git.gitState = updates.conflicts.length - ? SandboxGitState.CONFLICT_SOURCE - : SandboxGitState.OUT_OF_SYNC_SOURCE; - } else { - state.git.gitState = SandboxGitState.SYNCED; - } - } catch (e) { - // if there is a base git the issue is that the new branch does not exist, if not carry on - if (sandbox.baseGit) { - state.currentModal = 'notFoundBranchModal'; - } else { - throw new Error(); - } - } -}; - -export const _compareWithBase = async ({ - state, - effects, - actions, -}: Context) => { - const sandbox = state.editor.currentSandbox!; - - state.git.pr = await effects.api.getGitPr(sandbox.id, sandbox.prNumber!); - state.git.sourceCommitSha = state.git.pr.commitSha; - state.git.baseCommitSha = state.git.pr.baseCommitSha; - - const baseChanges = await effects.api.compareGit( - sandbox.id, - sandbox.originalGitCommitSha!, - sandbox.baseGit!.branch, - true - ); - - const updates = await actions.git._evaluateGitChanges(baseChanges.files); - - state.git.baseCommitSha = baseChanges?.headCommitSha; - state.git.conflicts = updates.conflicts; - - if (updates.changesCount || updates.conflicts.length) { - effects.notificationToast.add({ - message: `The sandbox is out of sync with "${sandbox.baseGit!.branch}" ${ - updates.conflicts.length ? 'and there are conflicts' : '' - }`, - title: 'Out of sync', - status: convertTypeToStatus('notice'), - sticky: false, - actions: { - primary: { - label: 'Resolve', - run: () => { - actions.workspace.setWorkspaceItem({ item: 'github' }); - }, - }, - secondary: { - label: 'See changes', - run: () => { - effects.browser.openWindow( - `https://github.com/${sandbox.originalGit!.username}/${ - sandbox.originalGit!.repo - }/compare/${sandbox.baseGit!.branch}...${ - sandbox.originalGit!.branch - }` - ); - }, - }, - }, - }); - effects.preview.refresh(); - state.git.gitState = updates.conflicts.length - ? SandboxGitState.CONFLICT_PR_BASE - : SandboxGitState.OUT_OF_SYNC_PR_BASE; - } else { - state.git.gitState = SandboxGitState.SYNCED; - } -}; - -export const _getGitChanges = async ({ state, effects }: Context) => { - const git = state.git; - const sandbox = state.editor.currentSandbox!; - - return { - added: await Promise.all( - git.gitChanges.added - .map(async path => { - const module = sandbox.modules.find( - moduleItem => moduleItem.path === path - ); - - if (!module) { - logBreadcrumb({ - message: `Tried adding Git change, but paths don't match. Expected: ${path}, paths available: ${JSON.stringify( - sandbox.modules.map(m => ({ - shortid: m.shortid, - path: m.path, - isBinary: m.isBinary, - })) - )}`, - }); - const err = new Error('Unable to add module to git changes'); - captureException(err); - - return false; - } - - if (module!.isBinary) { - return { - path, - content: await effects.http.blobToBase64(module!.code), - encoding: 'base64' as 'base64', - }; - } - - return { - path, - content: module!.code, - encoding: 'utf-8' as 'utf-8', - }; - }) - .filter(Boolean) - ), - deleted: git.gitChanges.deleted, - modified: git.gitChanges.modified.map(path => { - const module = sandbox.modules.find( - moduleItem => moduleItem.path === path - ); - - // A binary can not be modified, because we have no mechanism for comparing - // private binary files, as their urls are based on moduleId (which is different across sandboxes) - return { - path, - content: module!.code, - encoding: 'utf-8', - }; - }), - } as GitChanges; -}; - -export const _tryResolveConflict = async ({ - state, - effects, - actions, -}: Context) => { - const git = state.git; - actions.git._setGitChanges(); - - if (git.conflicts.length === 0) { - git.gitState = - git.gitState === SandboxGitState.CONFLICT_PR_BASE - ? SandboxGitState.RESOLVED_PR_BASE - : SandboxGitState.RESOLVED_SOURCE; - } - - // When the conflict is from a PR base and we just override it, we still have to create a commit to update the PR - if ( - git.gitState === SandboxGitState.RESOLVED_PR_BASE && - !git.gitChanges.added.length && - !git.gitChanges.deleted.length && - !git.gitChanges.modified.length - ) { - state.git.isCommitting = true; - const sandbox = state.editor.currentSandbox!; - const changes = await actions.git._getGitChanges(); - state.git.title = 'Resolve conflict'; - const commit = await effects.api.createGitCommit( - sandbox.id, - 'Resolved conflict', - changes, - git.baseCommitSha - ? [git.sourceCommitSha!, git.baseCommitSha] - : [git.sourceCommitSha!] - ); - sandbox.originalGit!.commitSha = commit.sha; - sandbox.originalGitCommitSha = commit.sha; - git.isCommitting = false; - git.title = ''; - git.description = ''; - git.gitState = SandboxGitState.SYNCED; - } -}; - -export const linkToGitSandbox = async ( - { state, effects, actions }: Context, - sandboxId: string -) => { - if (!state.editor.currentSandbox) return; - try { - state.git.isLinkingToGitSandbox = true; - const newGitData = await effects.api.makeGitSandbox(sandboxId); - state.editor.currentSandbox = { - ...state.editor.currentSandbox, - originalGitCommitSha: newGitData.originalGitCommitSha, - originalGit: newGitData.originalGit, - }; - await actions.git.loadGitSource(); - } catch (error) { - actions.internal.handleError({ - error, - message: - 'There has been a problem connecting your sandbox to the GitHub repo. Please try again.', - }); - } finally { - state.git.isLinkingToGitSandbox = false; - } -}; diff --git a/packages/app/src/app/overmind/namespaces/git/index.ts b/packages/app/src/app/overmind/namespaces/git/index.ts deleted file mode 100755 index 0d573be52de..00000000000 --- a/packages/app/src/app/overmind/namespaces/git/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { state } from './state'; -import * as actions from './actions'; - -export { state, actions }; diff --git a/packages/app/src/app/overmind/namespaces/git/internalActions.ts b/packages/app/src/app/overmind/namespaces/git/internalActions.ts deleted file mode 100644 index 38538198ae2..00000000000 --- a/packages/app/src/app/overmind/namespaces/git/internalActions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Context } from 'app/overmind'; - -export const fetchGitChanges = async ({ state, effects }: Context) => { - if (!state.editor.currentSandbox) { - return; - } - - const { id } = state.editor.currentSandbox; - - state.git.isFetching = true; - state.git.gitChanges = await effects.api.getGitChanges(id); - state.git.isFetching = false; -}; diff --git a/packages/app/src/app/overmind/namespaces/git/state.ts b/packages/app/src/app/overmind/namespaces/git/state.ts deleted file mode 100755 index afc3160d79f..00000000000 --- a/packages/app/src/app/overmind/namespaces/git/state.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - GitFileCompare, - GitPathChanges, - GitPr, - SandboxGitState, -} from '@codesandbox/common/lib/types'; - -type State = { - gitState: SandboxGitState; - repoTitle: string; - sourceSandboxId: string | null; - error: string | null; - isExported: boolean; - showExportedModal: boolean; - isFetching: boolean; - title: string; - description: string; - pr: GitPr | null; - isCommitting: boolean; - isCreatingPr: boolean; - conflicts: GitFileCompare[]; - gitChanges: GitPathChanges; - sourceGitChanges: { - [path: string]: GitFileCompare; - }; - sourceModulesByPath: { - [path: string]: { - code: string; - isBinary: boolean; - uploadId?: string; - sha?: string; - }; - }; - permission: 'admin' | 'write' | 'read'; - conflictsResolving: string[]; - outOfSyncUpdates: { - added: GitFileCompare[]; - deleted: GitFileCompare[]; - modified: GitFileCompare[]; - }; - // Rename source to original - sourceCommitSha: string | null; - baseCommitSha: string | null; - isResolving: boolean; - isLinkingToGitSandbox: boolean; -}; - -export const state: State = { - gitState: SandboxGitState.SYNCING, - sourceCommitSha: null, - baseCommitSha: null, - repoTitle: '', - sourceSandboxId: null, - error: null, - isExported: false, - showExportedModal: false, - isFetching: false, - title: '', - description: '', - pr: null, - isCommitting: false, - isCreatingPr: false, - isResolving: false, - permission: 'read', - sourceModulesByPath: {}, - conflicts: [], - gitChanges: { - added: [], - deleted: [], - modified: [], - }, - sourceGitChanges: {}, - conflictsResolving: [], - outOfSyncUpdates: { - added: [], - deleted: [], - modified: [], - }, - isLinkingToGitSandbox: false, -}; diff --git a/packages/app/src/app/overmind/namespaces/git/utils.ts b/packages/app/src/app/overmind/namespaces/git/utils.ts deleted file mode 100644 index d7b998d962c..00000000000 --- a/packages/app/src/app/overmind/namespaces/git/utils.ts +++ /dev/null @@ -1,59 +0,0 @@ -export function createDiff(textA, textB) { - const linesA = textA.split('\n'); - const linesB = textB.split('\n'); - const lineCount = Math.max(linesA.length, linesB.length); - - let result: string[] = []; - let currentConflict: { a: string[]; b: string[] } | null = null; - - function closeConflict(line) { - result = result.concat( - '<<<<<<< CodeSandbox', - currentConflict!.a, - '=======', - currentConflict!.b, - '>>>>>>> GitHub', - line - ); - currentConflict = null; - } - - for (let x = 0; x < lineCount; x++) { - if (x in linesA && x in linesB && linesA[x] !== linesB[x]) { - if (currentConflict && linesA[x + 1] === linesB[x]) { - closeConflict(linesA[x]); - linesB.splice(x, 0, linesA[x]); - } else if (currentConflict && linesA[x] === linesB[x + 1]) { - closeConflict(linesB[x]); - currentConflict = null; - linesA.splice(x, 0, linesB[x]); - } else if (currentConflict) { - currentConflict.a.push(linesA[x]); - currentConflict.b.push(linesB[x]); - } else { - currentConflict = { - a: [linesA[x]], - b: [linesB[x]], - }; - } - } else if (x in linesA && !(x in linesB)) { - if (currentConflict) { - closeConflict(linesA[x]); - } else { - result.push(linesA[x]); - } - } else if (!(x in linesA) && x in linesB) { - if (currentConflict) { - closeConflict(linesB[x]); - } else { - result.push(linesB[x]); - } - } else if (currentConflict) { - closeConflict(linesA[x]); - } else { - result.push(linesA[x]); - } - } - - return result.join('\n'); -} diff --git a/packages/app/src/app/overmind/namespaces/live/actions.ts b/packages/app/src/app/overmind/namespaces/live/actions.ts deleted file mode 100755 index 2a8b21c8d0b..00000000000 --- a/packages/app/src/app/overmind/namespaces/live/actions.ts +++ /dev/null @@ -1,465 +0,0 @@ -import { - IModuleStateModule, - LiveMessage, - LiveMessageEvent, - RoomInfo, - UserSelection, - UserViewRange, -} from '@codesandbox/common/lib/types'; -import { logBreadcrumb } from '@codesandbox/common/lib/utils/analytics/sentry'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { Context } from 'app/overmind'; -import { withLoadApp } from 'app/overmind/factories'; -import getItems from 'app/overmind/utils/items'; -import { filter, fork, pipe } from 'overmind'; - -import * as internalActions from './internalActions'; -import * as liveMessage from './liveMessageOperators'; - -export const internal = internalActions; - -export const signInToRoom = withLoadApp( - async ({ actions, state }: Context, roomId: string) => { - state.signInModalOpen = true; - - if (state.isLoggedIn) { - await actions.live.roomJoined(roomId); - } - } -); - -export const onOperationError = ( - { actions }: Context, - { - moduleShortid, - moduleInfo, - }: { - moduleShortid: string; - moduleInfo: IModuleStateModule; - } -) => { - actions.live.internal.initializeModuleFromState({ - moduleShortid, - moduleInfo, - }); -}; - -export const roomJoined = withLoadApp( - async ({ actions, effects, state }: Context, roomId: string) => { - if (!state.isLoggedIn) { - return; - } - - await effects.vscode.initialized; - await effects.vscode.closeAllTabs(); - - state.live.joinSource = 'live'; - - if (state.live.isLive) { - actions.live.internal.disconnect(); - } - - const sandbox = await actions.live.internal.initialize(roomId); - - if (!sandbox) { - return; - } - - if (state.updateStatus === 'available') { - const modal = 'liveVersionMismatch'; - effects.analytics.track('Open Modal', { modal }); - state.currentModal = modal; - } - - await actions.internal.setCurrentSandbox(sandbox); - - actions.editor.listenToSandboxChanges({ sandboxId: sandbox.id }); - const items = getItems(state); - const defaultItem = items.find(i => i.defaultOpen) || items[0]; - - state.workspace.openedWorkspaceItem = defaultItem.id; - - await effects.vscode.changeSandbox(sandbox, fs => { - state.editor.modulesByPath = fs; - }); - - effects.vscode.openModule(state.editor.currentModule); - - if ( - sandbox.featureFlags.comments && - hasPermission(sandbox.authorization, 'comment') - ) { - actions.comments.getSandboxComments(sandbox.id); - } - - state.editor.isLoading = false; - } -); - -export const createLiveClicked = async ( - { actions, effects, state }: Context, - sandboxId: string -) => { - effects.analytics.track('Create Live Session'); - - const roomId = await effects.api.createLiveRoom(sandboxId); - const sandbox = await actions.live.internal.initialize(roomId); - const currentSandbox = state.editor.currentSandbox; - - if (!sandbox || !currentSandbox) { - effects.notificationToast.error('Unable to create live room'); - return; - } - - Object.assign(sandbox, { - modules: sandbox.modules.map(module => { - const currentModule = currentSandbox.modules.find( - currentSandboxModule => currentSandboxModule.shortid === module.shortid - ); - return { - ...module, - code: currentModule ? currentModule.code : '', - }; - }), - }); - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create(sandbox); -}; - -export const liveMessageReceived = pipe( - filter((_, payload: LiveMessage) => - Object.values(LiveMessageEvent).includes(payload.event) - ), - filter(({ state }: Context) => - Boolean(state.live.isLive && state.live.roomInfo) - ), - fork('event', { - [LiveMessageEvent.JOIN]: liveMessage.onJoin, - [LiveMessageEvent.SAVE]: liveMessage.onSave, - [LiveMessageEvent.MODULE_STATE]: liveMessage.onModuleState, - [LiveMessageEvent.USER_ENTERED]: liveMessage.onUserEntered, - [LiveMessageEvent.USERS_CHANGED]: liveMessage.onUsersChanged, - [LiveMessageEvent.USER_LEFT]: liveMessage.onUserLeft, - [LiveMessageEvent.EXTERNAL_RESOURCES]: liveMessage.onExternalResources, - [LiveMessageEvent.MODULE_SAVED]: liveMessage.onModuleSaved, - [LiveMessageEvent.MODULE_CREATED]: liveMessage.onModuleCreated, - [LiveMessageEvent.MODULE_MASS_CREATED]: liveMessage.onModuleMassCreated, - [LiveMessageEvent.MODULE_UPDATED]: liveMessage.onModuleUpdated, - [LiveMessageEvent.MODULE_DELETED]: liveMessage.onModuleDeleted, - [LiveMessageEvent.DIRECTORY_CREATED]: liveMessage.onDirectoryCreated, - [LiveMessageEvent.DIRECTORY_UPDATED]: liveMessage.onDirectoryUpdated, - [LiveMessageEvent.DIRECTORY_DELETED]: liveMessage.onDirectoryDeleted, - [LiveMessageEvent.USER_SELECTION]: liveMessage.onUserSelection, - [LiveMessageEvent.USER_CURRENT_MODULE]: liveMessage.onUserCurrentModule, - [LiveMessageEvent.USER_VIEW_RANGE]: liveMessage.onUserViewRange, - [LiveMessageEvent.LIVE_MODE]: liveMessage.onLiveMode, - [LiveMessageEvent.LIVE_CHAT_ENABLED]: liveMessage.onLiveChatEnabled, - [LiveMessageEvent.LIVE_ADD_EDITOR]: liveMessage.onLiveAddEditor, - [LiveMessageEvent.LIVE_REMOVE_EDITOR]: liveMessage.onLiveRemoveEditor, - [LiveMessageEvent.OPERATION]: liveMessage.onOperation, - [LiveMessageEvent.CONNECTION_LOSS]: liveMessage.onConnectionLoss, - [LiveMessageEvent.DISCONNECT]: liveMessage.onDisconnect, - [LiveMessageEvent.OWNER_LEFT]: liveMessage.onOwnerLeft, - [LiveMessageEvent.CHAT]: liveMessage.onChat, - [LiveMessageEvent.NOTIFICATION]: liveMessage.onNotification, - }) -); - -export const applyTransformation = async ( - { effects }: Context, - { - operation, - moduleShortid, - }: { - operation: any; - moduleShortid: string; - } -) => { - try { - await effects.vscode.applyOperation(moduleShortid, operation); - } catch (error) { - // Do not care about the error, but something went wrong and we - // need a full sync - logBreadcrumb({ - category: 'ot', - message: `Apply transformation to VSCode failed ${JSON.stringify({ - moduleShortid, - operation, - })}`, - }); - effects.live.sendModuleStateSyncRequest(); - } -}; - -export const sendCurrentSelection = ({ state, effects }: Context) => { - if (!state.live.roomInfo) { - return; - } - - const { liveUserId } = state.live; - if (liveUserId && state.live.currentSelection) { - effects.live.sendUserSelection( - state.editor.currentModuleShortid, - liveUserId, - state.live.currentSelection - ); - } -}; - -export const sendCurrentViewRange = ({ state, effects }: Context) => { - if (!state.live.roomInfo) { - return; - } - - if (!state.live.isCurrentEditor) { - return; - } - - const { liveUserId, currentViewRange } = state.live; - if (liveUserId && currentViewRange) { - effects.live.sendUserViewRange( - state.editor.currentModuleShortid, - liveUserId, - currentViewRange - ); - } -}; - -export const onViewRangeChanged = ( - { state, effects }: Context, - viewRange: UserViewRange -) => { - if (!state.live.roomInfo) { - return; - } - - if (state.live.isCurrentEditor) { - const { liveUserId } = state.live; - const moduleShortid = state.editor.currentModuleShortid; - if (!liveUserId) { - return; - } - - state.live.currentViewRange = viewRange; - const userIndex = state.live.roomInfo.users.findIndex( - u => u.id === liveUserId - ); - - if (userIndex !== -1) { - if (state.live.roomInfo.users[userIndex]) { - state.live.roomInfo.users[ - userIndex - ].currentModuleShortid = moduleShortid; - - state.live.roomInfo.users[userIndex].viewRange = viewRange; - - effects.live.sendUserViewRange(moduleShortid, liveUserId, viewRange); - } - } - } -}; - -export const onSelectionChanged = ( - { state, effects }: Context, - selection: UserSelection -) => { - if (!state.live.roomInfo) { - return; - } - - const { liveUserId } = state.live; - const moduleShortid = state.editor.currentModuleShortid; - if (!moduleShortid || !liveUserId) { - return; - } - - state.live.currentSelection = selection; - const userIndex = state.live.roomInfo.users.findIndex( - u => u.id === liveUserId - ); - - if (userIndex > -1) { - const user = state.live.roomInfo.users[userIndex]; - if (user) { - user.currentModuleShortid = moduleShortid; - user.selection = selection; - - effects.live.sendUserSelection(moduleShortid, liveUserId, selection); - } - } -}; - -export const onModeChanged = ( - { effects, state }: Context, - mode: RoomInfo['mode'] -) => { - if (state.live.isOwner && state.live.roomInfo) { - state.live.roomInfo.mode = mode; - effects.live.sendLiveMode(mode); - } -}; - -export const onAddEditorClicked = ( - { effects, state }: Context, - liveUserId: string -) => { - if (!state.live.roomInfo) { - return; - } - - state.live.roomInfo.editorIds.push(liveUserId); - - effects.live.sendEditorAdded(liveUserId); -}; - -export const onRemoveEditorClicked = ( - { effects, state }: Context, - liveUserId: string -) => { - if (!state.live.roomInfo) { - return; - } - - state.live.roomInfo.editorIds = state.live.roomInfo.editorIds.filter( - id => id !== liveUserId - ); - - effects.live.sendEditorRemoved(liveUserId); -}; - -export const onSessionCloseClicked = ({ actions, effects }: Context) => { - effects.live.sendClosed(); - actions.live.internal.disconnect(); -}; - -export const onNavigateAway = ({ actions, state }: Context) => { - if (state.live.isLive) { - actions.live.internal.disconnect(); - } -}; - -export const onToggleNotificationsHidden = ({ state }: Context) => { - state.live.notificationsHidden = !state.live.notificationsHidden; -}; - -export const onSendChat = ( - { effects }: Context, - { message }: { message: string } -) => { - effects.live.sendChat(message); -}; - -export const onChatEnabledToggle = ({ effects, state }: Context) => { - effects.analytics.track('Enable Live Chat'); - - if (state.live.isOwner && state.live.roomInfo) { - const chatEnabled = state.live.roomInfo.chatEnabled; - state.live.roomInfo.chatEnabled = !chatEnabled; - effects.live.sendChatEnabled(!chatEnabled); - } -}; - -export const onFollow = ( - { actions, effects, state }: Context, - { liveUserId }: { liveUserId: string } -) => { - if (!state.live.roomInfo) { - return; - } - - effects.analytics.track('Follow Along in Live'); - state.live.followingUserId = liveUserId; - actions.live.revealViewRange(liveUserId); - - if (state.editor.currentModule) { - // In case the selections were hidden first - actions.editor.internal.updateSelectionsOfModule({ - module: state.editor.currentModule, - }); - } -}; - -export const onUserLeft = ( - { state, actions }: Context, - { - liveUserId, - }: { - liveUserId: string; - } -) => { - if (!state.live.roomInfo) { - return; - } - - if (state.live.followingUserId && state.live.followingUserId === liveUserId) { - // Unfollow user if they are the one who left - actions.live.onStopFollow(); - } - - actions.live.internal.clearUserSelections(liveUserId); -}; - -export const onStopFollow = ({ state, actions }: Context) => { - if (!state.live.roomInfo) { - return; - } - - state.live.followingUserId = null; - - if (state.editor.currentModule) { - // In case the selections were hidden first - actions.editor.internal.updateSelectionsOfModule({ - module: state.editor.currentModule, - }); - } -}; - -export const revealViewRange = ( - { actions, effects, state }: Context, - liveUserId: string -) => { - if (!state.live.roomInfo) { - return; - } - - const user = state.live.roomInfo.users.find(({ id }) => id === liveUserId); - - if (user && user.currentModuleShortid && state.editor.currentSandbox) { - const { modules } = state.editor.currentSandbox; - const module = modules.filter( - ({ shortid }) => shortid === user.currentModuleShortid - )[0]; - - actions.editor.moduleSelected({ id: module.id }); - - if (user.viewRange) { - effects.vscode.revealRange(user.viewRange); - } - } -}; - -export const revealCursorPosition = async ( - { state, effects, actions }: Context, - { liveUserId }: { liveUserId: string } -) => { - if (!state.live.roomInfo) { - return; - } - - const user = state.live.roomInfo.users.find(u => u.id === liveUserId); - - if (user && user.currentModuleShortid && state.editor.currentSandbox) { - const { modules } = state.editor.currentSandbox; - const module = modules.filter( - ({ shortid }) => shortid === user.currentModuleShortid - )[0]; - - await actions.editor.moduleSelected({ id: module.id }); - - if (user.selection?.primary?.cursorPosition) { - effects.vscode.revealPositionInCenterIfOutsideViewport( - user.selection.primary.cursorPosition, - 0 - ); - } - } -}; diff --git a/packages/app/src/app/overmind/namespaces/live/index.ts b/packages/app/src/app/overmind/namespaces/live/index.ts deleted file mode 100755 index 0d573be52de..00000000000 --- a/packages/app/src/app/overmind/namespaces/live/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { state } from './state'; -import * as actions from './actions'; - -export { state, actions }; diff --git a/packages/app/src/app/overmind/namespaces/live/internalActions.ts b/packages/app/src/app/overmind/namespaces/live/internalActions.ts deleted file mode 100755 index 5dfc5b0551a..00000000000 --- a/packages/app/src/app/overmind/namespaces/live/internalActions.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { - EditorSelection, - IModuleState, - IModuleStateModule, - Module, - Sandbox, -} from '@codesandbox/common/lib/types'; -import { logBreadcrumb } from '@codesandbox/common/lib/utils/analytics/sentry'; -import { getTextOperation } from '@codesandbox/common/lib/utils/diff'; -import { Context } from 'app/overmind'; -import { json } from 'overmind'; - -import { getSavedCode } from '../../utils/sandbox'; - -export const clearUserSelections = ( - { state, effects }: Context, - live_user_id: string | null -) => { - if (!state.live.roomInfo) { - return; - } - - const clearSelections = (userId: string) => { - const roomInfo = state.live.roomInfo!; - const userIndex = roomInfo.users.findIndex(u => u.id === userId); - - effects.vscode.clearUserSelections(userId); - if (userIndex > -1) { - const user = roomInfo.users[userIndex]; - if (user) { - user.selection = null; - } - } - }; - - if (!live_user_id) { - // All users - state.live.roomInfo.users.forEach(u => clearSelections(u.id)); - } else { - clearSelections(live_user_id); - } -}; - -export const reset = ({ state, actions, effects }: Context) => { - actions.live.internal.clearUserSelections(null); - state.live.isLive = false; - state.live.error = null; - state.live.isLoading = false; - state.live.roomInfo = null; - state.live.joinSource = 'sandbox'; - effects.live.reset(); -}; - -export const disconnect = ({ effects, actions }: Context) => { - effects.live.disconnect(); - actions.live.internal.reset(); -}; - -export const initialize = async ( - { state, effects, actions }: Context, - id: string -) => { - state.live.isLoading = true; - - try { - const { - roomInfo, - liveUserId, - moduleState, - } = await effects.live.joinChannel(id, reason => { - if (reason === 'room not found') { - if (state.live.roomInfo) { - // Reset the live room id so that the logic that checks if the room id changed - // doesn't get confused. We changed that a sandbox will always have a consistent - // room id, so we need to manually change the id so the "change" logic actually - // makes sure that we reconnect. - state.live.roomInfo.roomId = 'INVALID_ROOM'; - } - actions.refetchSandboxInfo(); - } - }); - - state.live.roomInfo = roomInfo; - state.live.liveUserId = liveUserId; - - const sandboxId = roomInfo.sandboxId; - let sandbox = state.editor.currentSandbox; - if (!sandbox || sandbox.id !== sandboxId) { - sandbox = await effects.api.getSandbox(sandboxId); - state.editor.sandboxes[sandboxId] = sandbox; - state.editor.currentId = sandboxId; - } - - actions.live.internal.initializeModuleState(moduleState); - effects.live.listen(actions.live.liveMessageReceived); - actions.live.internal.sendUnsavedChanges({ sandbox, moduleState }); - - (state.editor.changedModuleShortids || []).forEach(moduleShortId => { - effects.vscode.openModule( - sandbox!.modules.find( - moduleItem => moduleItem.shortid === moduleShortId - )! - ); - }); - - state.live.isLive = true; - state.live.error = null; - effects.live.markLiveReady(); - - return sandbox; - } catch (error) { - state.live.error = error.reason; - } finally { - state.live.isLoading = false; - } - - return null; -}; - -export const initializeModuleFromState = ( - { state, effects }: Context, - { - moduleShortid, - moduleInfo, - }: { - moduleShortid: string; - moduleInfo: IModuleStateModule; - } -) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - - // Module has not been saved, so is different - const module = sandbox.modules.find(m => m.shortid === moduleShortid); - - if (module) { - effects.live.createClient(moduleShortid, moduleInfo.revision || 0); - if (!('code' in moduleInfo)) { - return; - } - - const savedCodeChanged = - getSavedCode(moduleInfo.code, moduleInfo.saved_code) !== - getSavedCode(module.code, module.savedCode); - const moduleChanged = - moduleInfo.code !== module.code || - moduleInfo.saved_code !== module.savedCode; - - if (moduleChanged) { - if (moduleInfo.saved_code !== undefined) { - module.savedCode = moduleInfo.saved_code; - } - if (moduleInfo.code !== undefined) { - module.code = moduleInfo.code; - } - - if (savedCodeChanged) { - effects.vscode.sandboxFsSync.writeFile( - state.editor.modulesByPath, - module - ); - } - if (moduleInfo.synced) { - effects.vscode.syncModule(module); - } else { - effects.vscode.setModuleCode(module); - } - } - } -}; - -export const initializeModuleState = ( - { state, actions }: Context, - moduleState: IModuleState -) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - logBreadcrumb({ - category: 'ot', - message: 'Applying new module state', - }); - Object.keys(moduleState).forEach(moduleShortid => { - const moduleInfo = moduleState[moduleShortid]; - - actions.live.internal.initializeModuleFromState({ - moduleShortid, - moduleInfo, - }); - }); - // TODO: enable once we know exactly when we want to recover - // actions.files.internal.recoverFiles(); - actions.editor.internal.updatePreviewCode(); -}; - -export const getSelectionsForModule = ({ state }: Context, module: Module) => { - const selections: EditorSelection[] = []; - const moduleShortid = module.shortid; - - if (!state.live.roomInfo) { - return selections; - } - - state.live.roomInfo.users.forEach(user => { - const userId = user.id; - if ( - userId === state.live.liveUserId || - user.currentModuleShortid !== moduleShortid || - (!state.live.isEditor(userId) && state.live.followingUserId !== userId) - ) { - return; - } - - if (user.selection) { - selections.push({ - userId, - color: user.color, - name: user.username, - selection: json(user.selection), - }); - } - }); - - return selections; -}; - -/** - * This sends over all modules that are not synced with OT, and of which we have local changes. - * If there's a module that has OT changes from the module_state, we ignore them. - */ -export const sendUnsavedChanges = ( - { effects }: Context, - { - sandbox, - moduleState, - }: { - sandbox: Sandbox; - moduleState: IModuleState; - } -) => { - // We now need to send all dirty files that came over from the last sandbox. - // There is the scenario where you edit a file and press fork. Then the server - // doesn't know about how you got to that dirty state. - const changedModules = sandbox.modules.filter( - m => getSavedCode(m.code, m.savedCode) !== m.code - ); - changedModules.forEach(m => { - if (!moduleState[m.shortid]) { - const savedCode = getSavedCode(m.code, m.savedCode); - // Update server with latest data - effects.live.sendCodeUpdate( - m.shortid, - getTextOperation(savedCode, m.code || '') - ); - } - }); -}; diff --git a/packages/app/src/app/overmind/namespaces/live/liveMessageOperators.ts b/packages/app/src/app/overmind/namespaces/live/liveMessageOperators.ts deleted file mode 100644 index 27f81726351..00000000000 --- a/packages/app/src/app/overmind/namespaces/live/liveMessageOperators.ts +++ /dev/null @@ -1,878 +0,0 @@ -import { getModulesAndDirectoriesInDirectory } from '@codesandbox/common/lib/sandbox/modules'; -import { - Directory, - LiveDisconnectReason, - LiveMessage, - LiveUser, - Module, - RoomInfo, - UserSelection, - UserViewRange, -} from '@codesandbox/common/lib/types'; -import { logBreadcrumb } from '@codesandbox/common/lib/utils/analytics/sentry'; -import { NotificationStatus } from '@codesandbox/notifications/lib/state'; -import { camelizeKeys } from 'humps'; - -import { Context } from 'app/overmind'; -import { getSavedCode } from 'app/overmind/utils/sandbox'; - -export const onSave = ( - { state, effects }: Context, - { - data, - }: LiveMessage<{ - saved_code: string; - updated_at: string; - inserted_at: string; - version: number; - path: string; - }> -) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - const module = sandbox.modules.find( - moduleItem => moduleItem.path === data.path - ); - - if (!module) { - return; - } - - module.savedCode = module.code === data.saved_code ? null : data.saved_code; - module.updatedAt = data.updated_at; - module.insertedAt = data.inserted_at; - sandbox.version = data.version; - effects.vscode.sandboxFsSync.writeFile(state.editor.modulesByPath, module); - - if (module.savedCode === null) { - effects.vscode.syncModule(module); - } -}; - -export const onJoin = ( - { effects, actions, state }: Context, - { - data, - }: LiveMessage<{ - status: 'connected'; - live_user_id: string; - }> -) => { - state.live.liveUserId = data.live_user_id; - - if (state.live.reconnecting) { - // We reconnected! - effects.live.getAllClients().forEach(client => { - client.serverReconnect(); - }); - - if (state.live.roomInfo) { - // Clear all user selections - actions.live.internal.clearUserSelections(null); - } - } - - state.live.reconnecting = false; -}; - -export const onModuleState = ( - { actions }: Context, - { - data, - }: LiveMessage<{ - module_state: any; - }> -) => { - actions.live.internal.initializeModuleState(data.module_state); -}; - -export const onExternalResources = ( - { state, actions }: Context, - { - data, - }: LiveMessage<{ - externalResources: string[]; - }> -) => { - if (!state.editor.currentSandbox) { - return; - } - state.editor.currentSandbox.externalResources = data.externalResources; - actions.editor.internal.updatePreviewCode(); -}; - -export const onUsersChanged = ( - { state, actions }: Context, - { - data, - }: LiveMessage<{ - users: LiveUser[]; - editor_ids: string[]; - owner_ids: string[]; - }> -) => { - if (state.live.isLoading || !state.live.roomInfo || !state.live.isLive) { - return; - } - - const users = camelizeKeys(data.users); - - state.live.roomInfo.users = users as LiveUser[]; - state.live.roomInfo.editorIds = data.editor_ids; - state.live.roomInfo.ownerIds = data.owner_ids; - - if (state.editor.currentModule) { - actions.editor.internal.updateSelectionsOfModule({ - module: state.editor.currentModule, - }); - } -}; - -export const onUserEntered = ( - { state, effects, actions }: Context, - { - data, - }: LiveMessage<{ - users: LiveUser[]; - editor_ids: string[]; - owner_ids: string[]; - joined_user_id: string; - }> -) => { - if (state.live.isLoading || !state.live.roomInfo || !state.live.isLive) { - return; - } - - effects.analytics.track('Editor - Someone joined the live session'); - - const users = camelizeKeys(data.users); - - state.live.roomInfo.users = users as LiveUser[]; - state.live.roomInfo.editorIds = data.editor_ids; - state.live.roomInfo.ownerIds = data.owner_ids; - - if (state.editor.currentModule) { - actions.editor.internal.updateSelectionsOfModule({ - module: state.editor.currentModule, - }); - } - - // Send our own selections and viewranges to everyone, just to let the others know where - // we are - actions.live.sendCurrentSelection(); - actions.live.sendCurrentViewRange(); - - if (data.joined_user_id === state.live.liveUserId) { - return; - } - - const user = data.users.find(u => u.id === data.joined_user_id); - - if ( - !state.live.notificationsHidden && - user && - !state.user?.experiments.collaborator - ) { - effects.notificationToast.add({ - message: `${user.username} joined the live session.`, - status: NotificationStatus.NOTICE, - }); - } -}; - -export const onUserLeft = ( - { state, actions, effects }: Context, - { - data, - }: LiveMessage<{ - users: LiveUser[]; - left_user_id: string; - editor_ids: string[]; - owner_ids: string[]; - }> -) => { - if (!state.live.roomInfo) { - return; - } - - effects.analytics.track('Editor - Someone left the live session'); - - if (!state.live.notificationsHidden) { - const { users } = state.live.roomInfo; - const user = users ? users.find(u => u.id === data.left_user_id) : null; - - if ( - user && - user.id !== state.live.liveUserId && - !state.user?.experiments.collaborator - ) { - effects.notificationToast.add({ - message: `${user.username} left the live session.`, - status: NotificationStatus.NOTICE, - }); - } - } - - actions.live.onUserLeft({ liveUserId: data.left_user_id }); - - const users = camelizeKeys(data.users) as LiveUser[]; - - state.live.roomInfo.users = users; - state.live.roomInfo.ownerIds = data.owner_ids; - state.live.roomInfo.editorIds = data.editor_ids; -}; - -export const onModuleSaved = ( - { state, effects, actions }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - moduleShortid: string; - module: Module; - }> -) => { - if (_isOwnMessage || !state.editor.currentSandbox) { - return; - } - const module = state.editor.currentSandbox.modules.find( - moduleItem => moduleItem.shortid === data.moduleShortid - ); - - if (module) { - actions.editor.internal.setModuleSavedCode({ - moduleShortid: data.moduleShortid, - savedCode: data.module.savedCode, - }); - - effects.vscode.sandboxFsSync.writeFile(state.editor.modulesByPath, module); - const savedCode = getSavedCode(module.code, module.savedCode); - if (!effects.vscode.isModuleOpened(module)) { - module.code = savedCode; - } - if (module.code === savedCode) { - // We revert the module so that VSCode will flag saved indication correctly - effects.vscode.syncModule(module); - } - actions.editor.internal.updatePreviewCode(); - } -}; - -export const onModuleCreated = ( - { state, actions, effects }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - module: Module; - }> -) => { - if (_isOwnMessage || !state.editor.currentSandbox) { - return; - } - state.editor.currentSandbox.modules.push(data.module); - effects.vscode.sandboxFsSync.writeFile( - state.editor.modulesByPath, - data.module - ); - if (state.editor.currentSandbox.originalGit) { - actions.git.updateGitChanges(); - } -}; - -export const onModuleMassCreated = ( - { state, actions, effects }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - modules: Module[]; - directories: Directory[]; - }> -) => { - if (_isOwnMessage || !state.editor.currentSandbox) { - return; - } - state.editor.currentSandbox.modules = state.editor.currentSandbox.modules.concat( - data.modules - ); - state.editor.currentSandbox.directories = state.editor.currentSandbox.directories.concat( - data.directories - ); - - state.editor.modulesByPath = effects.vscode.sandboxFsSync.create( - state.editor.currentSandbox - ); - - actions.editor.internal.updatePreviewCode(); - if (state.editor.currentSandbox.originalGit) { - actions.git.updateGitChanges(); - } -}; - -export const onModuleUpdated = ( - { state, actions, effects }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - moduleShortid: string; - module: Module; - }> -) => { - const sandbox = state.editor.currentSandbox; - - if (_isOwnMessage || !sandbox) { - return; - } - - const moduleIndex = sandbox.modules.findIndex( - moduleEntry => moduleEntry.shortid === data.moduleShortid - ); - const existingModule = - state.editor.sandboxes[sandbox.id].modules[moduleIndex]; - - if (existingModule.path !== data.module.path) { - effects.vscode.sandboxFsSync.rename( - state.editor.modulesByPath, - existingModule.path!, - data.module.path! - ); - } - - Object.assign(existingModule, data.module); - - effects.vscode.sandboxFsSync.writeFile( - state.editor.modulesByPath, - existingModule - ); - - if (state.editor.currentModuleShortid === data.moduleShortid) { - effects.vscode.openModule(existingModule); - } - - actions.editor.internal.updatePreviewCode(); - if (state.editor.currentSandbox!.originalGit) { - actions.git.updateGitChanges(); - } -}; - -export const onModuleDeleted = ( - { state, effects, actions }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - moduleShortid: string; - }> -) => { - if (_isOwnMessage || !state.editor.currentSandbox) { - return; - } - const removedModule = state.editor.currentSandbox.modules.find( - directory => directory.shortid === data.moduleShortid - ); - if (!removedModule) { - return; - } - const moduleIndex = state.editor.currentSandbox.modules.indexOf( - removedModule - ); - const wasCurrentModule = - state.editor.currentModuleShortid === data.moduleShortid; - - state.editor.currentSandbox.modules.splice(moduleIndex, 1); - effects.vscode.sandboxFsSync.unlink( - state.editor.modulesByPath, - removedModule - ); - - if (wasCurrentModule && state.editor.mainModule) { - actions.editor.internal.setCurrentModule(state.editor.mainModule); - } - - actions.editor.internal.updatePreviewCode(); - if (state.editor.currentSandbox.originalGit) { - actions.git.updateGitChanges(); - } -}; - -export const onDirectoryCreated = ( - { state, effects }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - module: Directory; // This is very weird? - }> -) => { - if (_isOwnMessage || !state.editor.currentSandbox) { - return; - } - // Should this not be a directory? - state.editor.currentSandbox.directories.push(data.module); - effects.vscode.sandboxFsSync.mkdir(state.editor.modulesByPath, data.module); -}; - -export const onDirectoryUpdated = ( - { state, actions }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - directoryShortid: string; - module: Directory; // Still very weird - }> -) => { - const sandbox = state.editor.currentSandbox; - if (_isOwnMessage || !sandbox) { - return; - } - - const directoryIndex = sandbox.directories.findIndex( - directoryEntry => directoryEntry.shortid === data.directoryShortid - ); - const existingDirectory = - state.editor.sandboxes[sandbox.id].directories[directoryIndex]; - const hasChangedPath = existingDirectory.path !== data.module.path; - - Object.assign(existingDirectory, data.module); - - if (hasChangedPath) { - const prevCurrentModulePath = state.editor.currentModule.path; - - actions.files.internal.renameDirectoryInState({ - directory: existingDirectory, - sandbox, - title: data.module.title, - }); - - actions.editor.internal.updatePreviewCode(); - - if (prevCurrentModulePath !== state.editor.currentModule.path) { - actions.editor.internal.setCurrentModule(state.editor.currentModule); - } - } -}; - -export const onDirectoryDeleted = ( - { state, effects, actions }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - directoryShortid: string; - }> -) => { - const sandbox = state.editor.currentSandbox; - if (_isOwnMessage || !sandbox) { - return; - } - - const directory = sandbox.directories.find( - directoryItem => directoryItem.shortid === data.directoryShortid - ); - - if (!directory) { - return; - } - - const removedDirectory = sandbox.directories.splice( - sandbox.directories.indexOf(directory), - 1 - )[0]; - const { - removedModules, - removedDirectories, - } = getModulesAndDirectoriesInDirectory( - removedDirectory, - sandbox.modules, - sandbox.directories - ); - - removedModules.forEach(removedModule => { - effects.vscode.sandboxFsSync.unlink( - state.editor.modulesByPath, - removedModule - ); - sandbox.modules.splice(sandbox.modules.indexOf(removedModule), 1); - }); - - removedDirectories.forEach(removedDirectoryItem => { - sandbox.directories.splice( - sandbox.directories.indexOf(removedDirectoryItem), - 1 - ); - }); - - // We open the main module as we do not really know if you had opened - // any nested file of this directory. It would require complex logic - // to figure that out. This concept is soon removed anyways - if (state.editor.mainModule) - effects.vscode.openModule(state.editor.mainModule); - actions.editor.internal.updatePreviewCode(); - - if (state.editor.currentSandbox!.originalGit) { - actions.git.updateGitChanges(); - } -}; - -export const onUserSelection = ( - { state, effects, actions }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - liveUserId: string; - moduleShortid: string; - selection: UserSelection; - }> -) => { - if (_isOwnMessage || !state.live.roomInfo || !state.editor.currentSandbox) { - return; - } - - const userSelectionLiveUserId = data.liveUserId; - const { moduleShortid } = data; - const { selection } = data; - const userIndex = state.live.roomInfo.users.findIndex( - u => u.id === userSelectionLiveUserId - ); - - if (userIndex > -1) { - state.live.roomInfo.users[userIndex].currentModuleShortid = moduleShortid; - state.live.roomInfo.users[userIndex].selection = selection; - } - - const module = state.editor.currentSandbox.modules.find( - m => m.shortid === moduleShortid - ); - - const isFollowingUser = - state.live.followingUserId === userSelectionLiveUserId; - if ( - module && - (state.live.isEditor(userSelectionLiveUserId) || isFollowingUser) - ) { - const user = state.live.roomInfo.users.find( - u => u.id === userSelectionLiveUserId - ); - - if (user) { - effects.vscode.updateUserSelections( - module, - actions.live.internal.getSelectionsForModule(module) - ); - - if (isFollowingUser) { - actions.live.revealCursorPosition({ - liveUserId: userSelectionLiveUserId, - }); - } - } - } -}; - -export const onUserCurrentModule = ( - { state, actions }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - live_user_id: string; - moduleShortid: string; - }> -) => { - if (_isOwnMessage || !state.live.roomInfo || !state.editor.currentSandbox) { - return; - } - const userIndex = state.live.roomInfo.users.findIndex( - u => u.id === data.live_user_id - ); - - if (userIndex > -1) { - state.live.roomInfo.users[userIndex].currentModuleShortid = - data.moduleShortid; - } - - actions.live.internal.clearUserSelections(data.live_user_id); - - if ( - state.live.followingUserId === data.live_user_id && - data.moduleShortid !== state.editor.currentModuleShortid - ) { - const { moduleShortid } = data; - const { modules } = state.editor.currentSandbox; - const module = modules.find(m => m.shortid === moduleShortid); - - if (!module) { - return; - } - - actions.editor.moduleSelected({ - id: module.id, - }); - } -}; - -export const onUserViewRange = ( - { state, effects }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - liveUserId: string; - moduleShortid: string; - viewRange: UserViewRange; - }> -) => { - if (_isOwnMessage || !state.live.roomInfo || !state.editor.currentSandbox) { - return; - } - - const userSelectionLiveUserId = data.liveUserId; - const { moduleShortid } = data; - const { viewRange } = data; - const userIndex = state.live.roomInfo.users.findIndex( - u => u.id === userSelectionLiveUserId - ); - - if (userIndex !== -1) { - state.live.roomInfo.users[userIndex].currentModuleShortid = moduleShortid; - state.live.roomInfo.users[userIndex].viewRange = viewRange; - } - - const module = state.editor.currentSandbox.modules.find( - m => m.shortid === moduleShortid - ); - if (module) { - const user = state.live.roomInfo.users.find( - u => u.id === userSelectionLiveUserId - ); - - if (user && state.live.followingUserId === userSelectionLiveUserId) { - effects.vscode.revealRange(viewRange); - } - } -}; - -export const onLiveMode = ( - { actions, state }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - mode: RoomInfo['mode']; - }> -) => { - if (!state.live.roomInfo) { - return; - } - - if (!_isOwnMessage) { - state.live.roomInfo.mode = data.mode; - } - actions.live.internal.clearUserSelections(null); -}; - -export const onLiveChatEnabled = ( - { state }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - enabled: boolean; - }> -) => { - if (!state.live.roomInfo) { - return; - } - - if (_isOwnMessage) { - return; - } - state.live.roomInfo.chatEnabled = data.enabled; -}; - -export const onLiveAddEditor = ( - { state }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - editor_user_id: string; - }> -) => { - if (!state.live.roomInfo) { - return; - } - - if (!_isOwnMessage) { - state.live.roomInfo.editorIds.push(data.editor_user_id); - } -}; - -export const onLiveRemoveEditor = ( - { state, actions }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - editor_user_id: string; - }> -) => { - if (!state.live.roomInfo) { - return; - } - - const userId = data.editor_user_id; - actions.live.internal.clearUserSelections(userId); - - if (!_isOwnMessage) { - const editors = state.live.roomInfo.editorIds; - const newEditors = editors.filter(id => id !== userId); - - state.live.roomInfo.editorIds = newEditors; - } -}; - -export const onOperation = ( - { state, effects }: Context, - { - _isOwnMessage, - data, - }: LiveMessage<{ - module_shortid: string; - operation: any; - }> -) => { - if (state.live.isLoading) { - return; - } - if (_isOwnMessage) { - // Do nothing since we already sent this operation - } else { - try { - effects.live.applyServer(data.module_shortid, data.operation); - } catch (e) { - // Something went wrong, probably a sync mismatch. Request new version - console.error('Something went wrong with applying OT operation'); - - logBreadcrumb({ - category: 'ot', - message: `Apply operation from server to OT client failed ${JSON.stringify( - data - )}`, - }); - - effects.live.sendModuleStateSyncRequest(); - } - } -}; - -export const onConnectionLoss = async ({ state, effects }: Context) => { - if (!state.live.reconnecting) { - let notificationId: string | null = null; - const timeout = setTimeout(() => { - notificationId = effects.notificationToast.add({ - message: 'We lost connection with the live server, reconnecting...', - status: NotificationStatus.ERROR, - }); - }, 30000); - - state.live.reconnecting = true; - - await effects.flows.waitUntil(s => s.live.reconnecting === false); - if (notificationId) { - effects.notificationToast.remove(notificationId); - } - if (timeout) { - clearTimeout(timeout); - } - } -}; - -export const onDisconnect = ( - { state, actions }: Context, - { - data, - }: LiveMessage<{ - reason: LiveDisconnectReason; - }> -) => { - actions.live.internal.disconnect(); - - if (state.editor.currentSandbox) - state.editor.currentSandbox.owned = state.live.isOwner; - - actions.modalOpened({ - modal: 'liveSessionEnded', - message: - data.reason === 'close' - ? 'The owner ended the session' - : 'The session has ended due to inactivity', - }); - - actions.live.internal.reset(); -}; - -export const onOwnerLeft = ({ actions }: Context) => { - actions.modalOpened({ - modal: 'liveSessionEnded', - message: 'The owner left the session', - }); -}; - -export const onChat = ( - { state }: Context, - { - data, - }: LiveMessage<{ - live_user_id: string; - message: string; - date: number; - }> -) => { - if (!state.live.roomInfo) { - return; - } - - let name = state.live.roomInfo.chat.users[data.live_user_id]; - - if (!name) { - const user = state.live.roomInfo.users.find( - u => u.id === data.live_user_id - ); - - if (user) { - state.live.roomInfo.chat.users[data.live_user_id] = user.username; - name = user.username; - } else { - name = 'Unknown User'; - } - } - - state.live.roomInfo.chat.messages.push({ - userId: data.live_user_id, - message: data.message, - date: data.date, - }); -}; - -export const onNotification = ( - { effects }: Context, - { - data, - }: LiveMessage<{ - message: string; - type: NotificationStatus; - }> -) => { - effects.notificationToast.add({ - message: data.message, - status: data.type, - }); -}; diff --git a/packages/app/src/app/overmind/namespaces/live/state.ts b/packages/app/src/app/overmind/namespaces/live/state.ts deleted file mode 100755 index 7755b05b05f..00000000000 --- a/packages/app/src/app/overmind/namespaces/live/state.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { - LiveUser, - RoomInfo, - UserSelection, - UserViewRange, -} from '@codesandbox/common/lib/types'; -import { derived } from 'overmind'; - -type State = { - isLive: boolean; - isTeam: boolean; - isLoading: boolean; - error: string | null; - reconnecting: boolean; - notificationsHidden: boolean; - followingUserId: string | null; - liveUserId: string | null; - roomInfo: RoomInfo | null; - /** - * Whether we joined from /s/ or from /live/ - */ - joinSource: 'sandbox' | 'live'; - currentSelection: UserSelection | null; - currentViewRange: UserViewRange | null; - liveUser: LiveUser | null; - isEditor: (liveUserId: string) => boolean; - isCurrentEditor: boolean; - isOwner: boolean; - liveUsersByModule: { - [id: string]: number[][]; - }; -}; - -export const state: State = { - isLive: false, - isTeam: false, - isLoading: false, - reconnecting: false, - notificationsHidden: false, - followingUserId: null, - error: null, - liveUserId: null, - roomInfo: null, - currentSelection: { - primary: { - cursorPosition: 0, - selection: [], - }, - secondary: [], - source: 'overmind', - }, - currentViewRange: null, - joinSource: 'sandbox', - liveUser: derived( - (currentState: State) => - currentState.roomInfo?.users.find( - u => u.id === currentState.liveUserId - ) || null - ), - isEditor: derived((currentState: State) => liveUserId => - Boolean( - currentState.isLive && - currentState.roomInfo && - (currentState.roomInfo.mode === 'open' || - currentState.roomInfo.ownerIds.includes(liveUserId) || - currentState.roomInfo.editorIds.includes(liveUserId)) - ) - ), - isCurrentEditor: derived((currentState: State) => - Boolean( - currentState.liveUserId && currentState.isEditor(currentState.liveUserId) - ) - ), - isOwner: derived((currentState: State) => - Boolean( - currentState.isLive && - currentState.liveUserId && - currentState.roomInfo?.ownerIds.includes(currentState.liveUserId) - ) - ), - liveUsersByModule: derived((currentState: State) => { - const usersByModule: { [id: string]: number[][] } = {}; - - if (!currentState.isLive || !currentState.roomInfo) { - return {}; - } - - const { liveUserId } = currentState; - - currentState.roomInfo.users.forEach(user => { - const userId = user.id; - if (user && userId !== liveUserId && user.currentModuleShortid) { - usersByModule[user.currentModuleShortid] = - usersByModule[user.currentModuleShortid] || []; - usersByModule[user.currentModuleShortid].push(user.color); - } - }); - - return usersByModule; - }), -}; diff --git a/packages/app/src/app/overmind/namespaces/preferences/actions.ts b/packages/app/src/app/overmind/namespaces/preferences/actions.ts index d1c6217e411..ef142521dd7 100755 --- a/packages/app/src/app/overmind/namespaces/preferences/actions.ts +++ b/packages/app/src/app/overmind/namespaces/preferences/actions.ts @@ -1,8 +1,4 @@ -import { convertTypeToStatus } from '@codesandbox/common/lib/utils/notifications'; -import { isEqual } from 'lodash-es'; -import { saveAs } from 'file-saver'; import { Context } from 'app/overmind'; -import { SettingSync } from './state'; export const viewModeChanged = ( { state }: Context, @@ -39,10 +35,6 @@ export const itemIdChanged = async ( itemId: string ) => { state.preferences.itemId = itemId; - - if (itemId === 'integrations') { - await actions.deployment.internal.getVercelUserDetails(); - } }; export const settingChanged = ( @@ -64,10 +56,6 @@ export const settingChanged = ( ); settingsTarget[lastKey] = value; - if (name === 'vimMode') { - effects.vscode.setVimExtensionEnabled(Boolean(value)); - } - effects.settingsStore.set(firstKey, state.preferences.settings[firstKey]); effects.analytics.track('Change Settings', { @@ -76,245 +64,6 @@ export const settingChanged = ( }); }; -export const zenModeToggled = ({ state }: Context) => { - state.preferences.settings.zenMode = !state.preferences.settings.zenMode; -}; - -export const codeMirrorForced = ({ state }: Context) => { - state.preferences.settings.codeMirror = true; -}; - -export const getUserLocalSettings = () => { - const fs = window.BrowserFS.BFSRequire('fs'); - const all = fs.readdirSync('/vscode'); - const files = {}; - - const readFromDirectory = (path: string) => { - const filesInDirectory = fs.readdirSync(path); - if (path === '/vscode/userdata/CachedExtensions') { - return; - } - - filesInDirectory.forEach(p => { - const newPath = path + '/' + p; - if (fs.statSync(newPath).isDirectory()) { - readFromDirectory(newPath); - } else { - files[newPath] = fs.readFileSync(newPath).toString(); - } - }); - }; - - all.forEach(dir => { - const a = `/vscode/` + dir; - - if (fs.statSync(a).isDirectory()) { - readFromDirectory(a); - } else { - files[a] = fs.readFileSync(a).toString(); - } - }); - - const LOCAL_STORAGE_KEYS = [ - 'vs-global://colorThemeData', - 'VIEW_MODE_DASHBOARD', - 'vs-global://iconThemeData', - ...Object.keys(localStorage).filter(key => key.includes('settings.')), - ]; - const themeData = {}; - - LOCAL_STORAGE_KEYS.forEach(key => { - themeData[key] = localStorage.getItem(key); - }); - - return { - themeData, - vscode: files, - }; -}; - -export const renameUserSettings = async ( - { state, effects }: Context, - { - name, - id, - }: { - name: string; - id: string; - } -) => { - const { settingsSync } = state.preferences; - if (!name || !settingsSync.settings) return; - - try { - const response = await effects.api.editUserSettings( - { - ...settingsSync.settings.find(s => s.id === id), - name, - }, - id - ); - - settingsSync.settings = settingsSync.settings.map(setting => { - if (setting.id === response.id) { - return { - ...setting, - name, - }; - } - - return setting; - }); - } catch (e) { - effects.notificationToast.error( - 'There has been a problem renaming your profile' - ); - } -}; - -export const getUserSettings = async ({ state, effects }: Context) => { - const { settingsSync } = state.preferences; - settingsSync.fetching = true; - - const response = await effects.api.getUserSettings(); - - settingsSync.settings = response; - settingsSync.fetching = false; -}; - -export const createPreferencesProfile = async ({ - state, - effects, - actions, -}: Context) => { - state.preferences.settingsSync.syncing = true; - try { - const { vscode, themeData } = actions.preferences.getUserLocalSettings(); - - actions.preferences.updateServerSettings( - JSON.stringify({ - themeData, - vscode, - }) - ); - } catch (e) { - effects.notificationToast.error( - 'There has been a problem uploading your preferences' - ); - } -}; - -export const updateServerSettings = async ( - { state, effects }: Context, - settingsStringified: string -) => { - if (!state.user) return; - const { - id, - insertedAt, - name, - settings, - updatedAt, - } = await effects.api.createUserSettings({ - name: 'My Profile', - settings: settingsStringified, - }); - - state.preferences.settingsSync.syncing = false; - state.preferences.settingsSync.settings = [ - { - id, - insertedAt, - name, - settings, - updatedAt, - }, - ]; - localStorage.setItem(`profile-${id}`, updatedAt); - effects.notificationToast.success( - 'Your preferences have been successfully synced' - ); -}; - -export const checkifSynced = ({ actions }: Context, savedSetting: string) => { - const currentSettings = actions.preferences.getUserLocalSettings(); - return isEqual(currentSettings, JSON.parse(savedSetting)); -}; - -export const deleteUserSetting = async ( - { state, effects, actions }: Context, - id: string -) => { - if (!state.preferences.settingsSync.settings) return; - try { - await effects.api.removeUserSetting(id); - const removed = state.preferences.settingsSync.settings.find( - setting => setting.id === id - ); - - state.preferences.settingsSync.settings = state.preferences.settingsSync.settings.filter( - setting => setting.id !== id - ); - effects.notificationToast.add({ - title: 'Your profile has been removed', - message: '', - status: convertTypeToStatus('success'), - sticky: false, - actions: { - primary: { - label: 'Undo', - run: () => { - if (removed?.settings) { - actions.preferences.updateServerSettings(removed?.settings); - } - }, - }, - }, - }); - } catch (e) { - effects.notificationToast.error( - 'There has been a problem removing your profile' - ); - } -}; - -export const downloadPreferences = async ( - _: Context, - settings: SettingSync -) => { - const blob = new Blob([settings.settings], { type: 'application/json' }); - saveAs(blob, `CodeSandboxSettings-${settings.name}.json`); -}; - -export const applyPreferences = async ( - { state, effects }: Context, - settings: string -) => { - if (!state.preferences.settingsSync.settings) return; - const fs = window.BrowserFS.BFSRequire('fs'); - - try { - const parsedSyncedSettings = JSON.parse(settings); - - Object.keys(parsedSyncedSettings.themeData).forEach(key => { - localStorage.setItem(key, parsedSyncedSettings.themeData[key]); - }); - - Object.keys(parsedSyncedSettings.vscode).forEach(key => { - fs.writeFileSync(key, parsedSyncedSettings.vscode[key]); - }); - effects.notificationToast.success( - 'Your preferences have been applied. The page will now reload' - ); - - window.setTimeout(() => location.reload(), 1000); - } catch (e) { - effects.notificationToast.error( - 'There has been a problem applying your preferences' - ); - } -}; - export const updateAccountDetails = async ( { state, effects }: Context, { diff --git a/packages/app/src/app/overmind/namespaces/preview/actions.ts b/packages/app/src/app/overmind/namespaces/preview/actions.ts deleted file mode 100644 index 80b6a817fed..00000000000 --- a/packages/app/src/app/overmind/namespaces/preview/actions.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { Context } from 'app/overmind'; -import { isEqual } from 'lodash-es'; -import { json } from 'overmind'; -import { Presets, defaultPresets } from './state'; - -export const toggleResponsiveMode = async ({ - state, - actions, - effects, -}: Context) => { - const existingMode = state.preview.mode; - const newUrl = new URL(document.location.href); - - switch (existingMode) { - case 'responsive': - state.preview.mode = null; - break; - case 'add-comment': - state.preview.mode = 'responsive-add-comment'; - break; - case 'responsive-add-comment': - state.preview.mode = 'add-comment'; - break; - default: { - state.preview.mode = 'responsive'; - effects.analytics.track('Responsive Preview - Toggled On'); - } - } - - if (state.preview.mode) { - newUrl.searchParams.set( - 'resolutionWidth', - state.preview.responsive.resolution[0].toString() - ); - newUrl.searchParams.set( - 'resolutionHeight', - state.preview.responsive.resolution[1].toString() - ); - - if (!state.editor.workspaceConfig) { - actions.files.updateWorkspaceConfig({ - 'responsive-preview': defaultPresets, - }); - } - } else { - newUrl.searchParams.delete('resolutionWidth'); - newUrl.searchParams.delete('resolutionHeight'); - } - - if (newUrl) { - effects.router.replace( - newUrl.toString().replace(/%2F/g, '/').replace('%3A', ':') - ); - } -}; - -export const setResolution = ( - { state, effects }: Context, - newResolution: [number, number] -) => { - if (!newResolution) return; - const width = Math.round(newResolution[0]); - const height = Math.round(newResolution[1]); - - state.preview.responsive.resolution = [width, height]; - - const newUrl = new URL(document.location.href); - newUrl.searchParams.set('resolutionWidth', width.toString()); - newUrl.searchParams.set('resolutionHeight', height.toString()); - - if (newUrl) { - effects.router.replace( - newUrl.toString().replace(/%2F/g, '/').replace('%3A', ':') - ); - } -}; - -export const openDeletePresetModal = ({ state }: Context) => { - state.currentModal = 'deletePreset'; -}; - -export const openAddPresetModal = ({ state }: Context) => { - state.currentModal = 'addPreset'; -}; - -export const toggleEditPresets = ({ state }: Context) => { - state.currentModal = 'editPresets'; -}; - -export const deletePreset = async ({ state, actions }: Context) => { - const { presets, resolution } = state.preview.responsive; - - const canChangePresets = hasPermission( - state.editor.currentSandbox!.authorization, - 'write_code' - ); - - if (!canChangePresets) { - await actions.editor.forkSandboxClicked({}); - } - - const activePresetName = Object.keys(presets).find(preset => - isEqual(presets[preset], resolution) - ); - - if (activePresetName) { - state.preview.responsive.resolution = json(Object.values(presets)[0]); - - const responsivePreviewConfig = state.editor.workspaceConfig - ? state.editor.workspaceConfig['responsive-preview'] - : defaultPresets; - const presetsCopy = json(responsivePreviewConfig || {}); - delete presetsCopy[activePresetName.toString()]; - - await actions.files.updateWorkspaceConfig({ - 'responsive-preview': presetsCopy, - }); - } -}; - -export const addPreset = async ( - { state, actions, effects }: Context, - { - name, - width, - height, - }: { - name: string; - width: number; - height: number; - } -) => { - if (!name || !width || !height) return; - effects.analytics.track('Responsive Preview - Preset Created', { - width, - height, - }); - state.preview.responsive.resolution = [width, height]; - - const canChangePresets = hasPermission( - state.editor.currentSandbox!.authorization, - 'write_code' - ); - - if (!canChangePresets) { - await actions.editor.forkSandboxClicked({}); - } - - const responsivePreviewConfig = state.editor.workspaceConfig - ? state.editor.workspaceConfig['responsive-preview'] - : defaultPresets; - const presetsCopy = json(responsivePreviewConfig || {}); - presetsCopy[name] = [width, height]; - - await actions.files.updateWorkspaceConfig({ - 'responsive-preview': presetsCopy, - }); -}; - -export const editPresets = async ( - { actions }: Context, - newPresets: Presets -) => { - await actions.files.updateWorkspaceConfig({ - 'responsive-preview': newPresets, - }); -}; - -export const setExtension = ({ state }: Context, hasExtension: boolean) => { - state.preview.hasExtension = hasExtension; -}; - -export const closeExtensionBanner = ({ state }: Context) => { - state.preview.showExtensionBanner = false; -}; - -export const installExtension = async ({ - actions, - state, - effects, -}: Context) => { - await effects.browserExtension.install(); - - const doReload = await actions.modals.extensionInstalledModal.open(); - - if (doReload) { - effects.browser.reload(); - } - - state.preview.showExtensionBanner = false; -}; - -export const createPreviewComment = async ({ state, effects }: Context) => { - const currentSandbox = state.editor.currentSandbox; - - if (!currentSandbox || !effects.preview.canAddComments(currentSandbox)) { - return; - } - - const existingMode = state.preview.mode; - - state.preview.screenshot.source = null; - - const takeScreenshot = async () => { - try { - if (state.preview.hasExtension) { - effects.preview.showCommentCursor(); - const screenshot = await effects.preview.takeExtensionScreenshot(); - state.preview.screenshot.source = screenshot; - } else { - state.preview.screenshot.isLoading = true; - - const screenshot = await effects.preview.takeScreenshot( - state.editor.currentSandbox!.privacy === 2 - ); - - if (!effects.browserExtension.hasNotifiedImprovedScreenshots()) { - state.preview.showExtensionBanner = true; - effects.browserExtension.setNotifiedImprovedScreenshots(); - } - effects.preview.showCommentCursor(); - state.preview.screenshot.isLoading = false; - state.preview.screenshot.source = screenshot; - } - } catch (error) { - effects.notificationToast.error(error.message); - } - }; - - switch (existingMode) { - case 'responsive': - state.preview.mode = 'responsive-add-comment'; - await takeScreenshot(); - break; - case 'add-comment': - effects.preview.hideCommentCursor(); - state.preview.mode = null; - break; - case 'responsive-add-comment': - effects.preview.hideCommentCursor(); - state.preview.mode = 'responsive'; - break; - default: - state.preview.mode = 'add-comment'; - await takeScreenshot(); - } - - if (state.preview.mode && state.preview.mode.includes('comment')) { - effects.analytics.track('Preview Comment - Toggle', { - mode: state.preview.mode, - }); - } -}; - -export const checkURLParameters = ({ state, effects }: Context) => { - const ULRResolutionWidth = effects.router.getParameter('resolutionWidth'); - const URLResolutionHeight = effects.router.getParameter('resolutionHeight'); - - if (URLResolutionHeight && ULRResolutionWidth) { - state.preview.mode = 'responsive'; - state.preview.responsive.resolution = [ - parseInt(ULRResolutionWidth, 10), - parseInt(URLResolutionHeight, 10), - ]; - } -}; - -export const setIsResizing = ({ state }: Context, newIsResizing: boolean) => { - state.preview.responsive.isResizing = newIsResizing; -} \ No newline at end of file diff --git a/packages/app/src/app/overmind/namespaces/preview/index.ts b/packages/app/src/app/overmind/namespaces/preview/index.ts deleted file mode 100644 index 54c2413d3dd..00000000000 --- a/packages/app/src/app/overmind/namespaces/preview/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { state } from './state' -import * as actions from './actions' - -export { state, actions } \ No newline at end of file diff --git a/packages/app/src/app/overmind/namespaces/preview/state.ts b/packages/app/src/app/overmind/namespaces/preview/state.ts deleted file mode 100644 index b5937495135..00000000000 --- a/packages/app/src/app/overmind/namespaces/preview/state.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Context } from 'app/overmind'; -import { derived } from 'overmind'; - -export type Presets = { [name: string]: [number, number] }; - -type Mode = 'responsive' | 'add-comment' | 'responsive-add-comment' | null; - -type State = { - responsive: { - presets: Presets; - scale: number; - resolution: [number, number]; - isResizing: boolean; - }; - screenshot: { - source: string | null; - isLoading: boolean; - }; - mode: Mode; - hasExtension: boolean; - showExtensionBanner: boolean; -}; - -export const defaultPresets: Presets = { - Mobile: [320, 675], - Tablet: [1024, 765], - Desktop: [1400, 800], - 'Desktop HD': [1920, 1080], -}; - -export const state: State = { - responsive: { - presets: derived((_, rootState: Context['state']) => - rootState.editor.workspaceConfig && - rootState.editor.workspaceConfig['responsive-preview'] - ? rootState.editor.workspaceConfig['responsive-preview'] - : defaultPresets - ), - scale: 100, - resolution: [320, 675], - isResizing: false - }, - screenshot: { - source: null, - isLoading: false, - }, - mode: null, - hasExtension: false, - showExtensionBanner: false, -}; diff --git a/packages/app/src/app/overmind/namespaces/profile/actions.ts b/packages/app/src/app/overmind/namespaces/profile/actions.ts index f2a071d5f23..d04909060b1 100755 --- a/packages/app/src/app/overmind/namespaces/profile/actions.ts +++ b/packages/app/src/app/overmind/namespaces/profile/actions.ts @@ -20,20 +20,6 @@ export const profileMounted = withLoadApp( state.profile.profiles[profile.id] = profile; state.profile.currentProfileId = profile.id; - - if ( - profile.showcasedSandboxShortid && - !state.editor.sandboxes[profile.showcasedSandboxShortid] - ) { - try { - state.editor.sandboxes[ - profile.showcasedSandboxShortid - ] = await effects.api.getSandbox(profile.showcasedSandboxShortid); - } catch (e) { - // Ignore it - } - } - state.profile.isLoadingProfile = false; } ); @@ -150,14 +136,7 @@ export const newSandboxShowcaseSelected = async ( return; } - const [sandbox] = await Promise.all([ - state.editor.sandboxes[id] ? null : effects.api.getSandbox(id), - effects.api.updateShowcasedSandbox(state.user.username, id), - ]); - - if (sandbox) { - state.editor.sandboxes[id] = sandbox as Sandbox; - } + await effects.api.updateShowcasedSandbox(state.user.username, id); state.profile.isLoadingProfile = false; diff --git a/packages/app/src/app/overmind/namespaces/profile/state.ts b/packages/app/src/app/overmind/namespaces/profile/state.ts index 5b8f72abfe9..c526c67e5b4 100755 --- a/packages/app/src/app/overmind/namespaces/profile/state.ts +++ b/packages/app/src/app/overmind/namespaces/profile/state.ts @@ -83,13 +83,10 @@ export const state: State = { ? currentState.profiles[currentState.currentProfileId] : null ), - showcasedSandbox: derived( - (currentState: State, rootState: Context['state']) => - currentState.current && currentState.current.showcasedSandboxShortid - ? rootState.editor.sandboxes[ - currentState.current.showcasedSandboxShortid - ] - : null + showcasedSandbox: derived((currentState: State) => + currentState.current && currentState.current.showcasedSandboxShortid + ? currentState.sandboxes.all[currentState.current.showcasedSandboxShortid] + : null ), currentLikedSandboxes: derived((currentState: State) => currentState.current diff --git a/packages/app/src/app/overmind/namespaces/server/actions.ts b/packages/app/src/app/overmind/namespaces/server/actions.ts deleted file mode 100755 index f96a0dc3e51..00000000000 --- a/packages/app/src/app/overmind/namespaces/server/actions.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { ViewTab } from '@codesandbox/common/lib/templates/template'; -import { - Sandbox, - ServerContainerStatus, - ServerPort, - ServerStatus, -} from '@codesandbox/common/lib/types'; -import { NotificationStatus } from '@codesandbox/notifications/lib/state'; -import { Context } from 'app/overmind'; -import { getDevToolsTabPosition } from 'app/overmind/utils/server'; - -export const restartSandbox = ({ effects }: Context) => { - effects.executor.emit('sandbox:restart'); -}; - -export const restartContainer = ({ effects, state }: Context) => { - state.server.containerStatus = ServerContainerStatus.INITIALIZING; - effects.executor.emit('sandbox:restart-container'); -}; - -export const statusChanged = ({ state }: Context, status: ServerStatus) => { - state.server.status = status; -}; - -export const containerStatusChanged = ( - { state }: Context, - status: ServerContainerStatus -) => { - state.server.containerStatus = status; -}; - -export const onSSEMessage = ( - { state: { server, editor }, effects, actions }: Context, - { - event, - data, - }: { - event: string; - data: any; - } -) => { - switch (event) { - case 'connect': - server.error = null; - server.status = ServerStatus.CONNECTED; - break; - case 'disconnect': - if ( - server.containerStatus !== ServerContainerStatus.HIBERNATED && - server.status === ServerStatus.CONNECTED - ) { - server.status = ServerStatus.DISCONNECTED; - effects.codesandboxApi.disconnectSSE(); - } - break; - case 'sandbox:start': - server.containerStatus = ServerContainerStatus.SANDBOX_STARTED; - break; - case 'sandbox:stop': - if (server.containerStatus !== ServerContainerStatus.HIBERNATED) { - server.containerStatus = ServerContainerStatus.STOPPED; - } - break; - case 'sandbox:update': - actions.files.syncSandbox(data.updates); - break; - case 'sandbox:hibernate': - server.containerStatus = ServerContainerStatus.HIBERNATED; - effects.executor.closeExecutor(); - break; - case 'sandbox:status': - if (data.status === 'starting-container') { - server.containerStatus = ServerContainerStatus.INITIALIZING; - } else if (data.status === 'installing-packages') { - server.containerStatus = ServerContainerStatus.CONTAINER_STARTED; - } - break; - case 'sandbox:log': - effects.codesandboxApi.logTerminalMessage(data.data); - break; - case 'sandbox:port': { - const newPorts = data as ServerPort[]; - const currentPorts = server.ports; - const removedPorts = currentPorts.filter( - port => !newPorts.find(p => p.port === port.port) - ); - const addedPorts = newPorts.filter( - port => !currentPorts.find(p => p.port === port.port) - ); - const openedPorts: number[] = []; - - if (removedPorts.length > 0) { - effects.notificationToast.add({ - title: `Server Ports Closed`, - message: - removedPorts.length === 1 - ? `Port ${removedPorts[0].port} closed` - : `The following ports closed: ${removedPorts - .map(p => p.port) - .join(', ')}`, - status: NotificationStatus.NOTICE, - }); - } - - editor.devToolTabs.forEach(view => { - view.views.forEach(tab => { - if ( - tab.id === 'codesandbox.browser' && - tab.options && - tab.options.port - ) { - openedPorts.push(tab.options.port); - } - }); - }); - - addedPorts.forEach(port => { - if (!port.main && openedPorts.indexOf(port.port) === -1) { - effects.notificationToast.add({ - title: `Port ${port.port} Opened`, - message: `The server is listening on port ${port.port}, do you want to open it?`, - status: NotificationStatus.NOTICE, - actions: { - primary: { - label: 'Open Browser Pane', - run: () => actions.server.onBrowserFromPortOpened(port), - }, - }, - }); - } - }); - - server.ports = newPorts; - - break; - } - case 'sandbox:error': { - const { message: error, unrecoverable } = data; - - server.hasUnrecoverableError = unrecoverable; - server.error = error; - - if (unrecoverable) { - effects.notificationToast.add({ - title: `Container Error`, - message: error, - status: NotificationStatus.ERROR, - }); - effects.executor.closeExecutor(); - } else if (!error.includes('is larger than maximum size')) { - effects.notificationToast.add({ - title: `Container Warning`, - message: error, - status: NotificationStatus.WARNING, - }); - } - - break; - } - case 'shell:exit': - effects.codesandboxApi.exitShell(data); - break; - case 'shell:out': - effects.codesandboxApi.outShell(data); - break; - } -}; - -export const onCodeSandboxAPIMessage = ( - { effects }: Context, - { - data, - }: { - data: any; - } -) => { - if (data.type === 'socket:message') { - const { channel, type: _t, codesandbox: _c, ...message } = data; - effects.executor.emit(channel, message); - } -}; - -type BrowserOptions = { title?: string; url?: string } & ( - | { port: number } - | { url: string } -); -export const onBrowserTabOpened = ( - { actions, state }: Context, - { - closeable, - options, - }: { - closeable?: boolean; - options?: BrowserOptions; - } -) => { - const tab: ViewTab = { - id: 'codesandbox.browser', - }; - - if (typeof options !== 'undefined') { - tab.options = options; - } - - if (typeof closeable !== 'undefined') { - tab.closeable = closeable; - } - - const position = getDevToolsTabPosition({ - tabs: state.editor.devToolTabs, - tab, - }); - - if (position) { - actions.editor.onDevToolsPositionChanged({ position }); - } else { - actions.editor.onDevToolsTabAdded({ tab }); - } -}; - -export const onBrowserFromPortOpened = ( - { actions }: Context, - { main, port }: ServerPort -) => { - if (main) { - actions.server.onBrowserTabOpened({}); - } else { - actions.server.onBrowserTabOpened({ - closeable: true, - options: { - port, - }, - }); - } -}; - -export const startContainer = async ( - { effects, actions }: Context, - sandbox: Sandbox -) => { - await effects.executor.initializeExecutor(sandbox); - - [ - 'connect', - 'disconnect', - 'sandbox:status', - 'sandbox:start', - 'sandbox:stop', - 'sandbox:error', - 'sandbox:log', - 'sandbox:hibernate', - 'sandbox:update', - 'sandbox:port', - 'shell:out', - 'shell:exit', - ].forEach(message => { - effects.executor.listen(message, actions.server.onSSEMessage); - }); - - await effects.executor.setupExecutor(); -}; diff --git a/packages/app/src/app/overmind/namespaces/server/index.ts b/packages/app/src/app/overmind/namespaces/server/index.ts deleted file mode 100755 index 0d573be52de..00000000000 --- a/packages/app/src/app/overmind/namespaces/server/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { state } from './state'; -import * as actions from './actions'; - -export { state, actions }; diff --git a/packages/app/src/app/overmind/namespaces/server/state.ts b/packages/app/src/app/overmind/namespaces/server/state.ts deleted file mode 100755 index fcab24e2de4..00000000000 --- a/packages/app/src/app/overmind/namespaces/server/state.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - ServerContainerStatus, - ServerPort, - ServerStatus, -} from '@codesandbox/common/lib/types'; - -type State = { - status: ServerStatus; - containerStatus: ServerContainerStatus; - error: string | null; - hasUnrecoverableError: false; - ports: ServerPort[]; -}; - -export const state: State = { - status: ServerStatus.INITIALIZING, - containerStatus: ServerContainerStatus.INITIALIZING, - error: null, - hasUnrecoverableError: false, - ports: [], -}; diff --git a/packages/app/src/app/overmind/namespaces/workspace/actions.ts b/packages/app/src/app/overmind/namespaces/workspace/actions.ts deleted file mode 100755 index 771bcf503c2..00000000000 --- a/packages/app/src/app/overmind/namespaces/workspace/actions.ts +++ /dev/null @@ -1,635 +0,0 @@ -import getTemplate from '@codesandbox/common/lib/templates'; -import { Dependency } from '@codesandbox/common/lib/types/algolia'; -import { CustomTemplate } from '@codesandbox/common/lib/types'; -import track from '@codesandbox/common/lib/utils/analytics'; -import slugify from '@codesandbox/common/lib/utils/slugify'; -import { Context } from 'app/overmind'; -import { withOwnedSandbox } from 'app/overmind/factories'; -import getItems from 'app/overmind/utils/items'; -import { json } from 'overmind'; -import { SearchResults } from './state'; - -export const valueChanged = ( - { state }: Context, - { - field, - value, - }: { - field: string; - value: string; - } -) => { - state.workspace.project[field] = value; -}; - -export const tagChanged = ({ state }: Context, tagName: string) => { - state.workspace.tags.tagName = tagName; -}; - -export const tagAdded = withOwnedSandbox( - async ({ state, effects, actions }: Context) => { - const { tagName } = state.workspace.tags; - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - const cleanTag = tagName.replace(/#/g, ''); - - sandbox.tags.push(cleanTag); - - try { - sandbox.tags = await effects.api.createTag(sandbox.id, cleanTag); - - await actions.editor.internal.updateSandboxPackageJson(); - } catch (error) { - const index = sandbox.tags.indexOf(cleanTag); - sandbox.tags.splice(index, 1); - actions.internal.handleError({ message: 'Unable to add tag', error }); - } - } -); - -export const tagRemoved = withOwnedSandbox( - async ({ state, effects, actions }: Context, tag: string) => { - const sandbox = state.editor.currentSandbox; - - if (!sandbox) { - return; - } - - const tagIndex = sandbox.tags.indexOf(tag); - - sandbox.tags.splice(tagIndex, 1); - - try { - sandbox.tags = await effects.api.deleteTag(sandbox.id, tag); - - if ( - !state.editor.parsedConfigurations || - !state.editor.currentPackageJSON - ) { - return; - } - // Create a "joint action" on this - if (!state.editor.parsedConfigurations.package) { - return; - } - const { parsed } = state.editor.parsedConfigurations.package; - - if (!parsed) { - return; - } - - parsed.keywords = sandbox.tags; - parsed.name = slugify(sandbox.title || sandbox.id); - parsed.description = sandbox.description; - - const code = JSON.stringify(parsed, null, 2); - const moduleShortid = state.editor.currentPackageJSON.shortid; - - await actions.editor.codeSaved({ - code, - moduleShortid, - cbID: null, - }); - } catch (error) { - sandbox.tags.splice(tagIndex, 0, tag); - actions.internal.handleError({ message: 'Unable to remove tag', error }); - } - } -); - -export const tagsChanged = async ( - { actions, effects, state }: Context, - { - newTags, - removedTags, - }: { - newTags: string[]; - removedTags: string[]; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - const { tags } = state.editor.currentSandbox; - if (tags.length > 5) { - effects.notificationToast.error('You can have a maximum of 5 tags'); - return; - } - - const tagWasRemoved = - newTags.length < tags.length && removedTags.length === 1; - if (tagWasRemoved) { - removedTags.forEach(actions.workspace.tagRemoved); - return; - } - - await actions.workspace.tagAdded(); -}; - -/** tagsChanged2 takes new tags and does the diffing on its own - * This is v2 of tagsChanged. It's used in the redesign - */ -export const tagsChanged2 = withOwnedSandbox( - async ({ state, effects, actions }: Context, newTags: string[]) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) return; - - const { tags: oldTags } = sandbox; - - const removedTags = oldTags.filter(tag => !newTags.includes(tag)); - const addedTags = newTags.filter(tag => !oldTags.includes(tag)); - - removedTags.forEach(actions.workspace.tagRemoved); - - addedTags.forEach(async tag => { - const cleanTag = tag.replace(/#/g, ''); - - // use old methods to update tags - actions.workspace.tagChanged(cleanTag); - await actions.workspace.tagAdded(); - }); - } -); - -export const sandboxInfoUpdated = withOwnedSandbox( - async ({ state, effects, actions }: Context) => { - const sandbox = state.editor.currentSandbox; - if (!sandbox) { - return; - } - const { project } = state.workspace; - const hasChangedTitle = - project.title.trim() && sandbox.title !== project.title; - const hasChangedDescription = - project.description.trim() && sandbox.description !== project.description; - const hasChangedAlias = project.alias && sandbox.alias !== project.alias; - const hasChanged = - hasChangedTitle || hasChangedDescription || hasChangedAlias; - - if (hasChanged) { - try { - let event = 'Alias'; - - if (hasChangedTitle) { - event = 'Title'; - } else if (hasChangedDescription) { - event = 'Description'; - } - - effects.analytics.track(`Sandbox - Update ${event}`); - - sandbox.title = project.title; - sandbox.description = project.description; - sandbox.alias = project.alias; - - const updatedSandbox = await effects.api.updateSandbox(sandbox.id, { - title: project.title, - description: project.description, - alias: project.alias, - }); - - if (!updatedSandbox) { - effects.notificationToast.error('Could not update Sandbox'); - return; - } - - effects.router.replaceSandboxUrl(updatedSandbox); - - await actions.editor.internal.updateSandboxPackageJson(); - } catch (error) { - actions.internal.handleError({ - message: - 'We were not able to save your sandbox updates, please try again', - error, - }); - } - } - } -); - -export const externalResourceAdded = withOwnedSandbox( - async ({ effects, state, actions }: Context, resource: string) => { - if (!state.editor.currentSandbox) { - return; - } - const { externalResources } = state.editor.currentSandbox; - - externalResources.push(resource); - actions.editor.internal.updatePreviewCode(); - - try { - await effects.api.createResource( - state.editor.currentSandbox.id, - resource - ); - if (state.live.isLive) { - effects.live.sendExternalResourcesChanged( - state.editor.currentSandbox.externalResources - ); - } - } catch (error) { - externalResources.splice(externalResources.indexOf(resource), 1); - actions.internal.handleError({ - message: 'Could not save external resource', - error, - }); - actions.editor.internal.updatePreviewCode(); - } - } -); - -export const externalResourceRemoved = withOwnedSandbox( - async ({ effects, state, actions }: Context, resource: string) => { - if (!state.editor.currentSandbox) { - return; - } - - const { externalResources } = state.editor.currentSandbox; - const resourceIndex = externalResources.indexOf(resource); - - externalResources.splice(resourceIndex, 1); - actions.editor.internal.updatePreviewCode(); - - try { - await effects.api.deleteResource( - state.editor.currentSandbox.id, - resource - ); - if (state.live.isLive) { - effects.live.sendExternalResourcesChanged( - state.editor.currentSandbox.externalResources - ); - } - } catch (error) { - externalResources.splice(resourceIndex, 0, resource); - - actions.internal.handleError({ - message: 'Could not save removal of external resource', - error, - }); - actions.editor.internal.updatePreviewCode(); - } - } -); - -export const integrationsOpened = ({ state }: Context) => { - state.preferences.itemId = 'integrations'; - // I do not think this showModal is used? - state.preferences.showModal = true; -}; - -export const sandboxDeleted = async ({ state, effects, actions }: Context) => { - actions.modalClosed(); - - if (!state.editor.currentSandbox) { - return; - } - - await effects.api.deleteSandbox(state.editor.currentSandbox.id); - - // Not sure if this is in use? - state.workspace.showDeleteSandboxModal = false; - effects.notificationToast.success('Sandbox deleted!'); - - effects.router.redirectToSandboxWizard(); -}; - -export const sandboxPrivacyChanged = async ( - { actions, effects, state }: Context, - { - privacy, - source = 'generic', - }: { - privacy: /* public */ 0 | /* unlisted */ 1 | /* private */ 2; - source?: string; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - track('Sandbox - Update Privacy', { privacy, source }); - - // Save oldPrivacy in case we need to reset - const oldPrivacy = state.editor.currentSandbox.privacy; - // Optimistically update privacy - state.editor.currentSandbox.privacy = privacy; - - // The rest endpoint doesn't allow free users and teams to change their privacy - // at all, so we have to use the gql mutation to update the privacy from private - // or unlisted to public. - if (privacy === 0) { - try { - await effects.gql.mutations.changePrivacy({ - sandboxIds: [state.editor.currentSandbox.id], - privacy: 0, - }); - - const { isServer } = getTemplate(state.editor.currentSandbox.template); - const isChangeFromPrivate = oldPrivacy === 2; - - if (isServer && isChangeFromPrivate) { - actions.server.restartContainer(); - } - } catch (error) { - // Reset previous state - state.editor.currentSandbox.privacy = oldPrivacy; - - actions.internal.handleError({ - message: "We weren't able to update the sandbox privacy", - error, - }); - } - } else { - try { - const sandbox = await effects.api.updatePrivacy( - state.editor.currentSandbox.id, - privacy - ); - - state.editor.currentSandbox.previewSecret = sandbox.previewSecret; - - const { isServer } = getTemplate(state.editor.currentSandbox.template); - const isChangeToPrivate = oldPrivacy !== 2 && privacy === 2; - const isChangeFromPrivate = oldPrivacy === 2 && privacy !== 2; - - if (isServer && (isChangeToPrivate || isChangeFromPrivate)) { - // Privacy changed from private to unlisted/public or other way around, restart - // the sandbox to notify containers - actions.server.restartContainer(); - } - - if (state.editor.currentSandbox.draft && privacy === 2) { - // Optimistically update editing restriction when draft is set to private - state.editor.currentSandbox.restricted = false; - } - } catch (error) { - // Reset previous state - state.editor.currentSandbox.privacy = oldPrivacy; - - actions.internal.handleError({ - message: "We weren't able to update the sandbox privacy", - error, - }); - } - } -}; - -export const setWorkspaceItem = ( - { state, effects }: Context, - { - item, - }: { - item: string; - } -) => { - effects.analytics.track('Sidebar - Changed Workspace Item', { item }); - state.workspace.openedWorkspaceItem = item; -}; - -export const toggleCurrentWorkspaceItem = ({ state }: Context) => { - state.workspace.workspaceHidden = !state.workspace.workspaceHidden; -}; - -export const setWorkspaceHidden = ( - { state, effects }: Context, - { hidden }: { hidden: boolean } -) => { - effects.analytics.track('Sidebar - Set Visibility', { hidden }); - - state.workspace.workspaceHidden = hidden; - effects.vscode.resetLayout(); -}; - -export const deleteTemplate = async ({ state, actions, effects }: Context) => { - effects.analytics.track('Template - Removed', { source: 'editor' }); - if ( - !state.editor.currentSandbox || - !state.editor.currentSandbox.customTemplate - ) { - return; - } - const sandbox = state.editor.currentSandbox; - const templateId = state.editor.currentSandbox.customTemplate.id; - - try { - await effects.api.deleteTemplate(sandbox.id, templateId); - - sandbox.isFrozen = false; - sandbox.customTemplate = null; - actions.modalClosed(); - effects.notificationToast.success('Template Deleted'); - } catch (error) { - actions.internal.handleError({ - message: 'Could not delete custom template', - error, - }); - } -}; - -export const editTemplate = async ( - { state, actions, effects }: Context, - template: CustomTemplate -) => { - if (!state.editor.currentSandbox) { - return; - } - - effects.analytics.track('Template - Edited', { source: 'editor' }); - - const sandboxId = state.editor.currentSandbox.id; - - try { - const updatedTemplate = await effects.api.updateTemplate( - sandboxId, - template - ); - - actions.modalClosed(); - state.editor.currentSandbox.customTemplate = updatedTemplate; - effects.notificationToast.success('Template Edited'); - } catch (error) { - actions.internal.handleError({ - message: 'Could not edit custom template', - error, - }); - } -}; - -export const addedTemplate = async ( - { state, actions, effects }: Context, - template: { - color: string; - description: string; - title: string; - } -) => { - if (!state.editor.currentSandbox) { - return; - } - - effects.analytics.track('Template - Created', { source: 'editor' }); - - const sandboxId = state.editor.currentSandbox.id; - - try { - const newTemplate = await effects.api.createTemplate(sandboxId, template); - const sandbox = state.editor.currentSandbox; - sandbox.isFrozen = true; - sandbox.customTemplate = newTemplate; - actions.modalClosed(); - effects.notificationToast.success('Successfully created the template'); - } catch (error) { - actions.internal.handleError({ - message: 'Could not create template, please try again later', - error, - }); - if (process.env.NODE_ENV === 'development') { - console.error(error); - } - } -}; - -export const openDefaultItem = ({ state }: Context) => { - const items = getItems(state); - const defaultItem = items.find(i => i.defaultOpen) || items[0]; - - state.workspace.openedWorkspaceItem = defaultItem.id; -}; - -export const changeDependencySearch = ({ state }: Context, value: string) => { - state.workspace.dependencySearch = value; -}; - -export const getExplorerDependencies = async ( - { state, effects }: Context, - value: string -) => { - state.workspace.explorerDependenciesEmpty = false; - if (!value) { - state.workspace.explorerDependencies = []; - return; - } - state.workspace.loadingDependencySearch = true; - const searchResults = await effects.algoliaSearch.searchDependencies( - value, - 4 - ); - - state.workspace.loadingDependencySearch = false; - if (searchResults.length) { - state.workspace.explorerDependencies = searchResults; - } else { - state.workspace.explorerDependenciesEmpty = true; - } -}; - -export const clearExplorerDependencies = ({ state }: Context) => { - state.workspace.explorerDependencies = []; -}; - -export const getDependencies = async ( - { state, effects }: Context, - value: string -) => { - state.workspace.loadingDependencySearch = true; - const searchResults = await effects.algoliaSearch.searchDependencies(value); - - state.workspace.loadingDependencySearch = false; - state.workspace.dependencies = searchResults; -}; - -export const setSelectedDependencies = ( - { state }: Context, - dependency: Dependency -) => { - const selectedDependencies = state.workspace.selectedDependencies; - const versionMap = state.workspace.hitToVersionMap; - const dep = json(dependency); - - if (selectedDependencies[dep.objectID]) { - delete selectedDependencies[dep.objectID]; - delete versionMap[dep.objectID]; - } else { - selectedDependencies[dep.objectID] = dep; - } -}; - -export const handleVersionChange = ( - { state }: Context, - { - dependency, - version, - }: { - dependency: Dependency; - version: string; - } -) => { - if (state.editor.parsedConfigurations?.package?.parsed?.dependencies) { - const installedVersion = - state.editor.parsedConfigurations.package.parsed.dependencies[ - dependency.objectID - ]; - - /* Remove the dependency as the same version is already installed */ - if (installedVersion === version) { - const selectedDependencies = state.workspace.selectedDependencies; - const versionMap = state.workspace.hitToVersionMap; - delete selectedDependencies[dependency.objectID]; - delete versionMap[dependency.objectID]; - return; - } - } - state.workspace.hitToVersionMap[dependency.objectID] = version; -}; - -export const clearSelectedDependencies = ({ state }: Context) => { - state.workspace.selectedDependencies = {}; -}; - -export const toggleShowingSelectedDependencies = ({ state }: Context) => { - state.workspace.showingSelectedDependencies = !state.workspace - .showingSelectedDependencies; -}; - -export const searchValueChanged = ({ state }: Context, value: string) => { - state.workspace.searchValue = value; -}; - -export const filesToIncludeChanged = ({ state }: Context, value: string) => { - state.workspace.searchOptions.filesToInclude = value; -}; - -export const filesToExcludeChanged = ({ state }: Context, value: string) => { - state.workspace.searchOptions.filesToExclude = value; -}; - -export const openResult = ({ state }: Context, id: number) => { - state.workspace.searchResults[id].open = !state.workspace.searchResults[id] - .open; -}; - -export const searchResultsChanged = ( - { state }: Context, - results: SearchResults -) => { - state.workspace.searchResults = results; -}; - -export const searchOptionsToggled = ({ state }: Context, option: string) => { - state.workspace.searchOptions[option] = !state.workspace.searchOptions[ - option - ]; -}; - -export const toggleEditingSandboxInfo = ( - { state }: Context, - value: boolean -) => { - state.workspace.editingSandboxInfo = value; -}; diff --git a/packages/app/src/app/overmind/namespaces/workspace/index.ts b/packages/app/src/app/overmind/namespaces/workspace/index.ts deleted file mode 100755 index 0d573be52de..00000000000 --- a/packages/app/src/app/overmind/namespaces/workspace/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { state } from './state'; -import * as actions from './actions'; - -export { state, actions }; diff --git a/packages/app/src/app/overmind/namespaces/workspace/state.ts b/packages/app/src/app/overmind/namespaces/workspace/state.ts deleted file mode 100755 index 808fa190935..00000000000 --- a/packages/app/src/app/overmind/namespaces/workspace/state.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Module } from '@codesandbox/common/lib/types'; -import { Dependency } from '@codesandbox/common/lib/types/algolia'; - -export enum OptionTypes { - CaseSensitive = 'caseSensitive', - Regex = 'regex', - MatchFullWord = 'matchFullWord', -} - -export type SearchResults = (Module & { - open: boolean; - matches: [number, number][]; -})[]; - -type State = { - project: { - title: string; - description: string; - alias: string; - }; - tags: { - tagName: string; - }; - openedWorkspaceItem: string | null; - workspaceHidden: boolean; - showDeleteSandboxModal: boolean; - dependencies: Dependency[]; - explorerDependencies: Dependency[]; - explorerDependenciesEmpty: boolean; - selectedDependencies: - | { - [a: string]: Dependency; - } - | {}; - loadingDependencySearch: boolean; - hitToVersionMap: { - [name: string]: string; - }; - showingSelectedDependencies: boolean; - dependencySearch: string; - searchValue: string; - searchResults: SearchResults | []; - searchOptions: { - [OptionTypes.CaseSensitive]: boolean; - [OptionTypes.Regex]: boolean; - [OptionTypes.MatchFullWord]: boolean; - showFileFilters: boolean; - openFilesSearch: boolean; - filesToInclude: string; - filesToExclude: string; - }; - activeThumb: { [k: string]: { dataURI: string; type: string } } | null; - uploadingThumb: boolean; - editingSandboxInfo: boolean; -}; - -export const state: State = { - project: { - title: '', - description: '', - alias: '', - }, - tags: { - tagName: '', - }, - openedWorkspaceItem: null, - workspaceHidden: false, - showDeleteSandboxModal: false, - dependencies: [], - explorerDependencies: [], - explorerDependenciesEmpty: false, - selectedDependencies: {}, - loadingDependencySearch: false, - hitToVersionMap: {}, - showingSelectedDependencies: false, - dependencySearch: '', - searchValue: '', - searchResults: [], - - searchOptions: { - [OptionTypes.CaseSensitive]: false, - [OptionTypes.Regex]: false, - [OptionTypes.MatchFullWord]: false, - showFileFilters: false, - openFilesSearch: false, - filesToInclude: '', - filesToExclude: '', - }, - activeThumb: null, - uploadingThumb: false, - editingSandboxInfo: false, -}; diff --git a/packages/app/src/app/overmind/state.ts b/packages/app/src/app/overmind/state.ts index 60bd9f66d26..ac7aa152f1d 100755 --- a/packages/app/src/app/overmind/state.ts +++ b/packages/app/src/app/overmind/state.ts @@ -40,7 +40,6 @@ type State = { notifications: Notification[]; isLoadingCLI: boolean; isLoadingGithub: boolean; - isLoadingVercel: boolean; pendingUserId: string | null; pendingUser: PendingUserType; // Persists the primaryWorkspaceId for a fresh user until redirect @@ -138,7 +137,6 @@ export const state: State = { connected: true, notifications: [], contributors: [], - isLoadingVercel: false, isLoadingCLI: false, isLoadingGithub: false, contextMenu: { diff --git a/packages/app/src/app/overmind/utils/comments.ts b/packages/app/src/app/overmind/utils/comments.ts deleted file mode 100644 index 20f92be4cb8..00000000000 --- a/packages/app/src/app/overmind/utils/comments.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { UserQuery } from '@codesandbox/common/lib/types'; -import { - CommentFragment, - Reference, - UserReferenceMetadata, - ImageReferenceMetadata, -} from 'app/graphql/types'; - -export function convertMentionsToMentionLinks( - value: string, - mentions: { [mentionName: string]: UserQuery } -) { - return Object.keys(mentions).reduce( - (aggr, username) => - aggr.replace( - new RegExp('@' + username, 'ig'), - `[@${username}](user://${mentions[username].id})` - ), - value - ); -} - -export function convertMentionLinksToMentions(value = '') { - const words = value.split(' '); - - return words - .reduce((aggr, word) => { - const match = word.match(/\[(.*)\]\(user:\/\/.*\)/); - - if (match) { - return aggr.concat(match[1]); - } - - return aggr.concat(word); - }, []) - .join(' '); -} - -export function convertMentionsToUserReferences(mentions: { - [username: string]: UserQuery; -}) { - return Object.keys(mentions).reduce< - (Reference & { metadata: UserReferenceMetadata })[] - >((aggr, key) => { - const userQuery = mentions[key]; - return aggr.concat({ - id: '', - type: 'user', - resource: `user://${userQuery.id}`, - metadata: { - userId: userQuery.id, - username: userQuery.username, - }, - }); - }, []); -} - -export function convertUserReferencesToMentions( - userReferences: CommentFragment['references'] -) { - return userReferences.reduce<{ - [username: string]: UserQuery; - }>((aggr, reference) => { - if (reference.type === 'user') { - const metadata = reference.metadata as UserReferenceMetadata; - aggr[metadata.username] = { - id: metadata.userId, - username: metadata.username, - avatarUrl: '', - }; - } - - return aggr; - }, {}); -} - -export function convertImagesToImageReferences(images: { - [fileName: string]: { src: string; resolution: [number, number] }; -}) { - return Object.keys(images).reduce< - (Reference & { metadata: ImageReferenceMetadata })[] - >((aggr, key) => { - const image = images[key]; - return aggr.concat({ - id: '', - type: 'image', - resource: '', - metadata: { - fileName: key, - resolution: image.resolution, - uploadId: 0, - url: image.src, - }, - }); - }, []); -} - -export function convertImageReferencesToMarkdownImages( - value: string, - imageReferences: CommentFragment['references'] -) { - return imageReferences.reduce((aggr, reference) => { - if (reference.type === 'image') { - const metadata = reference.metadata as ImageReferenceMetadata; - - return aggr.replace( - // We do not check the full ![](FILE_NAME) signature, only (FILE_NAME) - // as this is more than enough - new RegExp('\\(' + metadata.fileName + '\\)', 'ig'), - `(${reference.resource})` - ); - } - - return aggr; - }, value); -} diff --git a/packages/app/src/app/overmind/utils/common.ts b/packages/app/src/app/overmind/utils/common.ts index 5fa9ebe9781..44719a54523 100755 --- a/packages/app/src/app/overmind/utils/common.ts +++ b/packages/app/src/app/overmind/utils/common.ts @@ -1,30 +1,9 @@ -import { Module } from '@codesandbox/common/lib/types'; import { fromPairs, sortBy, toPairs } from 'lodash-es'; export function sortObjectByKeys(object: object) { return fromPairs(sortBy(toPairs(object))); } -export function createOptimisticModule(overrides: Partial) { - return { - id: null, - title: '', - directoryShortid: null, - code: '', - savedCode: null, - shortid: null, - isBinary: false, - sourceId: null, - insertedAt: new Date().toString(), - updatedAt: new Date().toString(), - isNotSynced: true, - errors: [], - corrections: [], - type: 'file' as 'file', - ...overrides, - }; -} - export function lineAndColumnToIndex( lines: string[], lineNumber: number, diff --git a/packages/app/src/app/overmind/utils/files.ts b/packages/app/src/app/overmind/utils/files.ts deleted file mode 100644 index 3c689392fcb..00000000000 --- a/packages/app/src/app/overmind/utils/files.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getModulePath } from '@codesandbox/common/lib/sandbox/modules'; -import getDefinition from '@codesandbox/common/lib/templates'; -import { Sandbox } from '@codesandbox/common/lib/types'; - -import { resolveModuleWrapped } from './resolve-module-wrapped'; - -export const getModuleCode = (sandbox: Sandbox, prettierConfig: unknown) => { - const path = getModulePath(sandbox.modules, sandbox.directories, module.id); - const template = getDefinition(sandbox.template); - const config = template.configurationFiles[path]; - - if ( - config && - (config.generateFileFromSandbox || - config.getDefaultCode || - config.generateFileFromState) - ) { - if (config.generateFileFromState) { - return config.generateFileFromState(prettierConfig); - } - if (config.generateFileFromSandbox) { - return config.generateFileFromSandbox(sandbox); - } - if (config.getDefaultCode) { - const resolveModule = resolveModuleWrapped(sandbox); - - return config.getDefaultCode(sandbox.template, resolveModule); - } - } - - return ''; -}; diff --git a/packages/app/src/app/overmind/utils/items.ts b/packages/app/src/app/overmind/utils/items.ts deleted file mode 100755 index 28278f25f37..00000000000 --- a/packages/app/src/app/overmind/utils/items.ts +++ /dev/null @@ -1,216 +0,0 @@ -import getTemplate from '@codesandbox/common/lib/templates'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { config } from 'app/overmind'; -import { Overmind } from 'overmind'; - -export interface INavigationItem { - id: string; - name: string; - hasCustomHeader?: boolean; - defaultOpen?: boolean; - /** - * If the item is not applicable in the current situation we sometimes still - * want to show it because of visibility. This boolean decides that. - */ - showAsDisabledIfHidden?: boolean; -} - -export const PROJECT: INavigationItem = { - id: 'project', - name: 'Sandbox Info', -}; - -export const PROJECT_TEMPLATE: INavigationItem = { - ...PROJECT, - name: 'Template Info', -}; - -export const PROJECT_SUMMARY: INavigationItem = { - id: 'project-summary', - name: 'Sandbox Info', - hasCustomHeader: true, -}; - -export const SEARCH: INavigationItem = { - id: 'search', - name: 'Search', -}; - -export const FILES: INavigationItem = { - id: 'files', - name: 'Explorer', - hasCustomHeader: true, - defaultOpen: true, -}; - -export const GITHUB: INavigationItem = { - id: 'github', - name: 'GitHub', - showAsDisabledIfHidden: true, -}; - -export const DEPLOYMENT: INavigationItem = { - id: 'deploy', - name: 'Deployment', - showAsDisabledIfHidden: true, -}; - -export const CONFIGURATION: INavigationItem = { - id: 'config', - name: 'Configuration Files', -}; - -export const LIVE: INavigationItem = { - id: 'live', - name: 'Live', - showAsDisabledIfHidden: true, -}; - -export const SERVER: INavigationItem = { - id: 'server', - name: 'Server Control Panel', -}; - -export const COMMENTS: INavigationItem = { - id: 'comments', - name: 'Comments', -}; - -export const DOCKER: INavigationItem = { - id: 'docker', - name: 'Docker', -}; - -export const VSCODE: INavigationItem = { - id: 'vscode', - name: 'VS Code', -}; - -export const AITOOLS: INavigationItem = { - id: 'ai', - name: 'AI Tools', -}; - -export function getDisabledItems(store: any): INavigationItem[] { - const { currentSandbox } = store.editor; - - if (!currentSandbox) { - return [ - PROJECT_SUMMARY, - FILES, - SEARCH, - CONFIGURATION, - GITHUB, - COMMENTS, - DEPLOYMENT, - LIVE, - ]; - } - - if (currentSandbox.git) { - return [CONFIGURATION, DEPLOYMENT, LIVE]; - } - - if (!currentSandbox.owned || !store.isLoggedIn) { - const returnedItems = [GITHUB, DEPLOYMENT]; - if (!store.live.isLive) { - returnedItems.push(LIVE); - } - return returnedItems; - } - - return []; -} - -export default function getItems( - store: Overmind['state'] -): INavigationItem[] { - if (!store.editor.currentSandbox) { - return []; - } - - if ( - store.live.isLive && - !( - store.live.isOwner || - (store.user && - store.live && - store.live.roomInfo && - store.live.roomInfo.ownerIds.indexOf(store.user.id) > -1) - ) && - !hasPermission(store.editor.currentSandbox.authorization, 'write_project') - ) { - return [FILES, LIVE]; - } - - const { currentSandbox } = store.editor; - - const isServer = - store.isLoggedIn && - currentSandbox && - getTemplate(currentSandbox.template).isServer; - - if (currentSandbox.git) { - const gitItems = [PROJECT_SUMMARY]; - - if ( - isServer && - hasPermission(currentSandbox.authorization, 'write_project') - ) { - gitItems.push(SERVER); - } - - return gitItems; - } - - if (!currentSandbox || !currentSandbox.owned) { - return [PROJECT_SUMMARY, SEARCH, CONFIGURATION]; - } - - const isCustomTemplate = !!currentSandbox.customTemplate; - const items = [ - isCustomTemplate ? PROJECT_TEMPLATE : PROJECT, - FILES, - SEARCH, - CONFIGURATION, - ]; - - if (isServer) { - items.push(SERVER); - } - - if ( - store.isLoggedIn && - !store.environment.isOnPrem && - currentSandbox && - !currentSandbox.git - ) { - if ( - currentSandbox.featureFlags.comments && - hasPermission(currentSandbox.authorization, 'comment') - ) { - items.push(GITHUB, COMMENTS); - } else { - items.push(GITHUB); - } - } - - if (store.isLoggedIn && !store.environment.isOnPrem) { - items.push(DEPLOYMENT); - } - - if ( - store.isLoggedIn && - currentSandbox && - hasPermission(currentSandbox.authorization, 'write_code') - ) { - items.push(LIVE); - } - - if (store.environment.isOnPrem) { - return items; - } - - // Add docker, vs code and AI tools as conversion to v2 screens - return [...items, DOCKER, VSCODE, AITOOLS]; -} diff --git a/packages/app/src/app/overmind/utils/main-module.ts b/packages/app/src/app/overmind/utils/main-module.ts deleted file mode 100755 index 7b870057b4d..00000000000 --- a/packages/app/src/app/overmind/utils/main-module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Module, Sandbox } from '@codesandbox/common/lib/types'; -import getDefinition from '@codesandbox/common/lib/templates'; -import { resolveModuleWrapped } from './resolve-module-wrapped'; - -export function mainModule(sandbox: Sandbox, parsedConfigurations: any) { - const templateDefinition = getDefinition(sandbox.template); - - const resolve = resolveModuleWrapped(sandbox); - - const module = templateDefinition - .getEntries(parsedConfigurations) - .map(p => resolve(p)) - .find(m => Boolean(m)); - - return module || sandbox.modules[0]; -} - -export function defaultOpenedModule( - sandbox: Sandbox, - parsedConfigurations: any -): Module | undefined { - const templateDefinition = getDefinition(sandbox.template); - - const resolve = resolveModuleWrapped(sandbox); - - const module = templateDefinition - .getDefaultOpenedFiles(parsedConfigurations) - .map(p => resolve(p)) - .find(m => Boolean(m)); - - return module || sandbox.modules[0]; -} diff --git a/packages/app/src/app/overmind/utils/server.ts b/packages/app/src/app/overmind/utils/server.ts deleted file mode 100644 index 4476e4db2f6..00000000000 --- a/packages/app/src/app/overmind/utils/server.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - ViewConfig, - ViewTab, -} from '@codesandbox/common/lib/templates/template'; -import { DevToolsTabPosition } from '@codesandbox/common/lib/types'; - -// eslint-disable-next-line consistent-return -export function getDevToolsTabPosition({ - tabs, - tab, -}: { - tabs: ViewConfig[]; - tab: ViewTab; -}): DevToolsTabPosition | undefined { - const serializedTab = JSON.stringify(tab); - - for (let i = 0; i < tabs.length; i++) { - const view = tabs[i]; - - for (let j = 0; j < view.views.length; j++) { - const tabFounded = view.views[j]; - if (JSON.stringify(tabFounded) === serializedTab) { - return { - devToolIndex: i, - tabPosition: j, - }; - } - } - } -} diff --git a/packages/app/src/app/pages/Dashboard/Components/Repository/RepositoryCard.tsx b/packages/app/src/app/pages/Dashboard/Components/Repository/RepositoryCard.tsx index 40f4f172648..018db6c389d 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Repository/RepositoryCard.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Repository/RepositoryCard.tsx @@ -46,7 +46,7 @@ export const RepositoryCard: React.FC = ({ diff --git a/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx b/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx index 404ba44f597..de6f6d5e06f 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Sandbox/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { useHistory, Link } from 'react-router-dom'; import { getEmptyImage } from 'react-dnd-html5-backend'; import formatDistanceStrict from 'date-fns/formatDistanceStrict'; import { zonedTimeToUtc } from 'date-fns-tz'; @@ -12,7 +11,6 @@ import track, { } from '@codesandbox/common/lib/utils/analytics'; import { Icon } from '@codesandbox/components'; import { formatNumber } from '@codesandbox/components/lib/components/Stats'; -import { useBetaSandboxEditor } from 'app/hooks/useBetaSandboxEditor'; import { SandboxCard } from './SandboxCard'; import { SandboxListItem } from './SandboxListItem'; @@ -69,7 +67,6 @@ function getFolderName(item: GenericSandboxProps['item']): string | undefined { const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { const { user, dashboard, activeWorkspaceAuthorization } = useAppState(); - const [hasBetaEditorExperiment] = useBetaSandboxEditor(); const actions = useActions(); const { sandbox } = item; @@ -90,8 +87,7 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { const viewCount = formatNumber(sandbox.viewCount); - const url = sandboxUrl(sandbox, hasBetaEditorExperiment); - const linksToV2 = sandbox.isV2 || (!sandbox.isSse && hasBetaEditorExperiment); + const url = sandboxUrl(sandbox); const TemplateIcon = getTemplateIcon(sandbox); const PrivacyIcon = PrivacyIcons[sandbox.privacy]; @@ -171,8 +167,6 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { [onRightClick, onMenuEvent, sandbox.id] ); - const history = useHistory(); - const onDoubleClick = event => { if (page === 'deleted') { // Can't open deleted items, they don't exist anymore so we open @@ -183,10 +177,8 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { if (event.ctrlKey || event.metaKey) { window.open(url, '_blank'); - } else if (linksToV2) { - window.location.href = url; } else { - history.push(url); + window.location.href = url; } } }; @@ -241,9 +233,8 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { ? { ...baseInteractions, interaction: 'link' as const, - as: linksToV2 ? 'a' : Link, - to: linksToV2 ? undefined : url, - href: linksToV2 ? url : undefined, + as: 'a', + href: url, onClick: () => { // On the recent page the sandbox card is an anchor, so we only have // to track using onclick, the sandbox is opened through native anchor @@ -302,7 +293,7 @@ const GenericSandbox = ({ isScrolling, item, page }: GenericSandboxProps) => { }, [preview]); return ( -
    +
    = ({ const { activeTeam } = useAppState(); const { isPro } = useWorkspaceSubscription(); - const [hasBetaEditorExperiment] = useBetaSandboxEditor(); const { browser: { copyToClipboard }, @@ -40,8 +38,7 @@ export const SandboxMenu: React.FC = ({ const { userRole, isTeamAdmin, isTeamViewer } = useWorkspaceAuthorization(); const { isFrozen } = useWorkspaceLimits(); - const url = sandboxUrl(sandbox, hasBetaEditorExperiment); - const linksToV2 = sandbox.isV2 || (!sandbox.isSse && hasBetaEditorExperiment); + const url = sandboxUrl(sandbox); const folderUrl = getFolderUrl(item, activeTeam); const boxType = sandbox.isV2 ? 'devbox' : 'sandbox'; @@ -99,10 +96,9 @@ export const SandboxMenu: React.FC = ({ {isTemplate && hasWriteAccess ? ( { - actions.editor.forkExternalSandbox({ + actions.dashboard.forkSandbox({ sandboxId: sandbox.id, openInNewWindow: true, - hasBetaEditorExperiment, redirectAfterFork: true, }); }} @@ -112,11 +108,7 @@ export const SandboxMenu: React.FC = ({ ) : null} { - if (linksToV2) { - window.location.href = url; - } else { - history.push(url); - } + window.location.href = url; }} > Open @@ -150,13 +142,12 @@ export const SandboxMenu: React.FC = ({ {hasWriteAccess && !isTemplate ? ( { - actions.editor.forkExternalSandbox({ + actions.dashboard.forkSandbox({ sandboxId: sandbox.id, openInNewWindow: true, - hasBetaEditorExperiment, redirectAfterFork: true, body: { - privacy: sandbox.draft ? 2 : (sandbox.privacy as 2 | 1 | 0), + privacy: sandbox.privacy as 2 | 1 | 0, collectionId: sandbox.draft ? undefined : sandbox.collection.id, }, }); diff --git a/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx b/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx index 549250a15a5..b9be2dc9796 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Selection/DragPreview.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useDragLayer } from 'react-dnd'; import { motion } from 'framer-motion'; -import { Stack, Text, Icon } from '@codesandbox/components'; +import { Stack, Text, Icon, Element } from '@codesandbox/components'; import { getSandboxName } from '@codesandbox/common/lib/utils/get-sandbox-name'; import css from '@styled-system/css'; import { SIDEBAR_WIDTH } from '../../Sidebar/constants'; @@ -158,7 +158,7 @@ export const DragPreview: React.FC = React.memo( }} > {selectedIds.length > 1 ? ( -
    = React.memo( })} > {selectedIds.length} -
    + ) : null} {selectedItems.map((item, index) => ( diff --git a/packages/app/src/app/pages/Dashboard/Components/Selection/index.tsx b/packages/app/src/app/pages/Dashboard/Components/Selection/index.tsx index cb4b84c7f76..b1f5a69f2dc 100644 --- a/packages/app/src/app/pages/Dashboard/Components/Selection/index.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/Selection/index.tsx @@ -18,8 +18,6 @@ import { dashboard as dashboardUrls, } from '@codesandbox/common/lib/utils/url-generator'; -import { useBetaSandboxEditor } from 'app/hooks/useBetaSandboxEditor'; - import { DragPreview } from './DragPreview'; import { ContextMenu } from './ContextMenu'; import { @@ -105,7 +103,6 @@ export const SelectionProvider: React.FC = ({ children, interactive = true, }) => { - const [hasBetaEditorExperiment] = useBetaSandboxEditor(); const possibleItems = (items || []).filter( item => item.type === 'sandbox' || @@ -325,7 +322,6 @@ export const SelectionProvider: React.FC = ({ const selectedId = selectedIds[0]; let url: string; - let linksToV2 = false; if (selectedId.startsWith('/')) { // means its a folder url = dashboardUrls.sandboxes(selectedId, activeTeamId); @@ -333,18 +329,13 @@ export const SelectionProvider: React.FC = ({ const selectedItem = sandboxes.find( item => item.sandbox.id === selectedId ); - url = sandboxUrl(selectedItem.sandbox, hasBetaEditorExperiment); - linksToV2 = - selectedItem.sandbox.isV2 || - (!selectedItem.sandbox.isSse && hasBetaEditorExperiment); + url = sandboxUrl(selectedItem.sandbox); } if (event.ctrlKey || event.metaKey) { window.open(url, '_blank'); - } else if (linksToV2) { - window.location.href = url; } else { - history.push(url, { focus: 'FIRST_ITEM' }); + window.location.href = url; } } diff --git a/packages/app/src/app/pages/Dashboard/Components/SyncedSandbox/SyncedSandboxCard.tsx b/packages/app/src/app/pages/Dashboard/Components/SyncedSandbox/SyncedSandboxCard.tsx index 4be447519a8..ffe633776a0 100644 --- a/packages/app/src/app/pages/Dashboard/Components/SyncedSandbox/SyncedSandboxCard.tsx +++ b/packages/app/src/app/pages/Dashboard/Components/SyncedSandbox/SyncedSandboxCard.tsx @@ -21,7 +21,7 @@ export const SyncedSandboxCard = ({ name, path, url, ...props }) => { = ({ )} - + {isAdmin && ( + + )} void; diff --git a/packages/app/src/app/pages/Dashboard/Sidebar/UsageProgress.tsx b/packages/app/src/app/pages/Dashboard/Sidebar/UsageProgress.tsx index 3a1b5d8bf6f..7fc2e849987 100644 --- a/packages/app/src/app/pages/Dashboard/Sidebar/UsageProgress.tsx +++ b/packages/app/src/app/pages/Dashboard/Sidebar/UsageProgress.tsx @@ -1,4 +1,4 @@ -import { Link, Stack, Text, Tooltip } from '@codesandbox/components'; +import { Element, Link, Stack, Text, Tooltip } from '@codesandbox/components'; import { dashboard as dashboardUrls } from '@codesandbox/common/lib/utils/url-generator'; import css from '@styled-system/css'; import React, { useEffect, useState } from 'react'; @@ -50,7 +50,7 @@ export const UsageProgress: React.FC<{
    } > -
    -
    -
    + ( - <> - - Something went wrong - - - - {`It seems like this session doesn't exist or has been closed`} - - - - - - -); diff --git a/packages/app/src/app/pages/Live/Error/index.tsx b/packages/app/src/app/pages/Live/Error/index.tsx deleted file mode 100644 index 63e7c052749..00000000000 --- a/packages/app/src/app/pages/Live/Error/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Button, Text } from '@codesandbox/components'; -import css from '@styled-system/css'; -import React, { FunctionComponent } from 'react'; -import { Link } from 'react-router-dom'; - -import { useAppState } from 'app/overmind'; - -import { RoomNotFoundError } from './RoomNotFoundError'; - -export const Error: FunctionComponent = () => { - const { error } = useAppState().live; - - if (error === 'room not found') { - return ; - } - - return ( - <> - - An error occurred while connecting to the live session: - - - - {error} - - - - - - - ); -}; diff --git a/packages/app/src/app/pages/Live/NotAuthenticated.tsx b/packages/app/src/app/pages/Live/NotAuthenticated.tsx deleted file mode 100644 index 9e627b8f217..00000000000 --- a/packages/app/src/app/pages/Live/NotAuthenticated.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Button, Element, Stack, Text } from '@codesandbox/components'; -import React, { FunctionComponent } from 'react'; -import { useParams } from 'react-router-dom'; - -import { useActions } from 'app/overmind'; - -export const NotAuthenticated: FunctionComponent = () => { - const { signInToRoom } = useActions().live; - const { roomId } = useParams<{ roomId: string }>(); - - return ( - <> - - Sign in required - - - - You need to sign in to join this session - - - - - - - ); -}; diff --git a/packages/app/src/app/pages/Live/index.tsx b/packages/app/src/app/pages/Live/index.tsx deleted file mode 100644 index f6b894f50fc..00000000000 --- a/packages/app/src/app/pages/Live/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import CodeSandboxBlack from '@codesandbox/components/lib/themes/codesandbox-black'; -import { getSandboxName } from '@codesandbox/common/lib/utils/get-sandbox-name'; -import { Element, Stack, ThemeProvider } from '@codesandbox/components'; -import css from '@styled-system/css'; -import React, { FunctionComponent, useEffect } from 'react'; -import { Helmet } from 'react-helmet'; -import { useParams } from 'react-router-dom'; - -import { useAppState, useActions } from 'app/overmind'; -import { Navigation } from 'app/pages/common/Navigation'; - -import { Editor } from '../Sandbox/Editor'; - -import { Error } from './Error'; -import { NotAuthenticated } from './NotAuthenticated'; - -export const Live: FunctionComponent = () => { - const { onNavigateAway, roomJoined } = useActions().live; - const { - editor: { currentSandbox }, - isAuthenticating, - live: { error }, - user, - } = useAppState(); - const { roomId } = useParams<{ roomId: string }>(); - - useEffect(() => { - roomJoined(roomId); - }, [roomJoined, roomId]); - - useEffect(() => () => onNavigateAway(), [onNavigateAway]); - - const getContent = () => { - if (!isAuthenticating && !user) { - return ; - } - - if (error) { - return ; - } - - return null; - }; - - const content = getContent(); - if (content) { - return ( - - - - - - - {content} - - - - - ); - } - - return ( - <> - {currentSandbox ? ( - - {getSandboxName(currentSandbox)} - CodeSandbox - - ) : null} - - - - ); -}; diff --git a/packages/app/src/app/pages/Profile/ContextMenu.tsx b/packages/app/src/app/pages/Profile/ContextMenu.tsx index 3d6c6883f49..2ba4731afe1 100644 --- a/packages/app/src/app/pages/Profile/ContextMenu.tsx +++ b/packages/app/src/app/pages/Profile/ContextMenu.tsx @@ -15,7 +15,7 @@ export const ContextMenu = () => { }, } = useAppState(); const { - editor: { forkExternalSandbox }, + dashboard: { forkSandbox }, profile: { addFeaturedSandboxes, removeFeaturedSandboxes, @@ -120,7 +120,7 @@ export const ContextMenu = () => { { - forkExternalSandbox({ + forkSandbox({ sandboxId, openInNewWindow: true, redirectAfterFork: true, diff --git a/packages/app/src/app/pages/Profile/SandboxCard.tsx b/packages/app/src/app/pages/Profile/SandboxCard.tsx index 33f15931a9e..a2e7c79e19e 100644 --- a/packages/app/src/app/pages/Profile/SandboxCard.tsx +++ b/packages/app/src/app/pages/Profile/SandboxCard.tsx @@ -8,6 +8,7 @@ import { IconButton, SkeletonText, isMenuClicked as isTargetInMenu, + Element, } from '@codesandbox/components'; import designLanguage from '@codesandbox/components/lib/design-language/theme'; import css from '@styled-system/css'; @@ -233,7 +234,7 @@ export const SandboxCard: React.FC<{ })} {...props} > -
    )} -
    { - const state = useAppState(); - - const sandbox = state.editor.currentSandbox; - const { currentModule } = state.editor; - const modulePath = currentModule.path; - const template = sandbox && getTemplateDefinition(sandbox.template); - const config = template && template.configurationFiles[modulePath]; - - const followingUserId = state.live.followingUserId; - let followingUser: LiveUser | undefined; - - if (followingUserId && state.live.roomInfo && state.live.isLive) { - followingUser = state.live.roomInfo.users.find( - u => u.id === followingUserId - ); - } - - return ( - - {followingUser && ( -
    - 128 - ? 'rgba(0, 0, 0, 0.8)' - : 'white', - - ':hover': { - opacity: 0.5, - }, - }} - > - Following {followingUser.username} -
    - )} - - {config ? ( - - {config.partialSupportDisclaimer ? ( - - Partially Supported Config{' '} - - - ) : ( -
    Supported Configuration
    - )} -
    - ) : null} -
    - ); -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/Icons.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/Icons.tsx deleted file mode 100644 index 4166e00e0b8..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/Icons.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; - -export const SwitchIcon = (props: { color: string }) => ( - - - -); -export const ArrowDown = (props: { color: string }) => ( - - - -); - -export const CloseIcon = (props: { color: string }) => ( - - - -); - -export const ResizeIcon = props => ( - - - - - - - - - - - -); - -export const DismissIcon = props => ( - - - -); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/InstallExtensionBanner.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/InstallExtensionBanner.tsx deleted file mode 100644 index c14de477d75..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/InstallExtensionBanner.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import Modal from 'app/components/Modal'; -import css from '@styled-system/css'; -import { ThemeProvider, Stack, Text } from '@codesandbox/components'; -import { useAppState, useActions } from 'app/overmind'; -import { Alert } from 'app/pages/common/Modals/Common/Alert'; -import * as React from 'react'; -import { withTheme } from 'styled-components'; -import { DismissIcon } from './Icons'; - -const ADDRESSBAR_HEIGHT = 35; -const RESPONSIVE_BAR_HEIGHT = 40; - -const clearButtonStyles = { - background: 'transparent', - display: 'flex', - alignItems: 'center', - padding: 0, - border: 'none', -}; - -export const InstallExtensionBanner = withTheme(({ theme }: { theme: any }) => { - const [isShowing, setShowing] = React.useState(false); - const { - preview: { mode }, - modals, - } = useAppState(); - const actions = useActions(); - - React.useEffect(() => { - setShowing(true); - }, []); - - const isResponsive = - mode === 'responsive' || mode === 'responsive-add-comment'; - - const getTop = () => { - if (isShowing) { - if (isResponsive) { - return ADDRESSBAR_HEIGHT + RESPONSIVE_BAR_HEIGHT; - } - return ADDRESSBAR_HEIGHT; - } - - return 0; - }; - - return ( - - - - - - Get a better experience creating comments - - - - actions.modals.extensionInstalledModal.close(false)} - > - { - actions.modals.extensionInstalledModal.close(false); - }} - cancelMessage="Cancel" - onPrimaryAction={() => { - actions.modals.extensionInstalledModal.close(true); - }} - confirmMessage="Ok" - /> - - - - ); -}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/PresetMenu.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/PresetMenu.tsx deleted file mode 100644 index b343eedae1f..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/PresetMenu.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import css from '@styled-system/css'; -import { isEqual } from 'lodash-es'; -import { Element, Menu, Text } from '@codesandbox/components'; -import { ArrowDown } from './Icons'; - -type PresetMenuProps = { - canChangePresets: boolean; - presets: { [name: string]: [number, number] }; - theme: any; - resolution: [number, number]; - onSelect: (preset: [number, number]) => void; - openEditPresets: () => void; -}; - -export const PresetMenu = ({ - presets, - theme, - resolution, - onSelect, - openEditPresets, - canChangePresets, -}: PresetMenuProps) => { - const activePresetName = - Object.keys(presets).find(preset => isEqual(presets[preset], resolution)) || - 'Custom'; - - const sortedPresetsByWidth = Object.keys(presets).sort((a, b) => - presets[a][0] > presets[b][0] ? 1 : -1 - ); - - return ( - - - {activePresetName} - - - - - - {sortedPresetsByWidth.map(preset => ( - onSelect(presets[preset])} - > - {preset} - - ))} - {canChangePresets ? ( - - Edit Presets - - ) : null} - - - ); -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/PreviewCommentWrapper.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/PreviewCommentWrapper.tsx deleted file mode 100644 index 7e2d05cd052..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/PreviewCommentWrapper.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { - CommentWithRepliesFragment, - PreviewReferenceMetadata, -} from 'app/graphql/types'; -import { useActions, useAppState } from 'app/overmind'; -import * as React from 'react'; -import { Icon } from '@codesandbox/components'; -import styled, { createGlobalStyle } from 'styled-components'; - -const BUBBLE_SIZE = 16; - -const Wrapper = styled.div({ - height: '100%', - position: 'relative', - zIndex: 2, -}); - -const Screenshot = styled.div({ - position: 'absolute', - left: 0, - top: 0, - zIndex: 1, - backgroundSize: 'cover', - width: '100%', - height: '100%', -}); - -const BorderOverlay = styled.div<{ showCommentCursor: boolean }>(props => ({ - position: 'absolute', - left: 0, - top: 0, - width: '100%', - height: '100%', - boxSizing: 'border-box', - border: '4px solid #FFF', - color: '#FF3B30', - zIndex: 2, - cursor: props.showCommentCursor - ? `url('data:image/svg+xml;utf8,'), auto` - : 'inherit', -})); - -const PreviewBubble = styled(Icon)<{ active: boolean }>({ - position: 'absolute', - width: BUBBLE_SIZE, - height: BUBBLE_SIZE, - color: '#FF3B30', - zIndex: 2, - cursor: 'inherit', -}); - -function getPreviewReference( - comment: CommentWithRepliesFragment | null -): PreviewReferenceMetadata | null { - if ( - comment && - comment.anchorReference && - comment.anchorReference.type === 'preview' - ) { - return comment.anchorReference.metadata as PreviewReferenceMetadata; - } - - return null; -} - -type Props = { - children: any; - scale: number; -}; - -const FlashAnimationGlobalStyle = createGlobalStyle` - @keyframes screenshot-flash { - 0% { opacity: 0 } - 25% { opacity: 1 } - 35% { opacity: 1 } - 100% { opacity: 0 } - } - .screenshot-flash { - animation: screenshot-flash 0.5s linear; - } -`; - -const useFlash = ( - ref: React.MutableRefObject, - activate: boolean -) => { - React.useEffect(() => { - if (ref.current && activate) { - const flashEl = document.createElement('div'); - flashEl.style.position = 'absolute'; - flashEl.style.left = '0'; - flashEl.style.top = '0'; - flashEl.style.width = '100%'; - flashEl.style.height = '100%'; - flashEl.style.opacity = '0'; - flashEl.style.zIndex = '999999'; - flashEl.style.backgroundColor = 'white'; - flashEl.className = 'screenshot-flash'; - - const disposer = () => { - if (ref.current) { - flashEl.removeEventListener('animationend', onAnimationEnd); - if (flashEl.parentNode) { - flashEl.parentNode.removeChild(flashEl); - } - } - }; - const onAnimationEnd = disposer; - flashEl.addEventListener('animationend', onAnimationEnd); - ref.current.appendChild(flashEl); - - return disposer; - } - - return () => {}; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ref.current, activate]); -}; - -export const PreviewCommentWrapper = ({ children, scale }: Props) => { - const state = useAppState(); - const actions = useActions(); - const wrapperRef = React.useRef(null); - const previewReference = getPreviewReference(state.comments.currentComment); - const isAddingPreviewComment = - state.preview.mode === 'add-comment' || - state.preview.mode === 'responsive-add-comment'; - const hasSource = Boolean(state.preview.screenshot.source); - - useFlash(wrapperRef, isAddingPreviewComment && hasSource); - - return ( - - - {children} - {isAddingPreviewComment ? ( - <> - - { - if (state.preview.screenshot.isLoading) { - return; - } - - const parentBounds = (event.target as any).parentNode.getBoundingClientRect(); - - actions.comments.addOptimisticPreviewComment({ - x: event.clientX - parentBounds.left, - y: event.clientY - parentBounds.top, - screenshot: state.preview.screenshot.source, - scale, - }); - }} - showCommentCursor={hasSource} - > - {previewReference ? ( - - ) : null} - - - ) : null} - - ); -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/ResizeHandles.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/ResizeHandles.tsx deleted file mode 100644 index 494676ccfac..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/ResizeHandles.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import React from 'react'; - -import { - CornerResize, - Cover, - HeightResize, - MIN_SIZE_X, - MIN_SIZE_Y, - PADDING_OFFSET_X, - PADDING_OFFSET_Y, - ResizeWrapper, - Styled, - WidthResize, -} from './elements'; - -type ResizeHandlesProps = { - on: boolean; - showResizeHandles: boolean; - width: number; - height: number; - wrapper: HTMLDivElement; - scale: number; - widthAndHeightResizer: [ - { x: number; y: number } | null, - ({ x, y }: { x: number; y: number }) => void - ]; - widthResizer: [{ x: number } | null, ({ x }: { x: number }) => void]; - heightResizer: [{ y: number } | null, ({ y }: { y: number }) => void]; - setResolution: (resolution: [number, number]) => void; - children: React.ReactNode; - setIsResizing: (isResizing: boolean) => void; -}; - -const HANDLE_OFFSET = 15; - -const resize = ( - event: MouseEvent | React.MouseEvent, - { - resolution, - initialMousePosition, - wrapper, - scale, - offset, - resizer: [_, setSize], - setResolution, - setIsResizing, - }: { - resolution: [number, number]; - // Holds the actual mouse position and its offset on the 4px handle to determine - // where it should stop resizing. [position, handleOffset] - initialMousePosition: { x?: number; y?: number }; - // We need the wrapper, because we already introduce elements at the bottom of it, - // and we might introduce stuff on the right side. Meaning our constraint logic - // needs to know where the edges are - wrapper: HTMLDivElement; - // We need to know the offset of the handle, which is different from the explicit width - // and height handles and the both direction resizer - offset: number; - scale: number; - resizer: [{ x?: number; y?: number }, (payload: any) => void]; - setResolution: (resolution: [number, number]) => void; - setIsResizing: (isResizing: boolean) => void; - } -) => { - const [initialWidth, initialHeight] = resolution; - const newSize = { - x: resolution[0], - y: resolution[1], - }; - - function calculate(evt: MouseEvent | React.MouseEvent) { - const { - right: wrapperRight, - bottom: wrapperBottom, - } = wrapper.getBoundingClientRect(); - const maxClientX = Math.min( - evt.clientX, - wrapperRight - PADDING_OFFSET_X + offset - ); - const maxClientY = Math.min( - evt.clientY, - wrapperBottom - PADDING_OFFSET_Y + offset - ); - const width = - 'x' in initialMousePosition - ? initialWidth - (initialMousePosition.x - maxClientX) * (1 / scale) * 2 - : resolution[0]; - const height = - 'y' in initialMousePosition - ? initialHeight - (initialMousePosition.y - maxClientY) * (1 / scale) - : resolution[1]; - const positiveWidth = width > MIN_SIZE_X ? width : MIN_SIZE_X; - const positiveHeight = height > MIN_SIZE_Y ? height : MIN_SIZE_Y; - - // The preview can only be resized on its right and bottom side, which always align - // with the far bottom/right of the browser - newSize.x = Math.round( - 'x' in initialMousePosition ? positiveWidth : newSize.x - ); - newSize.y = Math.round( - 'y' in initialMousePosition ? positiveHeight : newSize.y - ); - } - - const mouseMoveListener: (event: MouseEvent) => void = evt => { - calculate(evt); - setSize({ ...newSize }); - }; - const mouseUpListener: (event: MouseEvent) => void = evt => { - window.removeEventListener('mousemove', mouseMoveListener); - window.removeEventListener('mouseup', mouseUpListener); - calculate(evt); - setResolution([newSize.x, newSize.y]); - setSize(null); - setIsResizing(false); - }; - - window.addEventListener('mousemove', mouseMoveListener); - window.addEventListener('mouseup', mouseUpListener); - - calculate(event); - setSize({ ...newSize }); - setIsResizing(true); -}; - -export const ResizeHandles = ({ - showResizeHandles, - on, - width, - height, - wrapper, - scale, - setResolution, - widthAndHeightResizer, - widthResizer, - heightResizer, - children, - setIsResizing, -}: ResizeHandlesProps) => ( - -
    - {showResizeHandles ? ( - <> - { - resize(event, { - resolution: [width, height], - scale, - wrapper, - offset: 0, - initialMousePosition: { x: event.clientX, y: event.clientY }, - resizer: widthAndHeightResizer, - setResolution, - setIsResizing, - }); - }} - style={{ - right: `calc(50% - ${(width * scale) / 2}px)`, - top: `calc(${height * scale + HANDLE_OFFSET}px)`, - }} - /> - { - resize(event, { - resolution: [width, height], - scale, - wrapper, - offset: HANDLE_OFFSET, - initialMousePosition: { x: event.clientX }, - resizer: widthResizer, - setResolution, - setIsResizing, - }); - }} - style={{ - right: `calc(50% - ${HANDLE_OFFSET}px - ${ - (width * scale) / 2 - }px)`, - top: `calc(${(height / 2) * scale - 8}px)`, - }} - /> - { - resize(event, { - resolution: [width, height], - wrapper, - scale, - offset: HANDLE_OFFSET, - initialMousePosition: { y: event.clientY }, - resizer: heightResizer, - setResolution, - setIsResizing, - }); - }} - style={{ - top: `calc(${height * scale + HANDLE_OFFSET * 3}px)`, - }} - /> - - ) : null} - - {widthAndHeightResizer[0] || widthResizer[0] || heightResizer[0] ? ( - - ) : null} - {children} - -
    -
    -); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/elements.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/elements.tsx deleted file mode 100644 index bda0e9a1c52..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/elements.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from 'react'; -import styled, { css } from 'styled-components'; -import { Element } from '@codesandbox/components'; -import { ResizeIcon } from './Icons'; - -export const MIN_SIZE_X = 72; - -export const MIN_SIZE_Y = 130; - -export const PADDING_OFFSET_X = 30; - -export const PADDING_OFFSET_Y = 30; - -export const Styled = styled(({ on: _, ...props }) => )<{ - on: boolean; - theme: any; -}>` - height: 100%; - - > div { - overflow: auto; - margin: auto; - background: ${props => props.theme['input.background']}; - height: 100%; - position: relative; - } - - ${props => - props.on - ? css` - iframe { - border-radius: 2px; - box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.24), - 0px 8px 4px rgba(0, 0, 0, 0.12); - } - ` - : null} - - > div > span { - min-width: 70px; - min-height: 130px; - position: absolute; - top: 0; - left: 0; - display: block; - } -`; - -export const Wrapper = styled(Element)` - background: ${props => props.theme['sideBar.background']}; - - input::-webkit-outer-spin-button, - input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - - input[type='number'] { - width: 50px; - -moz-appearance: textfield; - } -`; - -export const ResizeWrapper = styled.span` - position: relative; - width: 100%; - height: 100%; - transform-origin: top; -`; - -export const CornerResize = styled(ResizeIcon)` - position: absolute; - bottom: -0; - right: 0; - cursor: nwse-resize; - z-index: 2; -`; - -const sizeResize = css` - position: absolute; - border-radius: 50px; - opacity: 0.6; - background-color: ${props => props.theme['sideBar.foreground']}; - - z-index: 2; - user-select: none; - transition: opacity 200ms ease; - - :hover { - opacity: 1; - } -`; - -export const WidthResize = styled(Element)` - width: 4px; - height: 41px; - cursor: ew-resize; - ${sizeResize} -`; - -export const HeightResize = styled(Element)` - left: calc(50% - 20.5px); - width: 41px; - height: 4px; - cursor: ns-resize; - ${sizeResize} -`; - -export const Cover = styled.div` - position: absolute; - z-index: 1; - top: 0; - left: 0; - width: 100%; - height: 100%; - user-select: none; -`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/index.tsx deleted file mode 100644 index 3af43835117..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/index.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import { - Stack, - Text, - Button, - Input, - ThemeProvider, - IconButton, -} from '@codesandbox/components'; -import ResizeObserver from 'resize-observer-polyfill'; -import track from '@codesandbox/common/lib/utils/analytics'; -import css from '@styled-system/css'; -import { json } from 'overmind'; -import React, { useEffect, useState } from 'react'; -import { isEqual } from 'lodash-es'; - -import { useTheme } from 'styled-components'; -import { useAppState, useActions } from 'app/overmind'; -import { hasPermission } from '@codesandbox/common/lib/utils/permission'; -import { SwitchIcon } from './Icons'; -import { ResponsiveWrapperProps } from './types'; -import { PresetMenu } from './PresetMenu'; -import { - Wrapper, - PADDING_OFFSET_X, - PADDING_OFFSET_Y, - MIN_SIZE_X, - MIN_SIZE_Y, -} from './elements'; -import { ResizeHandles } from './ResizeHandles'; -import { PreviewCommentWrapper } from './PreviewCommentWrapper'; - -export const ResponsiveWrapper = ({ children }: ResponsiveWrapperProps) => { - const { - preview: { - responsive: { resolution, scale: stateScale, presets }, - mode, - }, - editor, - } = useAppState(); - const { - checkURLParameters, - openDeletePresetModal, - openAddPresetModal, - setResolution, - toggleEditPresets, - setIsResizing - } = useActions().preview; - - const theme = useTheme(); - const canChangePresets = hasPermission( - editor.currentSandbox!.authorization, - 'write_code' - ); - const on = mode === 'responsive' || mode === 'responsive-add-comment'; - - const element = document.getElementById('styled-resize-wrapper'); - const [wrapperWidth, setWrapperWidth] = useState( - element?.getBoundingClientRect().width - ); - const [wrapperHeight, setWrapperHeight] = useState( - element?.getBoundingClientRect().height - ); - const widthAndHeightResizer = useState<{ x: number; y: number } | null>(null); - const widthResizer = useState<{ x: number } | null>(null); - const heightResizer = useState<{ y: number } | null>(null); - const [resolutionWidth, resolutionHeight] = resolution; - - const minScaleResolutionWidth = Math.max( - resolutionWidth || MIN_SIZE_X, - MIN_SIZE_X - ); - const minScaleResolutionHeight = Math.max( - resolutionHeight || MIN_SIZE_Y, - MIN_SIZE_Y - ); - - const minResolutionWidth = Math.max( - widthResizer[0]?.x || - widthAndHeightResizer[0]?.x || - resolutionWidth || - MIN_SIZE_X, - MIN_SIZE_X - ); - const minResolutionHeight = Math.max( - heightResizer[0]?.y || - widthAndHeightResizer[0]?.y || - resolutionHeight || - MIN_SIZE_Y, - MIN_SIZE_Y - ); - - let scale = stateScale / 100; - - const hasHeightMostSpace = - wrapperWidth - resolutionWidth < wrapperHeight - resolutionHeight; - - if (hasHeightMostSpace && minScaleResolutionWidth > wrapperWidth) { - scale = (wrapperWidth - PADDING_OFFSET_X) / minScaleResolutionWidth; - } else if (minScaleResolutionHeight > wrapperHeight) { - scale = (wrapperHeight - PADDING_OFFSET_Y) / minScaleResolutionHeight; - } - - useEffect(() => { - let observer; - if (element) { - observer = new ResizeObserver(entries => { - entries.map(entry => { - if (entry.contentRect) { - const sizes = entry.contentRect; - - setWrapperWidth(sizes.width - PADDING_OFFSET_X); - setWrapperHeight(sizes.height - PADDING_OFFSET_Y); - } - - return null; - }); - }); - observer.observe(element); - } - return () => (observer ? observer.disconnect() : null); - }, [element]); - - const exists = Boolean( - Object.keys(presets).find(preset => isEqual(resolution, presets[preset])) - ); - - useEffect(() => { - checkURLParameters(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - // @ts-ignore - - - - - {exists ? ( - - ) : null} - {!exists ? ( - - ) : null} - - - - setResolution([ - parseInt(e.target.value, 10), - resolutionHeight, - ]) - } - />{' '} - - {' '} - - setResolution([resolutionWidth, parseInt(e.target.value, 10)]) - } - type="number" - css={css({ - height: 20, - paddingRight: 0, - '::-webkit-inner-spin-button': { padding: '12px 0' }, - })} - value={ - isNaN(resolutionHeight) - ? '' - : heightResizer[0]?.y || - widthAndHeightResizer[0]?.y || - resolutionHeight - } - /> - - - ( - {Math.ceil(scale * 100) === 100 - ? '1x' - : `${(Math.ceil(scale * 100) / 100).toFixed(2)}x`} - ) - - - { - track('Responsive Preview - Preset Changed', { - width: preset[0], - height: preset[1], - }); - setResolution(preset); - }} - theme={theme} - resolution={resolution} - presets={presets} - canChangePresets={canChangePresets} - /> - - - - - {children} - - - - ); -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/types.ts b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/types.ts deleted file mode 100644 index 7762d72f7ae..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/ResponsiveWrapper/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type ResponsiveWrapperProps = { - children?: any; -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/elements.ts deleted file mode 100644 index 575c2107fc5..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/elements.ts +++ /dev/null @@ -1,87 +0,0 @@ -import styled from 'styled-components'; -import fadeIn from '@codesandbox/common/lib/utils/animation/fade-in'; - -const Resizer = styled.div` - position: absolute; - z-index: 20; -`; - -const width = 8; - -export const TopResizer = styled(Resizer)` - top: -${width / 2}px; - height: ${width}px; - left: 0; - right: 0; - cursor: ns-resize; -`; - -export const RightResizer = styled(Resizer)` - top: 0; - bottom: 0; - width: ${width}px; - right: -${width / 2}px; - cursor: ew-resize; -`; - -export const BottomResizer = styled(Resizer)` - bottom: -${width / 2}px; - height: ${width}px; - left: 0; - right: 0; - cursor: ns-resize; -`; - -export const LeftResizer = styled(Resizer)` - top: 0; - bottom: 0; - width: ${width}px; - left: -${width / 2}px; - cursor: ew-resize; -`; - -export const NEResizer = styled(Resizer)` - top: -${width / 2}px; - right: -${width / 2}px; - width: ${width * 2}px; - height: ${width * 2}px; - cursor: nesw-resize; -`; - -export const SEResizer = styled(Resizer)` - bottom: -${width / 2}px; - right: -${width / 2}px; - width: ${width * 2}px; - height: ${width * 2}px; - cursor: nwse-resize; -`; - -export const SWResizer = styled(Resizer)` - bottom: -${width / 2}px; - left: -${width / 2}px; - width: ${width * 2}px; - height: ${width * 2}px; - cursor: nesw-resize; -`; - -export const NWResizer = styled(Resizer)` - top: -${width / 2}px; - left: -${width / 2}px; - width: ${width * 2}px; - height: ${width * 2}px; - cursor: nwse-resize; -`; - -export const ResizingNotice = styled.div` - position: absolute; - right: 1rem; - bottom: 1rem; - - background-color: rgba(0, 0, 0, 0.7); - color: white; - font-size: 1.125rem; - padding: 0.5rem 1rem; - - border-radius: 2px; - ${fadeIn(0)}; -`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx deleted file mode 100644 index ed09167523e..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { ServerContainerStatus } from '@codesandbox/common/lib/types'; -import BasePreview from '@codesandbox/common/lib/components/Preview'; -import { frameUrl } from '@codesandbox/common/lib/utils/url-generator'; -import RunOnClick from '@codesandbox/common/lib/components/RunOnClick'; - -import React, { FunctionComponent, useState } from 'react'; -import { useAppState, useActions, useEffects } from 'app/overmind'; - -import { ResponsiveWrapper } from './ResponsiveWrapper'; -import { InstallExtensionBanner } from './ResponsiveWrapper/InstallExtensionBanner'; - -type Props = { - hidden?: boolean; - options: { - url?: string; - port?: number; - }; - runOnClick?: boolean; -}; -export const Preview: FunctionComponent = ({ - hidden, - options, - runOnClick, -}) => { - const { - preview: previewActions, - editor: { errorsCleared, previewActionReceived, projectViewToggled }, - } = useActions(); - const { - preview, - editor: { - currentModule, - currentSandbox, - initialPath, - isInProjectView, - isResizing, - previewWindowVisible, - }, - preferences: { settings }, - server: { containerStatus, error, hasUnrecoverableError }, - } = useAppState(); - const { - preview: { initializePreview, canAddComments }, - browser, - } = useEffects(); - const [running, setRunning] = useState(!runOnClick); - - /** - * Responsible for showing a message when something is happening with SSE. Only used - * for server sandboxes right now, but we can extend it in the future. It would require - * a better design if we want to use it for more though. - */ - const getOverlayMessage = () => { - if (containerStatus === ServerContainerStatus.HIBERNATED) { - return 'The container has been hibernated because of inactivity, you can start it by refreshing the browser.'; - } - - if (containerStatus === ServerContainerStatus.STOPPED) { - return 'Restarting the sandbox...'; - } - - if (error && hasUnrecoverableError) { - return 'A sandbox error occurred, you can refresh the page to restart the container.'; - } - - return undefined; - }; - - // Only show in chromium browsers - const showBanner = - preview.showExtensionBanner && browser.isChromium(navigator.userAgent); - - const content = running ? ( - - ) : ( - setRunning(true)} /> - ); - - return ( - <> - {showBanner ? : null} - {content} - - ); -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ConsoleIcon.js b/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ConsoleIcon.js deleted file mode 100644 index 1fa089a190a..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/ConsoleIcon.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import Icon from 'react-icon-base'; - -const Console = props => ( - - - -); - -export default Console; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/PreviewIcon.js b/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/PreviewIcon.js deleted file mode 100644 index d456c2de4ec..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/PreviewIcon.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import Icon from 'react-icon-base'; - -const Preview = props => ( - - - -); - -export default Preview; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/elements.ts deleted file mode 100644 index ba42f955326..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/elements.ts +++ /dev/null @@ -1,111 +0,0 @@ -import styled, { css } from 'styled-components'; -import PrettierIcon from 'react-icons/lib/md/brush'; - -const HEIGHT = '35px'; - -export const Container = styled.div` - position: absolute; - top: 0; - left: 0; - right: 0; - display: flex; - height: ${HEIGHT}; - flex: 0 0 ${HEIGHT}; - color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; - - background-color: ${({ theme }) => - theme['editorGroupHeader.tabsBackground'] || theme.background4}; -`; - -export const TabsContainer = styled.div` - display: flex; - height: ${HEIGHT}; - flex: 1 0 ${HEIGHT}; - - overflow-x: auto; - overflow-y: hidden; - - -ms-overflow-style: none; - - &::-webkit-scrollbar { - height: 2px; - } -`; - -interface IStyledPrettierIconProps { - disabled?: boolean; -} -export const StyledPrettierIcon = styled(PrettierIcon)` - transition: 0.3s ease opacity; - width: 1.125rem; - height: 1.125rem; - cursor: pointer; - - padding-right: 0.5rem; - - opacity: 0.6; - - &:hover { - opacity: 1; - } - - ${(props: IStyledPrettierIconProps) => - props.disabled && - css` - opacity: 0; - pointer-events: none; - `}; -`; - -export const IconContainer = styled.div` - display: flex; - align-items: center; - float: right; - flex-shrink: 1; - padding: 0 0.75rem; -`; - -export const Line = styled.div` - height: 12px; - width: 1px; - - background-color: ${props => - props.theme['editorGroupHeader.tabsBorder'] || 'rgba(255, 255, 255, 0.3)'}; -`; - -interface IIconWrapperProps { - active?: boolean; - disabled?: boolean; - theme: any; -} -export const IconWrapper = styled.div` - svg { - transition: 0.3s ease opacity; - width: 1.25rem; - height: 1.25rem; - cursor: pointer; - - opacity: 0.6; - - padding-left: 0.5rem; - - &:hover { - opacity: 1; - } - - ${(props: IIconWrapperProps) => - props.active && - css` - opacity: 1; - color: ${props.theme['editor.foreground'] || 'white'}; - `}; - - ${props => - props.disabled && - css` - opacity: 0; - pointer-events: none; - `}; - } -`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/index.tsx deleted file mode 100644 index a0a71287f29..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/index.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import React, { useEffect } from 'react'; -import { useAppState, useActions } from 'app/overmind'; -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; -import TabContainer from './TabContainer'; -import PreviewIcon from './PreviewIcon'; - -import { - Container, - TabsContainer, - IconContainer, - StyledPrettierIcon, - IconWrapper, - Line, -} from './elements'; - -import ModuleTab from './ModuleTab'; - -interface IEditorTabsProps { - currentModuleId: string | number; -} - -export const EditorTabs: React.FunctionComponent = ({ - currentModuleId, -}) => { - const editorState = useAppState().editor; - const editorAction = useActions().editor; - - let container = null; - // eslint-disable-next-line react-hooks/exhaustive-deps - const tabEls = {}; - - useEffect(() => { - const currentTab = tabEls[currentModuleId] as ModuleTab; - - // We need to scroll to the tab - if (currentTab && container) { - const { width } = container.getBoundingClientRect(); - const scroll = container.scrollLeft; - const { left } = currentTab.getBoundingClientRect(); - - if (left > scroll && left < scroll + width) { - // if it's already in view - return; - } - - currentTab.scrollIntoView(false); - } - }, [container, currentModuleId, tabEls]); - - const closeTab = tabIndex => { - editorAction.tabClosed(tabIndex); - }; - - const moveTab = (prevIndex, nextIndex) => { - editorAction.tabMoved({ prevIndex, nextIndex }); - }; - - /** - * Mark all tabs not dirty (not cursive) - */ - const markNotDirty = () => { - editorAction.moduleDoubleClicked(); - }; - - const setCurrentModule = moduleId => { - editorAction.moduleSelected({ id: moduleId }); - }; - - const discardModuleChanges = moduleShortid => { - editorAction.discardModuleChanges({ moduleShortid }); - }; - - const prettifyModule = () => { - /* - This no longer exists - - editorAction.prettifyClicked({ - moduleShortid: editorState.currentModuleShortid, - }); - */ - }; - - const canPrettify = module => { - if (!module) { - return false; - } - - return canPrettify(module.title); - }; - - const sandbox = editorState.currentSandbox; - const moduleObject = {}; - // We keep this object to keep track if there are duplicate titles. - // In that case we need to show which directory the module is in. - const tabNamesObject = {}; - - sandbox.modules.forEach(m => { - moduleObject[m.shortid] = m; - }); - - (editorState.tabs.filter(tab => tab.type === 'MODULE') as ModuleTab[]) - .filter(tab => moduleObject[tab.moduleShortid]) - .forEach(tab => { - const module = moduleObject[tab.moduleShortid]; - - tabNamesObject[module.title] = tabNamesObject[module.title] || []; - tabNamesObject[module.title].push(module.shortid); - }); - - const currentTab = editorState.currentTab as ModuleTab; - const { currentModule } = editorState; - - const previewVisible = editorState.previewWindowVisible; - - return ( - - { - container = el; - }} - > - {(editorState.tabs as ModuleTab[]) - .map(tab => ({ ...tab, module: moduleObject[tab.moduleShortid] })) - .map((tab, i) => { - if (tab.type === 'MODULE') { - if (tab.module == null) { - return null; - } - - const { module } = tab; - const modulesWithName = tabNamesObject[module.title]; - const { id } = tab.module; - let dirName = null; - - if ( - modulesWithName.length > 1 && - module.directoryShortid != null - ) { - const dir = sandbox.directories.find( - d => - d.shortid === module.directoryShortid && - d.sourceId === module.sourceId - ); - - if (dir) { - dirName = dir.title; - } - } - - return ( - (error as any).moduleId === id - ).length - )} - closeTab={closeTab} - moveTab={moveTab} - markNotDirty={markNotDirty} - dirName={dirName} - tabCount={editorState.tabs.length} - position={i} - dirty={tab.dirty} - isNotSynced={Boolean( - editorState.changedModuleShortids.includes( - tab.module.shortid - ) - )} - ref={el => { - tabEls[id] = el; - }} - /> - ); - } - if (tab.type === 'DIFF') { - return ( - - editorAction.currentTabChanged({ tabId: tab.id }) - } - closeTab={closeTab} - moveTab={moveTab} - tabCount={editorState.tabs.length} - position={i} - dirty={tab.dirty} - ref={el => { - tabEls[tab.id] = el; - }} - title={`Diff: ${tab.titleA} - ${tab.titleB}`} - /> - ); - } - - return null; - })} - - - - - - - - - - - editorAction.togglePreviewContent()} /> - - - - - ); -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/preview.svg b/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/preview.svg deleted file mode 100644 index 57eabe77774..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Tabs/preview.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/__snapshots__/utils.test.ts.snap b/packages/app/src/app/pages/Sandbox/Editor/Content/__snapshots__/utils.test.ts.snap deleted file mode 100644 index 17a5fd95fc4..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/__snapshots__/utils.test.ts.snap +++ /dev/null @@ -1,174 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`addDevToolsTab can add a tab 1`] = ` -{ - "devTools": [ - { - "views": [ - { - "id": "codesandbox.browser", - }, - { - "id": "codesandbox.tests", - }, - { - "id": "codesandbox.browser", - }, - ], - }, - ], - "position": { - "devToolIndex": 0, - "tabPosition": 2, - }, -} -`; - -exports[`addDevToolsTab can add a tab on specific position 1`] = ` -{ - "devTools": [ - { - "views": [ - { - "id": "codesandbox.tests", - }, - { - "id": "codesandbox.browser", - }, - { - "id": "codesandbox.tests", - }, - ], - }, - ], - "position": { - "devToolIndex": 0, - "tabPosition": 0, - }, -} -`; - -exports[`moveDevToolsTab can move a tab from one devtools to the other 1`] = ` -[ - { - "views": [ - { - "id": "codesandbox.tests", - }, - ], - }, - { - "views": [ - { - "id": "codesandbox.console", - }, - { - "id": "codesandbox.browser", - }, - { - "id": "codesandbox.problems", - }, - ], - }, -] -`; - -exports[`moveDevToolsTab can move a tab from one devtools to the other and move it back 1`] = ` -[ - { - "views": [ - { - "id": "codesandbox.browser", - }, - { - "id": "codesandbox.tests", - }, - ], - }, - { - "views": [ - { - "id": "codesandbox.console", - }, - { - "id": "codesandbox.problems", - }, - ], - }, -] -`; - -exports[`moveDevToolsTab can move a tab in same devtools 1`] = ` -[ - { - "views": [ - { - "id": "codesandbox.tests", - }, - { - "id": "codesandbox.browser", - }, - ], - }, - { - "views": [ - { - "id": "codesandbox.console", - }, - { - "id": "codesandbox.problems", - }, - ], - }, -] -`; - -exports[`moveDevToolsTab can move a tab in same devtools and move the tab back 1`] = ` -[ - { - "views": [ - { - "id": "codesandbox.browser", - }, - { - "id": "codesandbox.tests", - }, - ], - }, - { - "views": [ - { - "id": "codesandbox.console", - }, - { - "id": "codesandbox.problems", - }, - ], - }, -] -`; - -exports[`moveDevToolsTab can move the tab to the same position 1`] = ` -[ - { - "views": [ - { - "id": "codesandbox.browser", - }, - { - "id": "codesandbox.tests", - }, - ], - }, - { - "views": [ - { - "id": "codesandbox.console", - }, - { - "id": "codesandbox.problems", - }, - ], - }, -] -`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/elements.js b/packages/app/src/app/pages/Sandbox/Editor/Content/elements.js deleted file mode 100644 index 5dbdd8442f7..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/elements.js +++ /dev/null @@ -1,14 +0,0 @@ -import styled from 'styled-components'; -import fadeIn from '@codesandbox/common/lib/utils/animation/fade-in'; - -export const FullSize = styled.div` - height: 100%; - width: 100%; - - ${fadeIn(0)}; - display: flex; - flex-direction: column; - - background-color: ${props => - props.theme['editor.background'] || 'transparent'}; -`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/index.tsx deleted file mode 100644 index ea6963dbd2b..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/index.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import getTemplateDefinition from '@codesandbox/common/lib/templates'; -import { BACKTICK } from '@codesandbox/common/lib/utils/keycodes'; -import { VSCode as CodeEditor } from 'app/components/CodeEditor/VSCode'; -import { DevTools } from 'app/components/Preview/DevTools'; -import { terminalUpgrade } from 'app/components/Preview/DevTools/TerminalUpgrade'; -import { useActions, useReaction, useEffects, useAppState } from 'app/overmind'; -import useKey from 'react-use/lib/useKey'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import SplitPane from 'react-split-pane'; -import { ThemeProvider } from 'styled-components'; - -import preventGestureScroll, { removeListener } from './prevent-gesture-scroll'; -import { Preview } from './Preview'; -import { EditorToast } from './EditorToast'; - -export const MainWorkspace: React.FC<{ theme: any }> = ({ theme }) => { - const state = useAppState(); - const actions = useActions(); - const effects = useEffects(); - const reaction = useReaction(); - const editorEl = useRef(null); - const contentEl = useRef(null); - const [showConsoleDevtool, setShowConsoleDevtool] = useState(false); - const [consoleDevtoolIndex, setConsoleDevtoolIndex] = useState(-1); - - const updateEditorSize = useCallback( - function updateEditorSize() { - if (editorEl.current) { - const { width, height } = editorEl.current.getBoundingClientRect(); - effects.vscode.updateLayout(width, height); - } - }, - [effects.vscode] - ); - - useEffect(() => { - const contentNode = contentEl.current; - - const disposeResizeDetector = reaction( - ({ preferences, workspace, editor }) => [ - preferences.settings.zenMode, - workspace.workspaceHidden, - editor.previewWindowOrientation, - ], - () => { - updateEditorSize(); - }, - { - immediate: true, - } - ); - - window.addEventListener('resize', updateEditorSize); - - preventGestureScroll(contentEl.current); - - actions.editor.contentMounted(); - - return () => { - window.removeEventListener('resize', updateEditorSize); - // clearInterval(this.interval); - disposeResizeDetector(); - removeListener(contentNode); - }; - }, [actions.editor, effects.vscode, reaction, updateEditorSize]); - - const views = state.editor.devToolTabs; - - useEffect(() => { - setConsoleDevtoolIndex(() => - views.findIndex( - ({ views: panes }) => - panes.findIndex(pane => pane.id === 'codesandbox.console') !== -1 - ) - ); - }, [views]); - - useKey( - e => e.ctrlKey && e.keyCode === BACKTICK, - e => { - e.preventDefault(); - setShowConsoleDevtool(value => !value); - }, - {}, - [] - ); - - const sandbox = state.editor.currentSandbox; - const { preferences } = state; - const windowVisible = state.editor.previewWindowVisible; - const template = sandbox && getTemplateDefinition(sandbox.template); - const currentPosition = state.editor.currentDevToolsPosition; - - const browserConfig = { - id: 'codesandbox.browser', - title: options => - options.port || options.title - ? `Browser (${options.title || `:${options.port}`})` - : `Browser`, - Content: ({ hidden, options }) => ( -