Implemented folder management.

This commit is contained in:
Tiberiu Chibici 2018-10-05 22:53:27 +03:00
parent 784d4deec8
commit c26732d101
19 changed files with 994 additions and 90 deletions

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/youtube-channel-manager.iml" filepath="$PROJECT_DIR$/.idea/youtube-channel-manager.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

51
.idea/watcherTasks.xml generated Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions">
<TaskOptions isEnabled="true">
<option name="arguments" value="$FileName$ $FileNameWithoutExtension$.css --source-map" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="less" />
<option name="immediateSync" value="true" />
<option name="name" value="Less" />
<option name="output" value="$FileNameWithoutExtension$.css:$FileNameWithoutExtension$.css.map" />
<option name="outputFilters">
<array>
<FilterInfo>
<option name="description" value="" />
<option name="name" value="" />
<option name="regExp" value="$MESSAGE$\Q in \E$FILE_PATH$\Q on line \E$LINE$\Q, column \E$COLUMN$" />
</FilterInfo>
</array>
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$USER_HOME$/AppData/Roaming/npm/lessc.cmd" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="true" />
<option name="workingDir" value="$FileDir$" />
<envs />
</TaskOptions>
<TaskOptions isEnabled="true">
<option name="arguments" value="--no-cache --update $FileName$:$FileNameWithoutExtension$.css" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="scss" />
<option name="immediateSync" value="true" />
<option name="name" value="SCSS" />
<option name="output" value="$FileNameWithoutExtension$.css:$FileNameWithoutExtension$.css.map" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="scss" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="true" />
<option name="workingDir" value="$FileDir$" />
<envs />
</TaskOptions>
</component>
</project>

463
.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,463 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="245751b6-c863-4572-8723-8499964fe105" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/watcherTasks.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/management.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/migrations/0004_auto_20181005_1626.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css.map" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManager/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManager/urls.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.js" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/js/subtree.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/YtManagerApp/views.py" beforeDir="false" afterPath="$PROJECT_DIR$/YtManagerApp/views.py" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="DjangoConsoleOptions" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)">
<option name="myCustomStartScript" value="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" />
</component>
<component name="FUSProjectUsageTrigger">
<session id="1684258556">
<usages-collector id="statistics.lifecycle.project">
<counts>
<entry key="project.closed" value="1" />
<entry key="project.open.time.11" value="1" />
<entry key="project.open.time.3" value="1" />
<entry key="project.opened" value="2" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.extensions.edit">
<counts>
<entry key="css" value="40" />
<entry key="html" value="620" />
<entry key="js" value="2858" />
<entry key="less" value="38" />
<entry key="py" value="1710" />
<entry key="py@youtube-channel-manager" value="19" />
<entry key="scss" value="26" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.types.edit">
<counts>
<entry key="CSS" value="40" />
<entry key="CommandLine" value="19" />
<entry key="HTML" value="620" />
<entry key="JavaScript" value="2858" />
<entry key="Less" value="38" />
<entry key="Python" value="1710" />
<entry key="SCSS" value="26" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.extensions.open">
<counts>
<entry key="css" value="1" />
<entry key="html" value="8" />
<entry key="js" value="5" />
<entry key="less" value="1" />
<entry key="py" value="7" />
<entry key="scss" value="3" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.types.open">
<counts>
<entry key="CSS" value="1" />
<entry key="HTML" value="8" />
<entry key="JavaScript" value="5" />
<entry key="Less" value="1" />
<entry key="Python" value="7" />
<entry key="SCSS" value="3" />
</counts>
</usages-collector>
</session>
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="334">
<caret line="62" lean-forward="true" selection-start-line="62" selection-end-line="62" />
<folding>
<element signature="e#0#35#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/management.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504">
<caret line="42" column="11" selection-start-line="42" selection-start-column="11" selection-end-line="42" selection-end-column="11" />
<folding>
<marker date="1538766618853" expanded="true" signature="90:91" ph="..." />
<marker date="1538766618853" expanded="true" signature="642:651" ph="..." />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1700">
<caret line="100" column="186" selection-start-line="100" selection-start-column="186" selection-end-line="100" selection-end-column="186" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357">
<caret line="21" column="30" lean-forward="true" selection-start-line="21" selection-start-column="30" selection-end-line="21" selection-end-column="30" />
<folding>
<element signature="e#433#440#1#HTML" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="SCSS File" />
<option value="Python Script" />
</list>
</option>
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>lds</find>
<find>get_selected</find>
<find>url</find>
<find>delete_fo</find>
<find>folder</find>
</findStrings>
<replaceStrings>
<replace>loading</replace>
<replace />
<replace>subscription</replace>
<replace>subscription</replace>
</replaceStrings>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.css" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.less" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html" />
<option value="$PROJECT_DIR$/YtManagerApp/views.py" />
<option value="$PROJECT_DIR$/YtManagerApp/management.py" />
<option value="$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/js/subscription_tree.js" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html" />
<option value="$PROJECT_DIR$/YtManager/urls.py" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js" />
<option value="$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="-8" />
<option name="y" value="1" />
<option name="width" value="1400" />
<option name="height" value="1000" />
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManager" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="static" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="static" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="controls" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="youtube-channel-manager" type="b2602c69:ProjectViewProjectNode" />
<item name="youtube-channel-manager" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="templates" type="462c0819:PsiDirectoryNode" />
<item name="YtManagerApp" type="462c0819:PsiDirectoryNode" />
<item name="js" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="SearchEverywhereHistoryKey" value="&#9;FILE&#9;file://D:/Dev/youtube-channel-manager/YtManagerApp/views.py" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$USER_HOME$/AppData/Roaming/npm/lessc.cmd" />
<property name="list.type.of.created.stylesheet" value="SCSS" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
<property name="settings.editor.selected.configurable" value="preferences.sourceCode" />
</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="D:\Dev\youtube-channel-manager\YtManagerApp\templates\YtManagerApp\controls" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="D:\Dev\youtube-channel-manager\YtManagerApp\templates\YtManagerApp" />
</key>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager">
<configuration name="youtube-channel-manager" type="Python.DjangoServer" factoryName="Django server">
<module name="youtube-channel-manager" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="DJANGO_SETTINGS_MODULE" value="YtManager.settings" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="launchJavascriptDebuger" value="false" />
<option name="port" value="8000" />
<option name="host" value="" />
<option name="additionalOptions" value="" />
<option name="browserUrl" value="" />
<option name="runTestServer" value="false" />
<option name="runNoReload" value="false" />
<option name="useCustomRunCommand" value="false" />
<option name="customRunCommand" value="" />
<method v="2" />
</configuration>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="245751b6-c863-4572-8723-8499964fe105" name="Default Changelist" comment="" />
<created>1538745535248</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1538745535248</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="-8" y="-8" width="1936" height="1056" extended-state="6" />
<editor active="true" />
<layout>
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.18017058" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="Favorites" order="2" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" sideWeight="0.49946696" weight="0.329718" />
<window_info anchor="bottom" id="Run" order="2" sideWeight="0.4978678" visible="true" weight="0.30911064" />
<window_info anchor="bottom" id="Debug" order="3" sideWeight="0.49946696" weight="0.3991323" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="manage.py@youtube-channel-manager" order="7" weight="0.3290993" />
<window_info anchor="bottom" id="Docker" order="8" show_stripe_button="false" />
<window_info anchor="bottom" id="Database Changes" order="9" show_stripe_button="false" />
<window_info anchor="bottom" id="Event Log" order="10" sideWeight="0.5021322" side_tool="true" visible="true" weight="0.30911064" />
<window_info anchor="bottom" id="Version Control" order="11" />
<window_info anchor="bottom" id="Terminal" order="12" />
<window_info anchor="bottom" id="Python Console" order="13" weight="0.3290993" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="SciView" order="3" />
<window_info anchor="right" id="Database" order="4" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.less">
<provider selected="true" editor-type-id="text-editor">
<state>
<caret column="18" lean-forward="true" selection-start-column="18" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/import/jstree/jstree.min.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="85">
<caret line="5" column="3166" selection-start-line="5" selection-start-column="3166" selection-end-line="5" selection-end-column="3166" />
<folding>
<element signature="n#style#0;n#ins#0;n#div#0;n#!!top" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/_style.css">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="272">
<caret line="16" lean-forward="true" selection-start-line="16" selection-end-line="17" selection-end-column="26" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/static/YtManagerApp/css/style.scss">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="170">
<caret line="10" lean-forward="true" selection-start-line="10" selection-end-line="10" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/folder_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="13" column="72" selection-start-line="13" selection-start-column="72" selection-end-line="13" selection-end-column="72" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/main_default.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357">
<caret line="26" column="4" lean-forward="true" selection-start-line="26" selection-start-column="4" selection-end-line="26" selection-end-column="4" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/views.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="334">
<caret line="62" lean-forward="true" selection-start-line="62" selection-end-line="62" />
<folding>
<element signature="e#0#35#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/management.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504">
<caret line="42" column="11" selection-start-line="42" selection-start-column="11" selection-end-line="42" selection-end-column="11" />
<folding>
<marker date="1538766618853" expanded="true" signature="90:91" ph="..." />
<marker date="1538766618853" expanded="true" signature="642:651" ph="..." />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManager/urls.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="272">
<caret line="19" lean-forward="true" selection-start-line="19" selection-end-line="19" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="113">
<caret line="20" column="28" selection-start-line="20" selection-start-column="28" selection-end-line="20" selection-end-column="28" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/js/subscription_tree.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1700">
<caret line="100" column="186" selection-start-line="100" selection-start-column="186" selection-end-line="100" selection-end-column="186" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/YtManagerApp/templates/YtManagerApp/controls/subscription_edit_dialog.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="357">
<caret line="21" column="30" lean-forward="true" selection-start-line="21" selection-start-column="30" selection-end-line="21" selection-end-column="30" />
<folding>
<element signature="e#433#440#1#HTML" expanded="true" />
</folding>
</state>
</provider>
</entry>
</component>
</project>

31
.idea/youtube-channel-manager.iml generated Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="YtManager/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/YtManagerApp/templates" />
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>

View File

@ -22,5 +22,7 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('ajax/get_children', views.ajax_get_children, name='ajax_get_children'),
path('ajax/get_folders', views.ajax_get_folders, name='ajax_get_folders'),
path('ajax/edit_folder', views.ajax_edit_folder, name='ajax_edit_folder'),
path('ajax/delete_folder/<int:fid>/', views.ajax_delete_folder, name='ajax_delete_folder'),
path(r'', views.index, name='home')
]

View File

@ -0,0 +1,44 @@
from .models import SubscriptionFolder, Subscription, Video
class FolderManager(object):
@staticmethod
def create_or_edit(fid, name, parent_id):
# Create or edit
if fid == '#':
folder = SubscriptionFolder()
else:
folder = SubscriptionFolder.objects.get(id=int(fid))
# Set attributes
folder.name = name
if parent_id == '#':
folder.parent = None
else:
folder.parent = SubscriptionFolder.objects.get(id=int(parent_id))
FolderManager.__validate(folder)
folder.save()
@staticmethod
def __validate(folder):
# Make sure folder name is unique in the parent folder
for dbFolder in SubscriptionFolder.objects.filter(parent_id=folder.parent_id):
if dbFolder.id != folder.id and dbFolder.name == folder.name:
raise ValueError('Folder name is not unique!')
# Prevent parenting loops
current = folder
visited = []
while not (current is None):
if current in visited:
raise ValueError('Parenting cycle detected!')
visited.append(current)
current = current.parent
@staticmethod
def delete(fid: int):
folder = SubscriptionFolder.objects.get(id=fid)
folder.delete()

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.2 on 2018-10-05 13:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('YtManagerApp', '0003_auto_20181003_1825'),
]
operations = [
migrations.AlterField(
model_name='video',
name='downloaded_path',
field=models.TextField(blank=True, null=True),
),
]

View File

@ -1,9 +1,31 @@
/* Some material font helpers */
.material-folder::before {
content: "\e2c7";
}
content: "\e2c7"; }
.material-person::before {
content: "\e7fd";
}
content: "\e7fd"; }
/* Loading animation */
.loading-dual-ring {
display: inline-block;
width: 64px;
height: 64px; }
.loading-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 1px;
border-radius: 50%;
border: 5px solid #007bff;
border-color: #007bff transparent #007bff transparent;
animation: loading-dual-ring 1.2s linear infinite; }
@keyframes loading-dual-ring {
0% {
transform: rotate(0deg); }
100% {
transform: rotate(360deg); } }
/*# sourceMappingURL=style.css.map */

View File

@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAEA,gCAAgC;AAChC,wBAAyB;EACrB,OAAO,EAAE,OAAO;;AAGpB,wBAAyB;EACrB,OAAO,EAAE,OAAO;;AAGpB,uBAAuB;AACvB,kBAAmB;EACf,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;;AAGhB,wBAAyB;EACrB,OAAO,EAAE,GAAG;EACZ,OAAO,EAAE,KAAK;EACd,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,MAAM,EAAE,GAAG;EACX,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,iBAAuB;EAC/B,YAAY,EAAE,uCAAmD;EACjE,SAAS,EAAE,sCAAsC;;AAGrD,4BAOC;EANG,EAAG;IACC,SAAS,EAAE,YAAY;EAE3B,IAAK;IACD,SAAS,EAAE,cAAc",
"sources": ["style.scss"],
"names": [],
"file": "style.css"
}

View File

@ -0,0 +1,38 @@
$accent-color: #007bff;
/* Some material font helpers */
.material-folder::before {
content: "\e2c7";
}
.material-person::before {
content: "\e7fd";
}
/* Loading animation */
.loading-dual-ring {
display: inline-block;
width: 64px;
height: 64px;
}
.loading-dual-ring:after {
content: " ";
display: block;
width: 46px;
height: 46px;
margin: 1px;
border-radius: 50%;
border: 5px solid $accent-color;
border-color: $accent-color transparent $accent-color transparent;
animation: loading-dual-ring 1.2s linear infinite;
}
@keyframes loading-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -4592,7 +4592,7 @@
if(theme_url === true) {
var dir = this.settings.core.themes.dir;
if(!dir) { dir = $.jstree.path + '/themes'; }
theme_url = dir + '/' + theme_name + '/style.css';
theme_url = dir + '/' + theme_name + '/_style.css';
}
if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
$('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');

View File

@ -1,50 +0,0 @@
function onSelectionChanged(e, data)
{
node = data.instance.get_selected(true)[0];
}
function validateChange(operation, node, parent, position, more)
{
if (more.dnd)
{
// create_node, rename_node, delete_node, move_node and copy_node
if (operation === "copy_node" || operation === "move_node")
{
if (more.ref.type === "sub")
return false;
}
}
return true;
}
function setupTree(dataIn)
{
$("#tree-wrapper").jstree({
core : {
data : {
url : 'ajax/get_children'
},
check_callback : validateChange,
themes : {
dots : false
},
},
types : {
folder : {
icon : "material-icons material-folder"
},
sub : {
icon : "material-icons material-person",
max_depth : 0
}
},
plugins : [ "types", "wholerow", "dnd" ]
});
$("#tree-wrapper").on("changed.jstree", onSelectionChanged);
}
$(document).ready(function ()
{
setupTree();
})

View File

@ -2,30 +2,38 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit folder</h5>
<h5 id="folder_edit_dialog_title" class="modal-title">Edit folder</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div id="folder_edit_dialog_loading" class="modal-body">
<div class="loading-dual-ring"></div>
<div id="folder_edit_dialog_error"></div>
</div>
<form id="folder_edit_dialog_form" action="{% url 'ajax_edit_folder' %}" method="post">
<div class="modal-body">
<form>
{% csrf_token %}
<input type="hidden" id="folder_edit_dialog_id" name="id" value="#">
<div class="form-group row">
<label class="col-sm-4" for="folder_edit_dialog_name">Name</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="folder_edit_dialog_name" placeholder="Folder name">
<label class="col-sm-3" for="folder_edit_dialog_name">Name</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="folder_edit_dialog_name" name="name" placeholder="Folder name">
</div>
</div>
<div class="form-check row">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
<div class="form-group row">
<label class="col-sm-3" for="folder_edit_dialog_parent">Parent folder</label>
<div class="col-sm-9">
<select class="form-control" id="folder_edit_dialog_parent" name="parent">
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">Save changes</button>
<button id="folder_edit_dialog_submit" type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,39 @@
<div id="subscription_edit_dialog" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 id="subscription_edit_dialog_title" class="modal-title">Edit subscription</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div id="subscription_edit_dialog_loading" class="modal-body">
<div class="loading-dual-ring"></div>
<div id="subscription_edit_dialog_error"></div>
</div>
<form id="subscription_edit_dialog_form" action="{% url 'ajax_edit_subscription' %}" method="post">
<div class="modal-body">
{% csrf_token %}
<input type="hidden" id="subscription_edit_dialog_id" name="id" value="#">
<div class="form-group row">
<label class="col-sm-3" for="subscription_edit_dialog_url">Link:</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="subscription_edit_dialog_name" name="name" placeholder="subscription name">
</div>
</div>
<div class="form-group row">
<label class="col-sm-3" for="subscription_edit_dialog_parent">Parent subscription</label>
<div class="col-sm-9">
<select class="form-control" id="subscription_edit_dialog_parent" name="parent">
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button id="subscription_edit_dialog_submit" type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -7,7 +7,12 @@
{% block scripts %}
<script src="{% static 'YtManagerApp/import/jstree/jstree.js' %}"></script>
<script src="{% static 'YtManagerApp/js/subtree.js' %}"></script>
<script>
{% include 'YtManagerApp/js/subscription_tree.js' %}
</script>
{% include 'YtManagerApp/controls/folder_edit_dialog.html' %}
{% endblock %}
{% block master %}
@ -17,10 +22,18 @@
<button type="button" class="btn btn-secondary" >
<i class="material-icons" aria-hidden="true">add</i>
</button>
<button type="button" class="btn btn-secondary">
<button id="btn_create_folder" type="button" class="btn btn-secondary">
<i class="material-icons" aria-hidden="true">create_new_folder</i>
</button>
</div>
<div class="btn-group btn-group-sm mr-2" role="group">
<button id="btn_edit_node" type="button" class="btn btn-secondary" >
<i class="material-icons" aria-hidden="true">edit</i>
</button>
<button id="btn_delete_node" type="button" class="btn btn-secondary" >
<i class="material-icons" aria-hidden="true">delete</i>
</button>
</div>
</div>
<div id="tree-wrapper">

View File

@ -0,0 +1,174 @@
function folderEditDialog_Show(isNew, editNode)
{
let dialog = $("#folder_edit_dialog");
dialog.find('#folder_edit_dialog_title').text(isNew ? "New folder" : "Edit folder");
dialog.find("#folder_edit_dialog_loading").show();
dialog.find("#folder_edit_dialog_error").hide();
dialog.find("#folder_edit_dialog_form").hide();
dialog.modal();
$.get("{% url 'ajax_get_folders' %}")
.done(function(folders)
{
// Populate list of folders
let selParent = dialog.find("#folder_edit_dialog_parent");
selParent.empty();
selParent.append(new Option('(None)', '#'));
let parentId = null;
if (!isNew) {
parentId = editNode.parent.replace('folder', '');
}
for (let folder of folders)
{
let o = new Option(folder.text, folder.id);
if (!isNew && folder.id.toString() === parentId.toString())
o.selected = true;
selParent.append(o);
}
// Show form
dialog.find("#folder_edit_dialog_loading").hide();
dialog.find("#folder_edit_dialog_form").show();
dialog.find("#folder_edit_dialog_submit").text(isNew ? "Create" : "Save");
if (isNew)
{
dialog.find("#folder_edit_dialog_id").val('#');
dialog.find("#folder_edit_dialog_name").val('');
}
if (!isNew)
{
idTrimmed = editNode.id.replace('folder', '');
dialog.find("#folder_edit_dialog_id").val(idTrimmed);
dialog.find("#folder_edit_dialog_name").val(editNode.text);
}
})
.fail(function() {
let msgError = dialog.find("#folder_edit_dialog_error");
msgError.show();
msgError.text("An error occurred!");
});
}
function folderEditDialog_ShowNew()
{
folderEditDialog_Show(true, null);
}
function folderEditDialog_Close()
{
$("#folder_edit_dialog").modal('hide');
}
function folderEditDialog_Submit(e)
{
let form = $(this);
let url = form.attr('action');
$.post(url, form.serialize())
.done(tree_Refresh);
folderEditDialog_Close();
e.preventDefault();
}
function treeNode_Edit()
{
let selectedNodes = $("#tree-wrapper").jstree('get_selected', true);
if (selectedNodes.length === 1)
{
let node = selectedNodes[0];
if (node.type === 'folder') {
folderEditDialog_Show(false, node);
}
else {
// TODO...
}
}
}
function treeNode_Delete()
{
let selectedNodes = $("#tree-wrapper").jstree('get_selected', true);
if (selectedNodes.length === 1)
{
let node = selectedNodes[0];
if (node.type === 'folder') {
let folderId = node.id.toString().replace('folder', '');
if (confirm('Are you sure you want to delete folder "' + node.text + '" and all its descendants?\nNote: the subscriptions won\'t be deleted, they will only be moved outside.'))
{
$.post("{% url 'ajax_delete_folder' 99999 %}".replace('99999', folderId), {
csrfmiddlewaretoken: '{{ csrf_token }}'
}).done(tree_Refresh);
}
}
else {
// TODO...
}
}
}
function tree_Initialize()
{
let treeWrapper = $("#tree-wrapper");
treeWrapper.jstree({
core : {
data : {
url : "{% url 'ajax_get_children' %}"
},
check_callback : tree_ValidateChange,
themes : {
dots : false
},
},
types : {
folder : {
icon : "material-icons material-folder"
},
sub : {
icon : "material-icons material-person",
max_depth : 0
}
},
plugins : [ "types", "wholerow", "dnd" ]
});
treeWrapper.on("changed.jstree", tree_OnSelectionChanged);
}
function tree_Refresh()
{
$("#tree-wrapper").jstree("refresh");
}
function tree_ValidateChange(operation, node, parent, position, more)
{
if (more.dnd)
{
// create_node, rename_node, delete_node, move_node and copy_node
if (operation === "copy_node" || operation === "move_node")
{
if (more.ref.type === "sub")
return false;
}
}
return true;
}
function tree_OnSelectionChanged(e, data)
{
node = data.instance.get_selected(true)[0];
}
$(document).ready(function ()
{
tree_Initialize();
$("#btn_create_folder").on("click", folderEditDialog_ShowNew);
$("#btn_edit_node").on("click", treeNode_Edit);
$("#btn_delete_node").on("click", treeNode_Delete);
$("#folder_edit_dialog_form").submit(folderEditDialog_Submit);
});

View File

@ -1,6 +1,8 @@
from django.shortcuts import render
from django.http import HttpResponse, HttpRequest, JsonResponse
from .models import SubscriptionFolder, Subscription
from .management import FolderManager
def get_children_recurse(parent_id):
children = []
@ -10,6 +12,7 @@ def get_children_recurse(parent_id):
"id": "folder" + str(folder.id),
"text": folder.name,
"type": "folder",
"state": {"opened": True},
"children": get_children_recurse(folder.id)
})
@ -25,11 +28,14 @@ def get_children_recurse(parent_id):
def get_folders(parent_id, path=""):
folders = []
prefix = path + "/"
if len(path) == 0:
prefix = ""
for folder in SubscriptionFolder.objects.filter(parent_id=parent_id).order_by('name'):
folder_path = path + "/" + folder.name
folder_path = prefix + folder.name
folders.append({
"id" : "folder" + str(folder.id),
"id": folder.id,
"text": folder_path
})
folders.extend(get_folders(folder.id, folder_path))
@ -40,9 +46,26 @@ def get_folders(parent_id, path = ""):
def ajax_get_children(request: HttpRequest):
return JsonResponse(get_children_recurse(None), safe=False)
def ajax_get_folders(request: HttpRequest):
return JsonResponse(get_folders(None), safe=False)
def ajax_edit_folder(request: HttpRequest):
if request.method == 'POST':
fid = request.POST['id']
name = request.POST['name']
parent_id = request.POST['parent']
FolderManager.create_or_edit(fid, name, parent_id)
return HttpResponse()
def ajax_delete_folder(request: HttpRequest, fid):
FolderManager.delete(fid)
return HttpResponse()
def index(request: HttpRequest):
context = {}
return render(request, 'YtManagerApp/index.html', context)